@bikdotai/bik-component-library 0.0.823 → 0.0.824
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/cjs/components/adbanner/AdBanner.js.map +1 -0
- package/dist/cjs/components/{banner → adbanner}/Banner.styled.js +13 -9
- package/dist/cjs/components/adbanner/Banner.styled.js.map +1 -0
- package/dist/cjs/components/adbanner/BannerInline.js.map +1 -0
- package/dist/cjs/components/adbanner/BannerWithImage.js.map +1 -0
- package/dist/cjs/components/adbanner/CardBanner.js.map +1 -0
- package/dist/cjs/components/adbanner/CardBannerWithImage.js.map +1 -0
- package/dist/cjs/components/adbanner/model.js.map +1 -0
- package/dist/cjs/components/agent-builder/components/CreateSubAgentPanel.js +1 -1
- package/dist/cjs/components/agent-builder/components/CreateSubAgentPanel.js.map +1 -1
- package/dist/cjs/components/agent-builder/context/AgentBuilderContext.js +1 -1
- package/dist/cjs/components/agent-builder/context/AgentBuilderContext.js.map +1 -1
- package/dist/cjs/components/agent-builder/utils/mentionSerialization.js +2 -2
- package/dist/cjs/components/agent-builder/utils/mentionSerialization.js.map +1 -1
- package/dist/cjs/editor/BikEditor.js +1 -1
- package/dist/cjs/editor/BikEditor.js.map +1 -1
- package/dist/cjs/editor/BikEditor.styles.js +143 -5
- package/dist/cjs/editor/BikEditor.styles.js.map +1 -1
- package/dist/cjs/editor/BikEditor.types.js.map +1 -1
- package/dist/cjs/editor/BikEditor.utils.js +3 -2
- package/dist/cjs/editor/BikEditor.utils.js.map +1 -1
- package/dist/cjs/editor/extensions/buildExtensions.js +3 -1
- package/dist/cjs/editor/extensions/buildExtensions.js.map +1 -1
- package/dist/cjs/editor/extensions/jsonHighlight/JsonHighlightExtension.js +4 -0
- package/dist/cjs/editor/extensions/jsonHighlight/JsonHighlightExtension.js.map +1 -0
- package/dist/cjs/editor/extensions/jsonHighlight/tokenizeJson.js +2 -0
- package/dist/cjs/editor/extensions/jsonHighlight/tokenizeJson.js.map +1 -0
- package/dist/cjs/icons/Actions/Common actions/ProductTour.js +2 -0
- package/dist/cjs/icons/Actions/Common actions/ProductTour.js.map +1 -0
- package/dist/cjs/icons/Actions/Common actions/RestApi.js +2 -0
- package/dist/cjs/icons/Actions/Common actions/RestApi.js.map +1 -0
- package/dist/cjs/icons/Actions/Common actions/Save.js +2 -0
- package/dist/cjs/icons/Actions/Common actions/Save.js.map +1 -0
- package/dist/cjs/icons/Actions/Common actions/Shortcut.js +2 -0
- package/dist/cjs/icons/Actions/Common actions/Shortcut.js.map +1 -0
- package/dist/cjs/icons/BIK AI specific/WandSparkles.js +2 -0
- package/dist/cjs/icons/BIK AI specific/WandSparkles.js.map +1 -0
- package/dist/cjs/icons/BIK AI specific/Webhook.js +2 -0
- package/dist/cjs/icons/BIK AI specific/Webhook.js.map +1 -0
- package/dist/cjs/icons/Informational/Store related/TicketForm.js +2 -0
- package/dist/cjs/icons/Informational/Store related/TicketForm.js.map +1 -0
- package/dist/cjs/icons/Legacy - Bikayi specific/Services/LoudSpeaker.js +2 -0
- package/dist/cjs/icons/Legacy - Bikayi specific/Services/LoudSpeaker.js.map +1 -0
- package/dist/cjs/icons/index.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/esm/components/adbanner/AdBanner.js.map +1 -0
- package/dist/esm/components/{banner → adbanner}/Banner.styled.js +18 -14
- package/dist/esm/components/adbanner/Banner.styled.js.map +1 -0
- package/dist/esm/components/adbanner/BannerInline.js.map +1 -0
- package/dist/esm/components/adbanner/BannerWithImage.js.map +1 -0
- package/dist/esm/components/adbanner/CardBanner.js.map +1 -0
- package/dist/esm/components/adbanner/CardBannerWithImage.js.map +1 -0
- package/dist/esm/components/adbanner/model.js.map +1 -0
- package/dist/esm/components/agent-builder/components/CreateSubAgentPanel.js +24 -24
- package/dist/esm/components/agent-builder/components/CreateSubAgentPanel.js.map +1 -1
- package/dist/esm/components/agent-builder/context/AgentBuilderContext.js +98 -101
- package/dist/esm/components/agent-builder/context/AgentBuilderContext.js.map +1 -1
- package/dist/esm/components/agent-builder/utils/mentionSerialization.d.ts +5 -7
- package/dist/esm/components/agent-builder/utils/mentionSerialization.js +40 -37
- package/dist/esm/components/agent-builder/utils/mentionSerialization.js.map +1 -1
- package/dist/esm/editor/BikEditor.js +247 -181
- package/dist/esm/editor/BikEditor.js.map +1 -1
- package/dist/esm/editor/BikEditor.styles.d.ts +9 -0
- package/dist/esm/editor/BikEditor.styles.js +147 -7
- package/dist/esm/editor/BikEditor.styles.js.map +1 -1
- package/dist/esm/editor/BikEditor.types.d.ts +76 -0
- package/dist/esm/editor/BikEditor.types.js.map +1 -1
- package/dist/esm/editor/BikEditor.utils.d.ts +15 -1
- package/dist/esm/editor/BikEditor.utils.js +155 -92
- package/dist/esm/editor/BikEditor.utils.js.map +1 -1
- package/dist/esm/editor/extensions/buildExtensions.d.ts +1 -1
- package/dist/esm/editor/extensions/buildExtensions.js +86 -55
- package/dist/esm/editor/extensions/buildExtensions.js.map +1 -1
- package/dist/esm/editor/extensions/jsonHighlight/JsonHighlightExtension.d.ts +14 -0
- package/dist/esm/editor/extensions/jsonHighlight/JsonHighlightExtension.js +132 -0
- package/dist/esm/editor/extensions/jsonHighlight/JsonHighlightExtension.js.map +1 -0
- package/dist/esm/editor/extensions/jsonHighlight/tokenizeJson.d.ts +18 -0
- package/dist/esm/editor/extensions/jsonHighlight/tokenizeJson.js +21 -0
- package/dist/esm/editor/extensions/jsonHighlight/tokenizeJson.js.map +1 -0
- package/dist/esm/editor/index.d.ts +1 -1
- package/dist/esm/icons/Actions/Common actions/ProductTour.d.ts +7 -0
- package/dist/esm/icons/Actions/Common actions/ProductTour.js +51 -0
- package/dist/esm/icons/Actions/Common actions/ProductTour.js.map +1 -0
- package/dist/esm/icons/Actions/Common actions/RestApi.d.ts +7 -0
- package/dist/esm/icons/Actions/Common actions/RestApi.js +29 -0
- package/dist/esm/icons/Actions/Common actions/RestApi.js.map +1 -0
- package/dist/esm/icons/Actions/Common actions/Save.d.ts +7 -0
- package/dist/esm/icons/Actions/Common actions/Save.js +40 -0
- package/dist/esm/icons/Actions/Common actions/Save.js.map +1 -0
- package/dist/esm/icons/Actions/Common actions/Shortcut.d.ts +7 -0
- package/dist/esm/icons/Actions/Common actions/Shortcut.js +40 -0
- package/dist/esm/icons/Actions/Common actions/Shortcut.js.map +1 -0
- package/dist/esm/icons/Actions/Common actions/index.d.ts +4 -0
- package/dist/esm/icons/BIK AI specific/WandSparkles.d.ts +7 -0
- package/dist/esm/icons/BIK AI specific/WandSparkles.js +39 -0
- package/dist/esm/icons/BIK AI specific/WandSparkles.js.map +1 -0
- package/dist/esm/icons/BIK AI specific/Webhook.d.ts +7 -0
- package/dist/esm/icons/BIK AI specific/Webhook.js +32 -0
- package/dist/esm/icons/BIK AI specific/Webhook.js.map +1 -0
- package/dist/esm/icons/BIK AI specific/index.d.ts +2 -0
- package/dist/esm/icons/Informational/Store related/TicketForm.d.ts +7 -0
- package/dist/esm/icons/Informational/Store related/TicketForm.js +65 -0
- package/dist/esm/icons/Informational/Store related/TicketForm.js.map +1 -0
- package/dist/esm/icons/Informational/Store related/index.d.ts +1 -0
- package/dist/esm/icons/Legacy - Bikayi specific/Services/LoudSpeaker.d.ts +7 -0
- package/dist/esm/icons/Legacy - Bikayi specific/Services/LoudSpeaker.js +35 -0
- package/dist/esm/icons/Legacy - Bikayi specific/Services/LoudSpeaker.js.map +1 -0
- package/dist/esm/icons/Legacy - Bikayi specific/Services/index.d.ts +1 -0
- package/dist/esm/icons/index.d.ts +4 -4
- package/dist/esm/icons/index.js +284 -268
- package/dist/esm/icons/index.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/package.json +1 -1
- package/dist/cjs/components/banner/AdBanner.js.map +0 -1
- package/dist/cjs/components/banner/Banner.styled.js.map +0 -1
- package/dist/cjs/components/banner/BannerInline.js.map +0 -1
- package/dist/cjs/components/banner/BannerWithImage.js.map +0 -1
- package/dist/cjs/components/banner/CardBanner.js.map +0 -1
- package/dist/cjs/components/banner/CardBannerWithImage.js.map +0 -1
- package/dist/cjs/components/banner/model.js.map +0 -1
- package/dist/esm/components/banner/AdBanner.js.map +0 -1
- package/dist/esm/components/banner/Banner.stories.d.ts +0 -39
- package/dist/esm/components/banner/Banner.styled.js.map +0 -1
- package/dist/esm/components/banner/BannerInline.js.map +0 -1
- package/dist/esm/components/banner/BannerWithImage.js.map +0 -1
- package/dist/esm/components/banner/CardBanner.js.map +0 -1
- package/dist/esm/components/banner/CardBannerWithImage.js.map +0 -1
- package/dist/esm/components/banner/model.js.map +0 -1
- /package/dist/cjs/components/{banner → adbanner}/AdBanner.js +0 -0
- /package/dist/cjs/components/{banner → adbanner}/BannerInline.js +0 -0
- /package/dist/cjs/components/{banner → adbanner}/BannerWithImage.js +0 -0
- /package/dist/cjs/components/{banner → adbanner}/CardBanner.js +0 -0
- /package/dist/cjs/components/{banner → adbanner}/CardBannerWithImage.js +0 -0
- /package/dist/cjs/components/{banner → adbanner}/model.js +0 -0
- /package/dist/esm/components/{banner → adbanner}/AdBanner.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/AdBanner.js +0 -0
- /package/dist/esm/components/{banner → adbanner}/Banner.styled.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/BannerInline.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/BannerInline.js +0 -0
- /package/dist/esm/components/{banner → adbanner}/BannerWithImage.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/BannerWithImage.js +0 -0
- /package/dist/esm/components/{banner → adbanner}/CardBanner.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/CardBanner.js +0 -0
- /package/dist/esm/components/{banner → adbanner}/CardBannerWithImage.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/CardBannerWithImage.js +0 -0
- /package/dist/esm/components/{banner → adbanner}/index.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/model.d.ts +0 -0
- /package/dist/esm/components/{banner → adbanner}/model.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BikEditor.utils.js","sources":["../../../src/editor/BikEditor.utils.ts"],"sourcesContent":["import { Editor } from '@tiptap/core';\nimport { DOMParser, DOMSerializer } from 'prosemirror-model';\nimport { EditorSnapshot, FormatState } from './BikEditor.types';\nimport { normalizeHardBreaks } from './extensions/plainClipboard/pasteUtils';\n\n// ---------------------------------------------------------------------------\n// HTML normalisation\n// ---------------------------------------------------------------------------\n\n/**\n * Normalise HTML before passing it to TipTap.\n *\n * 1. Unwrap style-less `<span>` wrappers (plain spans, and editor-cursor spans\n * like Quill's `<span class=\"ql-cursor\">`). Spans that carry inline styling\n * (`color` / `font-family` / `font-size` render as styled spans) or that\n * represent a `{{variable}}` token are PRESERVED — otherwise a setContent\n * round-trip would silently drop colour/font formatting that paste keeps.\n * 2. Collapse placeholder paragraphs (`<p><br></p>`) to `<p></p>`. ProseMirror\n * parses `<p></p>` as an empty paragraph and adds its own DOM `<br>` for the\n * cursor — one blank line, correct height. Leaving the source `<br>` would\n * create a `hard_break` child AND the cursor `<br>` → double-height blank line.\n *\n * Uses the DOM (regex cannot selectively pair span tags). `normalizeHtml` runs\n * in the component body for `initialContent`, so it is guarded for SSR: the\n * editor is created lazily on the client (`immediatelyRender: false`), meaning\n * the value is only parsed client-side and passing through on the server is safe.\n */\nexport function normalizeHtml(html: string): string {\n\tif (!html || typeof document === 'undefined') return html;\n\n\tconst container = document.createElement('div');\n\tcontainer.innerHTML = html;\n\n\tcontainer.querySelectorAll('span').forEach((span) => {\n\t\t// Keep schema-backed spans: styled spans, variables, and mention chips\n\t\t// (`data-type=\"mention…\"`). Stripping a mention span would drop the\n\t\t// `data-id`/`data-label`/`data-kind` the editor needs to reparse it.\n\t\tconst dataType = span.getAttribute('data-type');\n\t\tif (\n\t\t\tspan.hasAttribute('style') ||\n\t\t\tspan.hasAttribute('data-variable') ||\n\t\t\t(dataType !== null && dataType.startsWith('mention'))\n\t\t)\n\t\t\treturn;\n\t\tconst parent = span.parentNode;\n\t\tif (!parent) return;\n\t\twhile (span.firstChild) parent.insertBefore(span.firstChild, span);\n\t\tparent.removeChild(span);\n\t});\n\n\tcontainer.querySelectorAll('p').forEach((p) => {\n\t\tif (\n\t\t\tp.querySelectorAll('br').length === 1 &&\n\t\t\t(p.textContent ?? '').trim() === ''\n\t\t) {\n\t\t\tp.innerHTML = '';\n\t\t}\n\t});\n\n\treturn container.innerHTML;\n}\n\n/**\n * Make editor HTML portable across email clients and chat renderers.\n *\n * 1. Preserve consecutive spaces by converting every second space in a run\n * to ` ` (U+00A0). Standard HTML collapses adjacent regular spaces;\n * alternating with ` ` prevents that — same convention Gmail uses.\n * 2. Double a trailing `<br>` in content paragraphs so the blank line is\n * visible. A single trailing `<br>` before `</p>` is swallowed by most\n * email clients; `<br><br>` renders as a visible blank line (matches\n * Gmail's output).\n * 3. Convert empty `<p></p>` to `<p><br></p>` — email clients collapse\n * zero-height paragraphs without a `<br>`.\n */\nexport function toPortableHtml(html: string): string {\n\tconst div = document.createElement('div');\n\tdiv.innerHTML = html;\n\n\t// TreeWalker visits only text nodes, skipping HTML tags/attributes, so we safely\n\t// replace consecutive spaces with space + \\u00a0 (nbsp) to prevent email clients from collapsing them\n\tconst walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);\n\tlet textNode: Text | null;\n\twhile ((textNode = walker.nextNode() as Text | null)) {\n\t\tif (textNode.nodeValue && textNode.nodeValue.includes(' ')) {\n\t\t\ttextNode.nodeValue = textNode.nodeValue.replace(/ {2}/g, ' ');\n\t\t}\n\t}\n\n\tconst blocks = div.querySelectorAll('p');\n\tfor (const p of Array.from(blocks)) {\n\t\tif (p.childNodes.length === 0) {\n\t\t\tp.appendChild(document.createElement('br'));\n\t\t\tcontinue;\n\t\t}\n\t\tconst last = p.lastChild;\n\t\tif (last instanceof HTMLBRElement && p.childNodes.length > 1) {\n\t\t\tp.appendChild(document.createElement('br'));\n\t\t}\n\t}\n\treturn div.innerHTML;\n}\n\n// ---------------------------------------------------------------------------\n// Inline content insertion\n// ---------------------------------------------------------------------------\n\n/**\n * Insert HTML at the current cursor position, merging the first paragraph\n * inline while preserving the block structure of subsequent paragraphs/lists.\n *\n * Uses `parseSlice` which returns an open-ended Slice — the first block merges\n * at the cursor, middle blocks stay intact, and the last block merges with any\n * text that follows the cursor.\n */\nexport function insertInlineHtml(editor: Editor, html: string): void {\n\tconst normalised = normalizeHtml(html);\n\tconst dom = document.createElement('div');\n\tdom.innerHTML = normalised;\n\tconst slice = DOMParser.fromSchema(editor.schema).parseSlice(dom, {\n\t\tpreserveWhitespace: true,\n\t});\n\n\tif (slice.size === 0) return;\n\n\tconst { from, to } = editor.state.selection;\n\tconst tr = editor.state.tr.replaceRange(from, to, slice);\n\teditor.view.dispatch(tr);\n}\n\n// ---------------------------------------------------------------------------\n// Section divider HTML\n// ---------------------------------------------------------------------------\n\nexport const SECTION_DIVIDER_HTML = (id: string) =>\n\t`<div data-section-divider=\"${id}\" style=\"display:none;height:0;line-height:0;overflow:hidden;\"></div>`;\n\n/**\n * Build initial HTML for an editor with one or more named sections below the body.\n * Each section is separated by an invisible divider node.\n *\n * @example\n * buildSectionedContent('<p>Body</p>', [\n * { id: 'signature', content: '<p>Alice</p>' },\n * { id: 'forwarded', content: '<p>--- fwd ---</p>' },\n * ])\n */\nexport function buildSectionedContent(\n\tbody: string,\n\tsections: Array<{ id: string; content: string }>,\n): string {\n\tlet html = body;\n\tfor (const s of sections) {\n\t\thtml += SECTION_DIVIDER_HTML(s.id) + s.content;\n\t}\n\treturn html;\n}\n\n// ---------------------------------------------------------------------------\n// Section extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Parse the editor HTML into a Map of sectionId → raw HTML string.\n * The special key `'body'` always holds the content before the first divider.\n * Map iteration order matches document order.\n */\nexport function extractAllSections(editor: Editor): Map<string, string> {\n\treturn extractAllSectionsFromHtml(editor.getHTML());\n}\n\n/**\n * Extract just the body portion (everything before the first section divider)\n * from an HTML string produced by BikEditor. Safe to call on plain HTML too —\n * returns the full string when no dividers are present.\n */\nexport function getBodyHtml(html: string): string {\n\tif (!html) return '';\n\tconst sections = extractAllSectionsFromHtml(html);\n\treturn sections.get('body') ?? html;\n}\n\n/**\n * Extract everything from the first section divider onward (dividers + their\n * content). Returns an empty string when there are no sections.\n */\nexport function getSectionsHtml(html: string): string {\n\tif (!html) return '';\n\tconst body = getBodyHtml(html);\n\treturn html.substring(body.length);\n}\n\nexport function extractAllSectionsFromHtml(html: string): Map<string, string> {\n\tconst sections = new Map<string, string>();\n\tconst dividerRegex =\n\t\t/<div[^>]*data-section-divider=\"([^\"]*)[^>]*>\\s*<\\/div>/g;\n\tconst dividers: Array<{ id: string; index: number; endIndex: number }> = [];\n\n\tlet match: RegExpExecArray | null;\n\twhile ((match = dividerRegex.exec(html)) !== null) {\n\t\tdividers.push({\n\t\t\tid: match[1],\n\t\t\tindex: match.index,\n\t\t\tendIndex: match.index + match[0].length,\n\t\t});\n\t}\n\n\tif (dividers.length === 0) {\n\t\tsections.set('body', html);\n\t\treturn sections;\n\t}\n\n\tsections.set('body', html.slice(0, dividers[0].index));\n\tfor (let i = 0; i < dividers.length; i++) {\n\t\tconst start = dividers[i].endIndex;\n\t\tconst end = i + 1 < dividers.length ? dividers[i + 1].index : html.length;\n\t\tsections.set(dividers[i].id, html.slice(start, end));\n\t}\n\n\treturn sections;\n}\n\n/** Returns BikEditorContent for a single named section (or 'body'). */\nexport function extractSectionContent(\n\teditor: Editor,\n\tid: string,\n): EditorSnapshot {\n\tconst startPos = findSectionStartPos(editor, id);\n\tif (startPos === -1)\n\t\treturn { html: '', text: '', isEmpty: true, characterCount: 0 };\n\tconst endPos = findSectionEndPos(editor, id);\n\tconst slice = editor.state.doc.slice(startPos, endPos);\n\tconst container = document.createElement('div');\n\tconst serializer = DOMSerializer.fromSchema(editor.schema);\n\tcontainer.appendChild(serializer.serializeFragment(slice.content));\n\tconst html = toPortableHtml(container.innerHTML);\n\tconst text = container.textContent ?? '';\n\treturn { html, text, isEmpty: !text.trim(), characterCount: text.length };\n}\n\n/**\n * Parse an HTML string into ProseMirror nodes using the editor's schema.\n */\nfunction parseHtmlToNodes(editor: Editor, html: string) {\n\tconst container = document.createElement('div');\n\tcontainer.innerHTML = html;\n\treturn DOMParser.fromSchema(editor.schema).parse(container, {\n\t\tpreserveWhitespace: true,\n\t}).content;\n}\n\n/**\n * Replace the content of a single named section without touching others.\n * Uses a ProseMirror transaction to replace only the target range,\n * preserving undo history and other sections' state.\n * If the section doesn't exist yet, it is appended at the end of the document.\n */\nexport function setSectionContentInEditor(\n\teditor: Editor,\n\tid: string,\n\thtml: string,\n): void {\n\tconst startPos = findSectionStartPos(editor, id);\n\tif (startPos === -1) {\n\t\tconst endPos = editor.state.doc.content.size;\n\t\tconst dividerHtml = SECTION_DIVIDER_HTML(id) + html;\n\t\teditor.commands.insertContentAt(endPos, dividerHtml, {\n\t\t\tupdateSelection: false,\n\t\t});\n\t\treturn;\n\t}\n\tconst endPos = findSectionEndPos(editor, id);\n\tconst fragment = parseHtmlToNodes(editor, html);\n\tconst { tr } = editor.state;\n\t// For body, replace from position 0 (before the first paragraph's opening\n\t// boundary) so ProseMirror swaps entire block nodes cleanly. Position 1\n\t// (inside the paragraph) would force ProseMirror to close the open paragraph\n\t// first, creating a ghost empty <p></p>.\n\tconst replaceFrom = id === 'body' ? 0 : startPos;\n\ttr.replaceWith(replaceFrom, endPos, fragment);\n\ttr.setMeta('addToHistory', false);\n\teditor.view.dispatch(tr);\n}\n\n// ---------------------------------------------------------------------------\n// Convenience shorthands (body = first section)\n// ---------------------------------------------------------------------------\n\n/** Extract BikEditorContent for just the body (before the first divider). */\nexport function extractBodyContent(editor: Editor): EditorSnapshot {\n\treturn extractSectionContent(editor, 'body');\n}\n\n// ---------------------------------------------------------------------------\n// Active formats\n// ---------------------------------------------------------------------------\n\nexport function extractActiveFormats(editor: Editor): FormatState {\n\treturn {\n\t\tbold: editor.isActive('bold'),\n\t\titalic: editor.isActive('italic'),\n\t\tunderline: editor.isActive('underline'),\n\t\tstrike: editor.isActive('strike'),\n\t\tbulletList: editor.isActive('bulletList'),\n\t\torderedList: editor.isActive('orderedList'),\n\t\tblockquote: editor.isActive('blockquote'),\n\t\tcodeBlock: editor.isActive('codeBlock'),\n\t\tlink: (() => {\n\t\t\tif (editor.isActive('link')) {\n\t\t\t\treturn { href: editor.getAttributes('link')['href'] ?? '' };\n\t\t\t}\n\t\t\tconst { $from } = editor.state.selection;\n\t\t\tif ($from.pos > 0) {\n\t\t\t\tconst before = $from.doc.resolve($from.pos);\n\t\t\t\tconst linkMark = before.marks().find((m) => m.type.name === 'link');\n\t\t\t\tif (!linkMark) {\n\t\t\t\t\tconst nodeBefore = $from.nodeBefore;\n\t\t\t\t\tconst markOnPrev = nodeBefore?.marks.find(\n\t\t\t\t\t\t(m) => m.type.name === 'link',\n\t\t\t\t\t);\n\t\t\t\t\tif (markOnPrev) {\n\t\t\t\t\t\treturn { href: markOnPrev.attrs['href'] ?? '' };\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn { href: linkMark.attrs['href'] ?? '' };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t})(),\n\t\ttextAlign: editor.isActive({ textAlign: 'center' })\n\t\t\t? 'center'\n\t\t\t: editor.isActive({ textAlign: 'right' })\n\t\t\t? 'right'\n\t\t\t: editor.isActive({ textAlign: 'justify' })\n\t\t\t? 'justify'\n\t\t\t: editor.isActive({ textAlign: 'left' })\n\t\t\t? 'left'\n\t\t\t: null,\n\t\tfontFamily: editor.getAttributes('textStyle')['fontFamily'] ?? null,\n\t\tfontSize: editor.getAttributes('textStyle')['fontSize'] ?? null,\n\t\tcolor: editor.getAttributes('textStyle')['color'] ?? null,\n\t\thighlight: editor.isActive('highlight')\n\t\t\t? editor.getAttributes('highlight')['color'] ?? 'default'\n\t\t\t: null,\n\t\tsuperscript: editor.isActive('superscript'),\n\t\tsubscript: editor.isActive('subscript'),\n\t};\n}\n\nexport function extractContent(editor: Editor): EditorSnapshot {\n\tconst html = toPortableHtml(editor.getHTML());\n\tconst normalized = normalizeHardBreaks(editor.state.doc.content);\n\tconst tempDoc = editor.schema.node('doc', null, normalized);\n\t// Paragraph boundary → blank line (\\n\\n), soft line break (hardBreak) → \\n.\n\t// Matches Gmail's plain-text output for the same content.\n\tconst text = tempDoc.textBetween(0, tempDoc.content.size, '\\n\\n', '\\n');\n\treturn {\n\t\thtml,\n\t\ttext,\n\t\tisEmpty: editor.isEmpty,\n\t\tcharacterCount: text.length,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Section position helpers (used by insertAtSectionStart/End and appendBodyContent)\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the ProseMirror position immediately after a section's opening divider\n * (i.e. the start of the section's own content).\n * For `'body'`, returns 1 (beginning of the document).\n * Returns -1 if the named section's divider is not found.\n */\nexport function findSectionStartPos(editor: Editor, sectionId: string): number {\n\tif (sectionId === 'body') return 1;\n\tlet startPos = -1;\n\teditor.state.doc.descendants((node, pos) => {\n\t\tif (startPos !== -1) return false;\n\t\tif (\n\t\t\tnode.type.name === 'sectionDivider' &&\n\t\t\tnode.attrs['sectionId'] === sectionId\n\t\t) {\n\t\t\tstartPos = pos + node.nodeSize; // position right after the divider\n\t\t}\n\t});\n\treturn startPos;\n}\n\n/**\n * Returns the ProseMirror position at the end of a named section's content:\n * - For `'body'`: position of the first section divider, or end of document.\n * - For a named section: position of the next divider, or end of document.\n */\nexport function findSectionEndPos(editor: Editor, sectionId: string): number {\n\tconst doc = editor.state.doc;\n\tconst docSize = doc.content.size;\n\n\tif (sectionId === 'body') {\n\t\tlet firstDividerPos = -1;\n\t\tdoc.descendants((node, pos) => {\n\t\t\tif (firstDividerPos !== -1) return false;\n\t\t\tif (node.type.name === 'sectionDivider') {\n\t\t\t\tfirstDividerPos = pos;\n\t\t\t}\n\t\t});\n\t\treturn firstDividerPos !== -1 ? firstDividerPos : docSize;\n\t}\n\n\tlet passedSection = false;\n\tlet nextDividerPos = -1;\n\tdoc.descendants((node, pos) => {\n\t\tif (nextDividerPos !== -1) return false;\n\t\tif (!passedSection) {\n\t\t\tif (\n\t\t\t\tnode.type.name === 'sectionDivider' &&\n\t\t\t\tnode.attrs['sectionId'] === sectionId\n\t\t\t) {\n\t\t\t\tpassedSection = true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tif (node.type.name === 'sectionDivider') {\n\t\t\tnextDividerPos = pos;\n\t\t}\n\t});\n\treturn nextDividerPos !== -1 ? nextDividerPos : docSize;\n}\n"],"names":["normalizeHtml","html","container","span","dataType","parent","p","toPortableHtml","div","walker","textNode","blocks","insertInlineHtml","editor","normalised","dom","slice","DOMParser","from","to","tr","SECTION_DIVIDER_HTML","id","buildSectionedContent","body","sections","s","getBodyHtml","extractAllSectionsFromHtml","getSectionsHtml","dividerRegex","dividers","match","i","start","end","extractSectionContent","startPos","findSectionStartPos","endPos","findSectionEndPos","serializer","DOMSerializer","text","parseHtmlToNodes","setSectionContentInEditor","dividerHtml","fragment","replaceFrom","extractBodyContent","extractActiveFormats","$from","linkMark","m","markOnPrev","extractContent","normalized","normalizeHardBreaks","tempDoc","sectionId","node","pos","doc","docSize","firstDividerPos","passedSection","nextDividerPos"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BikEditor.utils.js","sources":["../../../src/editor/BikEditor.utils.ts"],"sourcesContent":["import { Editor } from '@tiptap/core';\nimport { DOMParser, DOMSerializer } from 'prosemirror-model';\nimport {\n\tEditorSnapshot,\n\tFormatState,\n\tJsonParseResult,\n} from './BikEditor.types';\nimport { normalizeHardBreaks } from './extensions/plainClipboard/pasteUtils';\n\n// ---------------------------------------------------------------------------\n// HTML normalisation\n// ---------------------------------------------------------------------------\n\n/**\n * Normalise HTML before passing it to TipTap.\n *\n * 1. Unwrap style-less `<span>` wrappers (plain spans, and editor-cursor spans\n * like Quill's `<span class=\"ql-cursor\">`). Spans that carry inline styling\n * (`color` / `font-family` / `font-size` render as styled spans) or that\n * represent a `{{variable}}` token are PRESERVED — otherwise a setContent\n * round-trip would silently drop colour/font formatting that paste keeps.\n * 2. Collapse placeholder paragraphs (`<p><br></p>`) to `<p></p>`. ProseMirror\n * parses `<p></p>` as an empty paragraph and adds its own DOM `<br>` for the\n * cursor — one blank line, correct height. Leaving the source `<br>` would\n * create a `hard_break` child AND the cursor `<br>` → double-height blank line.\n *\n * Uses the DOM (regex cannot selectively pair span tags). `normalizeHtml` runs\n * in the component body for `initialContent`, so it is guarded for SSR: the\n * editor is created lazily on the client (`immediatelyRender: false`), meaning\n * the value is only parsed client-side and passing through on the server is safe.\n */\nexport function normalizeHtml(html: string): string {\n\tif (!html || typeof document === 'undefined') return html;\n\n\tconst container = document.createElement('div');\n\tcontainer.innerHTML = html;\n\n\tcontainer.querySelectorAll('span').forEach((span) => {\n\t\t// Keep schema-backed spans: styled spans, variables, and mention chips\n\t\t// (`data-type=\"mention…\"`). Stripping a mention span would drop the\n\t\t// `data-id`/`data-label`/`data-kind` the editor needs to reparse it.\n\t\tconst dataType = span.getAttribute('data-type');\n\t\tif (\n\t\t\tspan.hasAttribute('style') ||\n\t\t\tspan.hasAttribute('data-variable') ||\n\t\t\t(dataType !== null && dataType.startsWith('mention'))\n\t\t)\n\t\t\treturn;\n\t\tconst parent = span.parentNode;\n\t\tif (!parent) return;\n\t\twhile (span.firstChild) parent.insertBefore(span.firstChild, span);\n\t\tparent.removeChild(span);\n\t});\n\n\tcontainer.querySelectorAll('p').forEach((p) => {\n\t\tif (\n\t\t\tp.querySelectorAll('br').length === 1 &&\n\t\t\t(p.textContent ?? '').trim() === ''\n\t\t) {\n\t\t\tp.innerHTML = '';\n\t\t}\n\t});\n\n\treturn container.innerHTML;\n}\n\n/**\n * Make editor HTML portable across email clients and chat renderers.\n *\n * 1. Preserve consecutive spaces by converting every second space in a run\n * to ` ` (U+00A0). Standard HTML collapses adjacent regular spaces;\n * alternating with ` ` prevents that — same convention Gmail uses.\n * 2. Double a trailing `<br>` in content paragraphs so the blank line is\n * visible. A single trailing `<br>` before `</p>` is swallowed by most\n * email clients; `<br><br>` renders as a visible blank line (matches\n * Gmail's output).\n * 3. Convert empty `<p></p>` to `<p><br></p>` — email clients collapse\n * zero-height paragraphs without a `<br>`.\n */\nexport function toPortableHtml(html: string): string {\n\tconst div = document.createElement('div');\n\tdiv.innerHTML = html;\n\n\t// TreeWalker visits only text nodes, skipping HTML tags/attributes, so we safely\n\t// replace consecutive spaces with space + \\u00a0 (nbsp) to prevent email clients from collapsing them\n\tconst walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);\n\tlet textNode: Text | null;\n\twhile ((textNode = walker.nextNode() as Text | null)) {\n\t\tif (textNode.nodeValue && textNode.nodeValue.includes(' ')) {\n\t\t\ttextNode.nodeValue = textNode.nodeValue.replace(/ {2}/g, ' ');\n\t\t}\n\t}\n\n\tconst blocks = div.querySelectorAll('p');\n\tfor (const p of Array.from(blocks)) {\n\t\tif (p.childNodes.length === 0) {\n\t\t\tp.appendChild(document.createElement('br'));\n\t\t\tcontinue;\n\t\t}\n\t\tconst last = p.lastChild;\n\t\tif (last instanceof HTMLBRElement && p.childNodes.length > 1) {\n\t\t\tp.appendChild(document.createElement('br'));\n\t\t}\n\t}\n\treturn div.innerHTML;\n}\n\n// ---------------------------------------------------------------------------\n// Inline content insertion\n// ---------------------------------------------------------------------------\n\n/**\n * Insert HTML at the current cursor position, merging the first paragraph\n * inline while preserving the block structure of subsequent paragraphs/lists.\n *\n * Uses `parseSlice` which returns an open-ended Slice — the first block merges\n * at the cursor, middle blocks stay intact, and the last block merges with any\n * text that follows the cursor.\n */\nexport function insertInlineHtml(editor: Editor, html: string): void {\n\tconst normalised = normalizeHtml(html);\n\tconst dom = document.createElement('div');\n\tdom.innerHTML = normalised;\n\tconst slice = DOMParser.fromSchema(editor.schema).parseSlice(dom, {\n\t\tpreserveWhitespace: true,\n\t});\n\n\tif (slice.size === 0) return;\n\n\tconst { from, to } = editor.state.selection;\n\tconst tr = editor.state.tr.replaceRange(from, to, slice);\n\teditor.view.dispatch(tr);\n}\n\n// ---------------------------------------------------------------------------\n// Section divider HTML\n// ---------------------------------------------------------------------------\n\nexport const SECTION_DIVIDER_HTML = (id: string) =>\n\t`<div data-section-divider=\"${id}\" style=\"display:none;height:0;line-height:0;overflow:hidden;\"></div>`;\n\n/**\n * Build initial HTML for an editor with one or more named sections below the body.\n * Each section is separated by an invisible divider node.\n *\n * @example\n * buildSectionedContent('<p>Body</p>', [\n * { id: 'signature', content: '<p>Alice</p>' },\n * { id: 'forwarded', content: '<p>--- fwd ---</p>' },\n * ])\n */\nexport function buildSectionedContent(\n\tbody: string,\n\tsections: Array<{ id: string; content: string }>,\n): string {\n\tlet html = body;\n\tfor (const s of sections) {\n\t\thtml += SECTION_DIVIDER_HTML(s.id) + s.content;\n\t}\n\treturn html;\n}\n\n// ---------------------------------------------------------------------------\n// Section extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Parse the editor HTML into a Map of sectionId → raw HTML string.\n * The special key `'body'` always holds the content before the first divider.\n * Map iteration order matches document order.\n */\nexport function extractAllSections(editor: Editor): Map<string, string> {\n\treturn extractAllSectionsFromHtml(editor.getHTML());\n}\n\n/**\n * Extract just the body portion (everything before the first section divider)\n * from an HTML string produced by BikEditor. Safe to call on plain HTML too —\n * returns the full string when no dividers are present.\n */\nexport function getBodyHtml(html: string): string {\n\tif (!html) return '';\n\tconst sections = extractAllSectionsFromHtml(html);\n\treturn sections.get('body') ?? html;\n}\n\n/**\n * Extract everything from the first section divider onward (dividers + their\n * content). Returns an empty string when there are no sections.\n */\nexport function getSectionsHtml(html: string): string {\n\tif (!html) return '';\n\tconst body = getBodyHtml(html);\n\treturn html.substring(body.length);\n}\n\nexport function extractAllSectionsFromHtml(html: string): Map<string, string> {\n\tconst sections = new Map<string, string>();\n\tconst dividerRegex =\n\t\t/<div[^>]*data-section-divider=\"([^\"]*)[^>]*>\\s*<\\/div>/g;\n\tconst dividers: Array<{ id: string; index: number; endIndex: number }> = [];\n\n\tlet match: RegExpExecArray | null;\n\twhile ((match = dividerRegex.exec(html)) !== null) {\n\t\tdividers.push({\n\t\t\tid: match[1],\n\t\t\tindex: match.index,\n\t\t\tendIndex: match.index + match[0].length,\n\t\t});\n\t}\n\n\tif (dividers.length === 0) {\n\t\tsections.set('body', html);\n\t\treturn sections;\n\t}\n\n\tsections.set('body', html.slice(0, dividers[0].index));\n\tfor (let i = 0; i < dividers.length; i++) {\n\t\tconst start = dividers[i].endIndex;\n\t\tconst end = i + 1 < dividers.length ? dividers[i + 1].index : html.length;\n\t\tsections.set(dividers[i].id, html.slice(start, end));\n\t}\n\n\treturn sections;\n}\n\n/** Returns BikEditorContent for a single named section (or 'body'). */\nexport function extractSectionContent(\n\teditor: Editor,\n\tid: string,\n): EditorSnapshot {\n\tconst startPos = findSectionStartPos(editor, id);\n\tif (startPos === -1)\n\t\treturn { html: '', text: '', isEmpty: true, characterCount: 0 };\n\tconst endPos = findSectionEndPos(editor, id);\n\tconst slice = editor.state.doc.slice(startPos, endPos);\n\tconst container = document.createElement('div');\n\tconst serializer = DOMSerializer.fromSchema(editor.schema);\n\tcontainer.appendChild(serializer.serializeFragment(slice.content));\n\tconst html = toPortableHtml(container.innerHTML);\n\tconst text = container.textContent ?? '';\n\treturn { html, text, isEmpty: !text.trim(), characterCount: text.length };\n}\n\n/**\n * Parse an HTML string into ProseMirror nodes using the editor's schema.\n */\nfunction parseHtmlToNodes(editor: Editor, html: string) {\n\tconst container = document.createElement('div');\n\tcontainer.innerHTML = html;\n\treturn DOMParser.fromSchema(editor.schema).parse(container, {\n\t\tpreserveWhitespace: true,\n\t}).content;\n}\n\n/**\n * Replace the content of a single named section without touching others.\n * Uses a ProseMirror transaction to replace only the target range,\n * preserving undo history and other sections' state.\n * If the section doesn't exist yet, it is appended at the end of the document.\n */\nexport function setSectionContentInEditor(\n\teditor: Editor,\n\tid: string,\n\thtml: string,\n): void {\n\tconst startPos = findSectionStartPos(editor, id);\n\tif (startPos === -1) {\n\t\tconst endPos = editor.state.doc.content.size;\n\t\tconst dividerHtml = SECTION_DIVIDER_HTML(id) + html;\n\t\teditor.commands.insertContentAt(endPos, dividerHtml, {\n\t\t\tupdateSelection: false,\n\t\t});\n\t\treturn;\n\t}\n\tconst endPos = findSectionEndPos(editor, id);\n\tconst fragment = parseHtmlToNodes(editor, html);\n\tconst { tr } = editor.state;\n\t// For body, replace from position 0 (before the first paragraph's opening\n\t// boundary) so ProseMirror swaps entire block nodes cleanly. Position 1\n\t// (inside the paragraph) would force ProseMirror to close the open paragraph\n\t// first, creating a ghost empty <p></p>.\n\tconst replaceFrom = id === 'body' ? 0 : startPos;\n\ttr.replaceWith(replaceFrom, endPos, fragment);\n\ttr.setMeta('addToHistory', false);\n\teditor.view.dispatch(tr);\n}\n\n// ---------------------------------------------------------------------------\n// Convenience shorthands (body = first section)\n// ---------------------------------------------------------------------------\n\n/** Extract BikEditorContent for just the body (before the first divider). */\nexport function extractBodyContent(editor: Editor): EditorSnapshot {\n\treturn extractSectionContent(editor, 'body');\n}\n\n// ---------------------------------------------------------------------------\n// Active formats\n// ---------------------------------------------------------------------------\n\nexport function extractActiveFormats(editor: Editor): FormatState {\n\treturn {\n\t\tbold: editor.isActive('bold'),\n\t\titalic: editor.isActive('italic'),\n\t\tunderline: editor.isActive('underline'),\n\t\tstrike: editor.isActive('strike'),\n\t\tbulletList: editor.isActive('bulletList'),\n\t\torderedList: editor.isActive('orderedList'),\n\t\tblockquote: editor.isActive('blockquote'),\n\t\tcodeBlock: editor.isActive('codeBlock'),\n\t\tlink: (() => {\n\t\t\tif (editor.isActive('link')) {\n\t\t\t\treturn { href: editor.getAttributes('link')['href'] ?? '' };\n\t\t\t}\n\t\t\tconst { $from } = editor.state.selection;\n\t\t\tif ($from.pos > 0) {\n\t\t\t\tconst before = $from.doc.resolve($from.pos);\n\t\t\t\tconst linkMark = before.marks().find((m) => m.type.name === 'link');\n\t\t\t\tif (!linkMark) {\n\t\t\t\t\tconst nodeBefore = $from.nodeBefore;\n\t\t\t\t\tconst markOnPrev = nodeBefore?.marks.find(\n\t\t\t\t\t\t(m) => m.type.name === 'link',\n\t\t\t\t\t);\n\t\t\t\t\tif (markOnPrev) {\n\t\t\t\t\t\treturn { href: markOnPrev.attrs['href'] ?? '' };\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn { href: linkMark.attrs['href'] ?? '' };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t})(),\n\t\ttextAlign: editor.isActive({ textAlign: 'center' })\n\t\t\t? 'center'\n\t\t\t: editor.isActive({ textAlign: 'right' })\n\t\t\t? 'right'\n\t\t\t: editor.isActive({ textAlign: 'justify' })\n\t\t\t? 'justify'\n\t\t\t: editor.isActive({ textAlign: 'left' })\n\t\t\t? 'left'\n\t\t\t: null,\n\t\tfontFamily: editor.getAttributes('textStyle')['fontFamily'] ?? null,\n\t\tfontSize: editor.getAttributes('textStyle')['fontSize'] ?? null,\n\t\tcolor: editor.getAttributes('textStyle')['color'] ?? null,\n\t\thighlight: editor.isActive('highlight')\n\t\t\t? editor.getAttributes('highlight')['color'] ?? 'default'\n\t\t\t: null,\n\t\tsuperscript: editor.isActive('superscript'),\n\t\tsubscript: editor.isActive('subscript'),\n\t};\n}\n\nexport function extractContent(editor: Editor): EditorSnapshot {\n\tconst html = toPortableHtml(editor.getHTML());\n\tconst normalized = normalizeHardBreaks(editor.state.doc.content);\n\tconst tempDoc = editor.schema.node('doc', null, normalized);\n\t// Paragraph boundary → blank line (\\n\\n), soft line break (hardBreak) → \\n.\n\t// Matches Gmail's plain-text output for the same content.\n\tconst text = tempDoc.textBetween(0, tempDoc.content.size, '\\n\\n', '\\n');\n\treturn {\n\t\thtml,\n\t\ttext,\n\t\tisEmpty: editor.isEmpty,\n\t\tcharacterCount: text.length,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Section position helpers (used by insertAtSectionStart/End and appendBodyContent)\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the ProseMirror position immediately after a section's opening divider\n * (i.e. the start of the section's own content).\n * For `'body'`, returns 1 (beginning of the document).\n * Returns -1 if the named section's divider is not found.\n */\nexport function findSectionStartPos(editor: Editor, sectionId: string): number {\n\tif (sectionId === 'body') return 1;\n\tlet startPos = -1;\n\teditor.state.doc.descendants((node, pos) => {\n\t\tif (startPos !== -1) return false;\n\t\tif (\n\t\t\tnode.type.name === 'sectionDivider' &&\n\t\t\tnode.attrs['sectionId'] === sectionId\n\t\t) {\n\t\t\tstartPos = pos + node.nodeSize; // position right after the divider\n\t\t}\n\t});\n\treturn startPos;\n}\n\n/**\n * Returns the ProseMirror position at the end of a named section's content:\n * - For `'body'`: position of the first section divider, or end of document.\n * - For a named section: position of the next divider, or end of document.\n */\nexport function findSectionEndPos(editor: Editor, sectionId: string): number {\n\tconst doc = editor.state.doc;\n\tconst docSize = doc.content.size;\n\n\tif (sectionId === 'body') {\n\t\tlet firstDividerPos = -1;\n\t\tdoc.descendants((node, pos) => {\n\t\t\tif (firstDividerPos !== -1) return false;\n\t\t\tif (node.type.name === 'sectionDivider') {\n\t\t\t\tfirstDividerPos = pos;\n\t\t\t}\n\t\t});\n\t\treturn firstDividerPos !== -1 ? firstDividerPos : docSize;\n\t}\n\n\tlet passedSection = false;\n\tlet nextDividerPos = -1;\n\tdoc.descendants((node, pos) => {\n\t\tif (nextDividerPos !== -1) return false;\n\t\tif (!passedSection) {\n\t\t\tif (\n\t\t\t\tnode.type.name === 'sectionDivider' &&\n\t\t\t\tnode.attrs['sectionId'] === sectionId\n\t\t\t) {\n\t\t\t\tpassedSection = true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tif (node.type.name === 'sectionDivider') {\n\t\t\tnextDividerPos = pos;\n\t\t}\n\t});\n\treturn nextDividerPos !== -1 ? nextDividerPos : docSize;\n}\n\n// ---------------------------------------------------------------------------\n// JSON body helpers (used when features.jsonMode is on — the whole document is\n// a single code block whose text is raw JSON, Postman-style)\n// ---------------------------------------------------------------------------\n\nfunction escapeHtml(raw: string): string {\n\treturn raw.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n/**\n * Wrap a raw JSON string in a `<pre><code>` code block so TipTap parses it as a\n * single code-block node (the container for JSON body mode).\n */\nexport function buildJsonCodeBlockHtml(raw: string): string {\n\treturn `<pre><code>${escapeHtml(raw ?? '')}</code></pre>`;\n}\n\n/** Returns the raw text of the first code block in the document (empty if none). */\nexport function getCodeBlockText(editor: Editor): string {\n\tlet text = '';\n\tlet found = false;\n\teditor.state.doc.descendants((node) => {\n\t\tif (found) return false;\n\t\tif (node.type.name === 'codeBlock') {\n\t\t\ttext = node.textContent;\n\t\t\tfound = true;\n\t\t\treturn false;\n\t\t}\n\t\treturn undefined;\n\t});\n\treturn text;\n}\n\n/**\n * Replace the text of the first code block with `raw`, keeping it a code block.\n * Falls back to replacing the whole document when no code block exists yet.\n */\nexport function setCodeBlockText(editor: Editor, raw: string): void {\n\teditor.commands.setContent(buildJsonCodeBlockHtml(raw), { emitUpdate: true });\n}\n\n/** 1-based line/column of a 0-based character offset within `text`. */\nfunction offsetToLineCol(\n\ttext: string,\n\toffset: number,\n): { line: number; column: number } {\n\tconst before = text.slice(0, Math.max(0, offset));\n\tconst lines = before.split('\\n');\n\treturn {\n\t\tline: lines.length,\n\t\tcolumn: (lines[lines.length - 1]?.length ?? 0) + 1,\n\t};\n}\n\n/**\n * Turn a raw `JSON.parse` error into a clean message + location. Engine messages\n * look like `\"Bad control character in string literal in JSON at position 94\n * (line 7 column 9)\"` — we strip the `\"in JSON at position …\"` tail for the\n * message and pull the line/column from it (falling back to computing them from\n * the position offset when the engine omits them).\n */\nfunction describeJsonError(\n\trawMessage: string,\n\ttext: string,\n): { message: string; line: number | null; column: number | null } {\n\tconst message =\n\t\trawMessage.replace(/\\s*in JSON at position[\\s\\S]*$/i, '').trim() ||\n\t\trawMessage;\n\n\tconst lineCol = rawMessage.match(/line (\\d+) column (\\d+)/i);\n\tif (lineCol) {\n\t\treturn {\n\t\t\tmessage,\n\t\t\tline: Number(lineCol[1]),\n\t\t\tcolumn: Number(lineCol[2]),\n\t\t};\n\t}\n\n\tconst posMatch = rawMessage.match(/position (\\d+)/i);\n\tif (posMatch) {\n\t\tconst { line, column } = offsetToLineCol(text, Number(posMatch[1]));\n\t\treturn { message, line, column };\n\t}\n\n\treturn { message, line: null, column: null };\n}\n\n/** Parse a JSON string without throwing. Empty / whitespace-only text is valid. */\nexport function parseJson(text: string): JsonParseResult {\n\tif (!text.trim()) {\n\t\treturn { valid: true, value: null, error: null, line: null, column: null };\n\t}\n\ttry {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tvalue: JSON.parse(text),\n\t\t\terror: null,\n\t\t\tline: null,\n\t\t\tcolumn: null,\n\t\t};\n\t} catch (e) {\n\t\tconst { message, line, column } = describeJsonError(\n\t\t\t(e as Error).message,\n\t\t\ttext,\n\t\t);\n\t\treturn { valid: false, value: null, error: message, line, column };\n\t}\n}\n"],"names":["normalizeHtml","html","container","span","dataType","parent","p","toPortableHtml","div","walker","textNode","blocks","insertInlineHtml","editor","normalised","dom","slice","DOMParser","from","to","tr","SECTION_DIVIDER_HTML","id","buildSectionedContent","body","sections","s","getBodyHtml","extractAllSectionsFromHtml","getSectionsHtml","dividerRegex","dividers","match","i","start","end","extractSectionContent","startPos","findSectionStartPos","endPos","findSectionEndPos","serializer","DOMSerializer","text","parseHtmlToNodes","setSectionContentInEditor","dividerHtml","fragment","replaceFrom","extractBodyContent","extractActiveFormats","$from","linkMark","m","markOnPrev","extractContent","normalized","normalizeHardBreaks","tempDoc","sectionId","node","pos","doc","docSize","firstDividerPos","passedSection","nextDividerPos","escapeHtml","raw","buildJsonCodeBlockHtml","getCodeBlockText","found","setCodeBlockText","offsetToLineCol","offset","lines","describeJsonError","rawMessage","message","lineCol","posMatch","line","column","parseJson","e"],"mappings":"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"}
|
|
@@ -34,5 +34,5 @@ interface ExtensionConfig {
|
|
|
34
34
|
renderSlashCommandItem?: (item: SlashCommandItem, isActive: boolean) => ReactNode;
|
|
35
35
|
renderSlashCommandDropdown?: (props: SlashCommandDropdownRenderProps) => ReactNode;
|
|
36
36
|
}
|
|
37
|
-
export declare function buildExtensions(config: ExtensionConfig): (import('@tiptap/core').Node<any, any> | import('@tiptap/core').Extension<any, any> | import('@tiptap/core').Node<import('@tiptap/extension-mention').MentionOptions<any, import('@tiptap/extension-mention').MentionNodeAttrs>, any> | import('@tiptap/core').
|
|
37
|
+
export declare function buildExtensions(config: ExtensionConfig): (import('@tiptap/core').Node<any, any> | import('@tiptap/core').Extension<any, any> | import('@tiptap/core').Node<import('@tiptap/extension-mention').MentionOptions<any, import('@tiptap/extension-mention').MentionNodeAttrs>, any> | import('@tiptap/core').Extension<import('@tiptap/starter-kit').StarterKitOptions, any> | import('@tiptap/core').Extension<import('@tiptap/extensions').PlaceholderOptions, any> | import('@tiptap/core').Extension<import('@tiptap/extensions').CharacterCountOptions, import('@tiptap/extensions').CharacterCountStorage> | import('@tiptap/core').Node<import('@tiptap/extension-paragraph').ParagraphOptions, any> | import('@tiptap/core').Node<import('@tiptap/extension-hard-break').HardBreakOptions, any> | import('@tiptap/core').Mark<import('@tiptap/extension-underline').UnderlineOptions, any> | import('@tiptap/core').Mark<import('@tiptap/extension-link').LinkOptions, any> | import('@tiptap/core').Mark<import('@tiptap/extension-text-style').TextStyleOptions, any> | import('@tiptap/core').Extension<import('@tiptap/extension-text-style').ColorOptions, any> | import('@tiptap/core').Mark<import('@tiptap/extension-highlight').HighlightOptions, any> | import('@tiptap/core').Extension<import('@tiptap/extension-text-align').TextAlignOptions, any> | import('@tiptap/core').Node<import('@tiptap/extension-image').ImageOptions, any>)[];
|
|
38
38
|
export {};
|
|
@@ -1,49 +1,80 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { TextStyle as
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import { FontSizeExtension as
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
1
|
+
import a from "@tiptap/extension-character-count";
|
|
2
|
+
import i from "@tiptap/extension-color";
|
|
3
|
+
import S from "@tiptap/extension-font-family";
|
|
4
|
+
import k from "@tiptap/extension-hard-break";
|
|
5
|
+
import M from "@tiptap/extension-highlight";
|
|
6
|
+
import b from "@tiptap/extension-image";
|
|
7
|
+
import C from "@tiptap/extension-link";
|
|
8
|
+
import y from "../../node_modules/@tiptap/extension-paragraph/dist/index.js";
|
|
9
|
+
import l from "@tiptap/extension-placeholder";
|
|
10
|
+
import E from "@tiptap/extension-subscript";
|
|
11
|
+
import T from "@tiptap/extension-superscript";
|
|
12
|
+
import P from "@tiptap/extension-text-align";
|
|
13
|
+
import { TextStyle as D } from "@tiptap/extension-text-style";
|
|
14
|
+
import H from "@tiptap/extension-underline";
|
|
15
|
+
import m from "@tiptap/starter-kit";
|
|
16
|
+
import { FontSizeExtension as L } from "./FontSizeExtension.js";
|
|
17
|
+
import { JsonHighlightExtension as g } from "./jsonHighlight/JsonHighlightExtension.js";
|
|
18
|
+
import { buildAgentMentionExtension as w, buildTeamMentionExtension as B } from "./mention/MentionExtension.js";
|
|
19
|
+
import { PasteExtension as I } from "./paste/PasteExtension.js";
|
|
20
|
+
import { PasteNormalizationExtension as O } from "./plainClipboard/PasteNormalizationExtension.js";
|
|
21
|
+
import { SectionDividerNode as R } from "./sectionDivider/SectionDividerNode.js";
|
|
22
|
+
import { SendShortcutExtension as d } from "./sendShortcut/SendShortcutExtension.js";
|
|
22
23
|
import { buildSlashCommandExtension as A } from "./slashCommand/SlashCommandExtension.js";
|
|
23
|
-
import { VariableDecorationExtension as
|
|
24
|
-
function
|
|
25
|
-
|
|
24
|
+
import { VariableDecorationExtension as z } from "./variable/VariableDecorationExtension.js";
|
|
25
|
+
function ie(e) {
|
|
26
|
+
if (e.features?.jsonMode)
|
|
27
|
+
return [
|
|
28
|
+
m.configure({
|
|
29
|
+
link: !1,
|
|
30
|
+
underline: !1,
|
|
31
|
+
// Keep codeBlock (the JSON container) and history; drop everything
|
|
32
|
+
// that has no meaning inside raw JSON.
|
|
33
|
+
bold: !1,
|
|
34
|
+
italic: !1,
|
|
35
|
+
strike: !1,
|
|
36
|
+
code: !1,
|
|
37
|
+
heading: !1,
|
|
38
|
+
bulletList: !1,
|
|
39
|
+
orderedList: !1,
|
|
40
|
+
blockquote: !1,
|
|
41
|
+
horizontalRule: !1
|
|
42
|
+
}),
|
|
43
|
+
l.configure({
|
|
44
|
+
placeholder: e.placeholder ?? `{
|
|
45
|
+
|
|
46
|
+
}`
|
|
47
|
+
}),
|
|
48
|
+
g,
|
|
49
|
+
d.configure({
|
|
50
|
+
onSend: e.onSend,
|
|
51
|
+
sendShortcut: e.sendShortcut,
|
|
52
|
+
extraShortcuts: e.shortcuts ?? []
|
|
53
|
+
}),
|
|
54
|
+
...e.maxCharacters ? [a.configure({ limit: e.maxCharacters })] : []
|
|
55
|
+
];
|
|
56
|
+
const o = e.features?.richPaste ?? !1, u = e.features?.richTypography ?? !1, n = e.features?.allowedMarks, t = (r) => !n || n.includes(r), p = k.extend({
|
|
26
57
|
addKeyboardShortcuts() {
|
|
27
58
|
return {
|
|
28
59
|
"Shift-Enter": () => this.editor.commands.setHardBreak()
|
|
29
60
|
};
|
|
30
61
|
}
|
|
31
|
-
}),
|
|
62
|
+
}), s = (r) => r === "0" || r === "0px" || r === "0pt" || r === "0em", h = y.extend({
|
|
32
63
|
addAttributes() {
|
|
33
64
|
return {
|
|
34
65
|
...this.parent?.() ?? {},
|
|
35
66
|
margin: {
|
|
36
67
|
default: null,
|
|
37
|
-
parseHTML: (r) =>
|
|
68
|
+
parseHTML: (r) => s(r.style.marginTop) && s(r.style.marginBottom) ? "0" : null,
|
|
38
69
|
renderHTML: (r) => r.margin === "0" ? { style: "margin: 0px" } : {}
|
|
39
70
|
}
|
|
40
71
|
};
|
|
41
72
|
}
|
|
42
|
-
}),
|
|
73
|
+
}), f = [
|
|
43
74
|
// Exclude Link and Underline from StarterKit — TipTap v3 bundles both.
|
|
44
75
|
// Without this, two copies of each are registered and the StarterKit copy's
|
|
45
76
|
// click handler calls window.open even when openOnClick: false is set.
|
|
46
|
-
|
|
77
|
+
m.configure({
|
|
47
78
|
link: !1,
|
|
48
79
|
underline: !1,
|
|
49
80
|
hardBreak: !1,
|
|
@@ -53,12 +84,12 @@ function ae(e) {
|
|
|
53
84
|
strike: t("strike") ? {} : !1,
|
|
54
85
|
code: t("code") ? {} : !1
|
|
55
86
|
}),
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
...t("underline") ? [
|
|
87
|
+
h,
|
|
88
|
+
p,
|
|
89
|
+
...t("underline") ? [H] : [],
|
|
59
90
|
// Extend Link to strip addPasteRules() and addInputRules() so pasting a
|
|
60
91
|
// URL never creates a hyperlink regardless of autolink/linkOnPaste values.
|
|
61
|
-
|
|
92
|
+
C.extend({
|
|
62
93
|
addPasteRules: () => [],
|
|
63
94
|
addInputRules: () => []
|
|
64
95
|
}).configure({
|
|
@@ -70,36 +101,36 @@ function ae(e) {
|
|
|
70
101
|
class: "bik-link"
|
|
71
102
|
}
|
|
72
103
|
}),
|
|
73
|
-
|
|
74
|
-
|
|
104
|
+
D,
|
|
105
|
+
l.configure({
|
|
75
106
|
placeholder: e.placeholder ?? "Type a message..."
|
|
76
107
|
}),
|
|
77
108
|
// Consumer paste callback runs first. If it returns true the event is
|
|
78
109
|
// consumed and neither PlainClipboardExtension nor the editor's default
|
|
79
110
|
// paste handling will run for that event.
|
|
80
|
-
...e.onPaste ? [
|
|
81
|
-
|
|
111
|
+
...e.onPaste ? [I.configure({ onPaste: e.onPaste })] : [],
|
|
112
|
+
O.configure({
|
|
82
113
|
preserveMarks: o,
|
|
83
114
|
// Only email + signature (richPaste) keep the rich HTML paste pipeline.
|
|
84
115
|
// Every other channel (WhatsApp, IG, FB, comments) forces plain-text
|
|
85
116
|
// paste for BOTH Cmd/Ctrl+V and Cmd/Ctrl+Shift+V — no formatting.
|
|
86
117
|
plainOnly: !o
|
|
87
118
|
}),
|
|
88
|
-
|
|
119
|
+
d.configure({
|
|
89
120
|
onSend: e.onSend,
|
|
90
121
|
sendShortcut: e.sendShortcut,
|
|
91
122
|
extraShortcuts: e.shortcuts ?? []
|
|
92
123
|
}),
|
|
93
|
-
|
|
94
|
-
...e.maxCharacters ? [
|
|
124
|
+
z,
|
|
125
|
+
...e.maxCharacters ? [a.configure({ limit: e.maxCharacters })] : [],
|
|
95
126
|
// Always register SectionDividerNode so the custom <div data-section-divider>
|
|
96
127
|
// node is part of the schema in every editor instance. This means imperative
|
|
97
128
|
// callers (setBodyAndSections / setSectionContent) work even when no
|
|
98
129
|
// `sections` prop was provided at mount time.
|
|
99
|
-
|
|
100
|
-
],
|
|
130
|
+
R
|
|
131
|
+
], c = [
|
|
101
132
|
...e.mentions?.agents ? [
|
|
102
|
-
|
|
133
|
+
w(
|
|
103
134
|
e.mentions.agents,
|
|
104
135
|
e.onMentionSelected,
|
|
105
136
|
e.renderMentionItem,
|
|
@@ -108,7 +139,7 @@ function ae(e) {
|
|
|
108
139
|
)
|
|
109
140
|
] : [],
|
|
110
141
|
...e.mentions?.teams ? [
|
|
111
|
-
|
|
142
|
+
B(
|
|
112
143
|
e.mentions.teams,
|
|
113
144
|
e.onMentionSelected,
|
|
114
145
|
e.renderMentionItem,
|
|
@@ -124,19 +155,19 @@ function ae(e) {
|
|
|
124
155
|
e.renderSlashCommandDropdown
|
|
125
156
|
)
|
|
126
157
|
] : []
|
|
127
|
-
],
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
] : [
|
|
137
|
-
return [...
|
|
158
|
+
], x = u ? [
|
|
159
|
+
i,
|
|
160
|
+
M.configure({ multicolor: !0 }),
|
|
161
|
+
S,
|
|
162
|
+
L,
|
|
163
|
+
P.configure({ types: ["heading", "paragraph"] }),
|
|
164
|
+
E,
|
|
165
|
+
T,
|
|
166
|
+
b
|
|
167
|
+
] : [i];
|
|
168
|
+
return [...f, ...c, ...x];
|
|
138
169
|
}
|
|
139
170
|
export {
|
|
140
|
-
|
|
171
|
+
ie as buildExtensions
|
|
141
172
|
};
|
|
142
173
|
//# sourceMappingURL=buildExtensions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildExtensions.js","sources":["../../../../src/editor/extensions/buildExtensions.ts"],"sourcesContent":["import CharacterCount from '@tiptap/extension-character-count';\nimport Color from '@tiptap/extension-color';\nimport FontFamily from '@tiptap/extension-font-family';\nimport HardBreak from '@tiptap/extension-hard-break';\nimport Highlight from '@tiptap/extension-highlight';\nimport Image from '@tiptap/extension-image';\nimport Link from '@tiptap/extension-link';\nimport Paragraph from '@tiptap/extension-paragraph';\nimport Placeholder from '@tiptap/extension-placeholder';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport TextAlign from '@tiptap/extension-text-align';\nimport { TextStyle } from '@tiptap/extension-text-style';\nimport Underline from '@tiptap/extension-underline';\nimport StarterKit from '@tiptap/starter-kit';\nimport type { ReactNode } from 'react';\nimport type {\n\tEditorFeatures,\n\tEditorSnapshot,\n\tKeyboardShortcut,\n\tMentionDropdownRenderProps,\n\tMentionItem,\n\tPasteData,\n\tSlashCommandDropdownRenderProps,\n\tSlashCommandItem,\n} from '../BikEditor.types';\nimport { FontSizeExtension } from './FontSizeExtension';\nimport {\n\tbuildAgentMentionExtension,\n\tbuildTeamMentionExtension,\n} from './mention/MentionExtension';\nimport { PasteExtension } from './paste/PasteExtension';\nimport { PasteNormalizationExtension } from './plainClipboard/PasteNormalizationExtension';\nimport { SectionDividerNode } from './sectionDivider/SectionDividerNode';\nimport { SendShortcutExtension } from './sendShortcut/SendShortcutExtension';\nimport { buildSlashCommandExtension } from './slashCommand/SlashCommandExtension';\nimport { VariableDecorationExtension } from './variable/VariableDecorationExtension';\n\ninterface ExtensionConfig {\n\tfeatures?: EditorFeatures;\n\tplaceholder?: string;\n\tmaxCharacters?: number;\n\thasSections?: boolean;\n\tmentions?: {\n\t\tagents?: { current: MentionItem[] };\n\t\tteams?: { current: MentionItem[] };\n\t};\n\tslashCommands?: { current: SlashCommandItem[] };\n\tonPaste?: (data: PasteData) => boolean | void;\n\tonSend?: (content: EditorSnapshot) => void;\n\tsendShortcut?:\n\t\t| { key: string; modifiers?: Array<'mod' | 'ctrl' | 'shift' | 'alt'> }\n\t\t| Array<{\n\t\t\t\tkey: string;\n\t\t\t\tmodifiers?: Array<'mod' | 'ctrl' | 'shift' | 'alt'>;\n\t\t }>;\n\tshortcuts?: KeyboardShortcut[];\n\tonMentionSelected?: (item: MentionItem, char: '@' | '#') => void;\n\tonSlashCommandSelected?: (command: SlashCommandItem) => void;\n\trenderMentionItem?: (item: MentionItem, isActive: boolean) => ReactNode;\n\trenderMentionDropdown?: (props: MentionDropdownRenderProps) => ReactNode;\n\tremoveMentionTriggerOnDismiss?: boolean;\n\trenderSlashCommandItem?: (\n\t\titem: SlashCommandItem,\n\t\tisActive: boolean,\n\t) => ReactNode;\n\trenderSlashCommandDropdown?: (\n\t\tprops: SlashCommandDropdownRenderProps,\n\t) => ReactNode;\n}\n\nexport function buildExtensions(config: ExtensionConfig) {\n\tconst hasRichPaste = config.features?.richPaste ?? false;\n\tconst hasRichTypography = config.features?.richTypography ?? false;\n\n\t// When allowedMarks is specified only those marks are registered in the schema.\n\t// This disables rendering, keyboard shortcuts AND paste-preservation for the\n\t// excluded marks — all three come for free when the extension is absent.\n\tconst allowedMarks = config.features?.allowedMarks;\n\tconst isMark = (m: 'bold' | 'italic' | 'strike' | 'underline' | 'code') =>\n\t\t!allowedMarks || allowedMarks.includes(m);\n\n\t// Soft line break. Shift-Enter inserts a <br> inside the current block\n\t// (matches Gmail); plain Enter still splits the block into a new paragraph\n\t// via StarterKit's default. HardBreak is registered standalone — like Link\n\t// and Underline above — instead of through StarterKit, so we can bind ONLY\n\t// Shift-Enter here and leave Mod-Enter free for the send shortcut.\n\tconst softBreak = HardBreak.extend({\n\t\taddKeyboardShortcuts() {\n\t\t\treturn {\n\t\t\t\t'Shift-Enter': () => this.editor.commands.setHardBreak(),\n\t\t\t};\n\t\t},\n\t});\n\n\t// Paragraph that preserves a zero block-margin from the source (Gmail-style).\n\t// Most sources (ChatGPT, typed text) use plain <p> → no margin attr → the\n\t// editor/viewer default 1em margin provides the gap. Google-Docs-style\n\t// content uses margin:0 paragraphs and a separate blank-line paragraph for\n\t// spacing; we keep that margin:0 so the blank line — not stacked margins —\n\t// provides a single-line gap, exactly like Gmail. The paste pipeline also\n\t// sets margin:'0' on every paragraph when a paste contains blank-line\n\t// paragraphs (see applyParagraphSpacing), so spacing comes from EITHER the\n\t// margin OR the blank line, never both (no double gap).\n\tconst isZeroLen = (v: string) =>\n\t\tv === '0' || v === '0px' || v === '0pt' || v === '0em';\n\tconst ParagraphWithMargin = Paragraph.extend({\n\t\taddAttributes() {\n\t\t\treturn {\n\t\t\t\t...(this.parent?.() ?? {}),\n\t\t\t\tmargin: {\n\t\t\t\t\tdefault: null,\n\t\t\t\t\tparseHTML: (el: HTMLElement) =>\n\t\t\t\t\t\tisZeroLen(el.style.marginTop) && isZeroLen(el.style.marginBottom)\n\t\t\t\t\t\t\t? '0'\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\trenderHTML: (attrs: { margin?: string | null }) =>\n\t\t\t\t\t\tattrs.margin === '0' ? { style: 'margin: 0px' } : {},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t});\n\n\tconst base = [\n\t\t// Exclude Link and Underline from StarterKit — TipTap v3 bundles both.\n\t\t// Without this, two copies of each are registered and the StarterKit copy's\n\t\t// click handler calls window.open even when openOnClick: false is set.\n\t\tStarterKit.configure({\n\t\t\tlink: false,\n\t\t\tunderline: false,\n\t\t\thardBreak: false,\n\t\t\tparagraph: false,\n\t\t\tbold: isMark('bold') ? {} : false,\n\t\t\titalic: isMark('italic') ? {} : false,\n\t\t\tstrike: isMark('strike') ? {} : false,\n\t\t\tcode: isMark('code') ? {} : false,\n\t\t}),\n\t\tParagraphWithMargin,\n\t\tsoftBreak,\n\t\t...(isMark('underline') ? [Underline] : []),\n\t\t// Extend Link to strip addPasteRules() and addInputRules() so pasting a\n\t\t// URL never creates a hyperlink regardless of autolink/linkOnPaste values.\n\t\tLink.extend({\n\t\t\taddPasteRules: () => [],\n\t\t\taddInputRules: () => [],\n\t\t}).configure({\n\t\t\topenOnClick: false,\n\t\t\tautolink: false,\n\t\t\tlinkOnPaste: false,\n\t\t\tHTMLAttributes: {\n\t\t\t\trel: 'noopener noreferrer',\n\t\t\t\tclass: 'bik-link',\n\t\t\t},\n\t\t}),\n\t\tTextStyle,\n\t\tPlaceholder.configure({\n\t\t\tplaceholder: config.placeholder ?? 'Type a message...',\n\t\t}),\n\t\t// Consumer paste callback runs first. If it returns true the event is\n\t\t// consumed and neither PlainClipboardExtension nor the editor's default\n\t\t// paste handling will run for that event.\n\t\t...(config.onPaste\n\t\t\t? [PasteExtension.configure({ onPaste: config.onPaste })]\n\t\t\t: []),\n\t\tPasteNormalizationExtension.configure({\n\t\t\tpreserveMarks: hasRichPaste,\n\t\t\t// Only email + signature (richPaste) keep the rich HTML paste pipeline.\n\t\t\t// Every other channel (WhatsApp, IG, FB, comments) forces plain-text\n\t\t\t// paste for BOTH Cmd/Ctrl+V and Cmd/Ctrl+Shift+V — no formatting.\n\t\t\tplainOnly: !hasRichPaste,\n\t\t}),\n\t\tSendShortcutExtension.configure({\n\t\t\tonSend: config.onSend,\n\t\t\tsendShortcut: config.sendShortcut,\n\t\t\textraShortcuts: config.shortcuts ?? [],\n\t\t}),\n\t\tVariableDecorationExtension,\n\t\t...(config.maxCharacters\n\t\t\t? [CharacterCount.configure({ limit: config.maxCharacters })]\n\t\t\t: []),\n\t\t// Always register SectionDividerNode so the custom <div data-section-divider>\n\t\t// node is part of the schema in every editor instance. This means imperative\n\t\t// callers (setBodyAndSections / setSectionContent) work even when no\n\t\t// `sections` prop was provided at mount time.\n\t\tSectionDividerNode,\n\t];\n\n\tconst mentionExtensions = [\n\t\t...(config.mentions?.agents\n\t\t\t? [\n\t\t\t\t\tbuildAgentMentionExtension(\n\t\t\t\t\t\tconfig.mentions.agents,\n\t\t\t\t\t\tconfig.onMentionSelected,\n\t\t\t\t\t\tconfig.renderMentionItem,\n\t\t\t\t\t\tconfig.renderMentionDropdown,\n\t\t\t\t\t\tconfig.removeMentionTriggerOnDismiss,\n\t\t\t\t\t),\n\t\t\t ]\n\t\t\t: []),\n\t\t...(config.mentions?.teams\n\t\t\t? [\n\t\t\t\t\tbuildTeamMentionExtension(\n\t\t\t\t\t\tconfig.mentions.teams,\n\t\t\t\t\t\tconfig.onMentionSelected,\n\t\t\t\t\t\tconfig.renderMentionItem,\n\t\t\t\t\t\tconfig.renderMentionDropdown,\n\t\t\t\t\t\tconfig.removeMentionTriggerOnDismiss,\n\t\t\t\t\t),\n\t\t\t ]\n\t\t\t: []),\n\t\t...(config.slashCommands\n\t\t\t? [\n\t\t\t\t\tbuildSlashCommandExtension(\n\t\t\t\t\t\tconfig.slashCommands,\n\t\t\t\t\t\tconfig.onSlashCommandSelected,\n\t\t\t\t\t\tconfig.renderSlashCommandItem,\n\t\t\t\t\t\tconfig.renderSlashCommandDropdown,\n\t\t\t\t\t),\n\t\t\t ]\n\t\t\t: []),\n\t];\n\n\tconst richExtensions = hasRichTypography\n\t\t? [\n\t\t\t\tColor,\n\t\t\t\tHighlight.configure({ multicolor: true }),\n\t\t\t\tFontFamily,\n\t\t\t\tFontSizeExtension,\n\t\t\t\tTextAlign.configure({ types: ['heading', 'paragraph'] }),\n\t\t\t\tSubscript,\n\t\t\t\tSuperscript,\n\t\t\t\tImage,\n\t\t ]\n\t\t: [Color];\n\n\treturn [...base, ...mentionExtensions, ...richExtensions];\n}\n"],"names":["buildExtensions","config","hasRichPaste","hasRichTypography","allowedMarks","isMark","m","softBreak","HardBreak","isZeroLen","v","ParagraphWithMargin","Paragraph","el","attrs","base","StarterKit","Underline","Link","TextStyle","Placeholder","PasteExtension","PasteNormalizationExtension","SendShortcutExtension","VariableDecorationExtension","CharacterCount","SectionDividerNode","mentionExtensions","buildAgentMentionExtension","buildTeamMentionExtension","buildSlashCommandExtension","richExtensions","Color","Highlight","FontFamily","FontSizeExtension","TextAlign","Subscript","Superscript","Image"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buildExtensions.js","sources":["../../../../src/editor/extensions/buildExtensions.ts"],"sourcesContent":["import CharacterCount from '@tiptap/extension-character-count';\nimport Color from '@tiptap/extension-color';\nimport FontFamily from '@tiptap/extension-font-family';\nimport HardBreak from '@tiptap/extension-hard-break';\nimport Highlight from '@tiptap/extension-highlight';\nimport Image from '@tiptap/extension-image';\nimport Link from '@tiptap/extension-link';\nimport Paragraph from '@tiptap/extension-paragraph';\nimport Placeholder from '@tiptap/extension-placeholder';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport TextAlign from '@tiptap/extension-text-align';\nimport { TextStyle } from '@tiptap/extension-text-style';\nimport Underline from '@tiptap/extension-underline';\nimport StarterKit from '@tiptap/starter-kit';\nimport type { ReactNode } from 'react';\nimport type {\n\tEditorFeatures,\n\tEditorSnapshot,\n\tKeyboardShortcut,\n\tMentionDropdownRenderProps,\n\tMentionItem,\n\tPasteData,\n\tSlashCommandDropdownRenderProps,\n\tSlashCommandItem,\n} from '../BikEditor.types';\nimport { FontSizeExtension } from './FontSizeExtension';\nimport { JsonHighlightExtension } from './jsonHighlight/JsonHighlightExtension';\nimport {\n\tbuildAgentMentionExtension,\n\tbuildTeamMentionExtension,\n} from './mention/MentionExtension';\nimport { PasteExtension } from './paste/PasteExtension';\nimport { PasteNormalizationExtension } from './plainClipboard/PasteNormalizationExtension';\nimport { SectionDividerNode } from './sectionDivider/SectionDividerNode';\nimport { SendShortcutExtension } from './sendShortcut/SendShortcutExtension';\nimport { buildSlashCommandExtension } from './slashCommand/SlashCommandExtension';\nimport { VariableDecorationExtension } from './variable/VariableDecorationExtension';\n\ninterface ExtensionConfig {\n\tfeatures?: EditorFeatures;\n\tplaceholder?: string;\n\tmaxCharacters?: number;\n\thasSections?: boolean;\n\tmentions?: {\n\t\tagents?: { current: MentionItem[] };\n\t\tteams?: { current: MentionItem[] };\n\t};\n\tslashCommands?: { current: SlashCommandItem[] };\n\tonPaste?: (data: PasteData) => boolean | void;\n\tonSend?: (content: EditorSnapshot) => void;\n\tsendShortcut?:\n\t\t| { key: string; modifiers?: Array<'mod' | 'ctrl' | 'shift' | 'alt'> }\n\t\t| Array<{\n\t\t\t\tkey: string;\n\t\t\t\tmodifiers?: Array<'mod' | 'ctrl' | 'shift' | 'alt'>;\n\t\t }>;\n\tshortcuts?: KeyboardShortcut[];\n\tonMentionSelected?: (item: MentionItem, char: '@' | '#') => void;\n\tonSlashCommandSelected?: (command: SlashCommandItem) => void;\n\trenderMentionItem?: (item: MentionItem, isActive: boolean) => ReactNode;\n\trenderMentionDropdown?: (props: MentionDropdownRenderProps) => ReactNode;\n\tremoveMentionTriggerOnDismiss?: boolean;\n\trenderSlashCommandItem?: (\n\t\titem: SlashCommandItem,\n\t\tisActive: boolean,\n\t) => ReactNode;\n\trenderSlashCommandDropdown?: (\n\t\tprops: SlashCommandDropdownRenderProps,\n\t) => ReactNode;\n}\n\nexport function buildExtensions(config: ExtensionConfig) {\n\t// JSON body mode is a dedicated code editor — it deliberately skips the rich\n\t// text stack (mentions, typography, rich paste, sections) and turns the whole\n\t// document into one syntax-highlighted JSON code block, Postman-style.\n\tif (config.features?.jsonMode) {\n\t\treturn [\n\t\t\tStarterKit.configure({\n\t\t\t\tlink: false,\n\t\t\t\tunderline: false,\n\t\t\t\t// Keep codeBlock (the JSON container) and history; drop everything\n\t\t\t\t// that has no meaning inside raw JSON.\n\t\t\t\tbold: false,\n\t\t\t\titalic: false,\n\t\t\t\tstrike: false,\n\t\t\t\tcode: false,\n\t\t\t\theading: false,\n\t\t\t\tbulletList: false,\n\t\t\t\torderedList: false,\n\t\t\t\tblockquote: false,\n\t\t\t\thorizontalRule: false,\n\t\t\t}),\n\t\t\tPlaceholder.configure({\n\t\t\t\tplaceholder: config.placeholder ?? '{\\n \\n}',\n\t\t\t}),\n\t\t\tJsonHighlightExtension,\n\t\t\tSendShortcutExtension.configure({\n\t\t\t\tonSend: config.onSend,\n\t\t\t\tsendShortcut: config.sendShortcut,\n\t\t\t\textraShortcuts: config.shortcuts ?? [],\n\t\t\t}),\n\t\t\t...(config.maxCharacters\n\t\t\t\t? [CharacterCount.configure({ limit: config.maxCharacters })]\n\t\t\t\t: []),\n\t\t];\n\t}\n\n\tconst hasRichPaste = config.features?.richPaste ?? false;\n\tconst hasRichTypography = config.features?.richTypography ?? false;\n\n\t// When allowedMarks is specified only those marks are registered in the schema.\n\t// This disables rendering, keyboard shortcuts AND paste-preservation for the\n\t// excluded marks — all three come for free when the extension is absent.\n\tconst allowedMarks = config.features?.allowedMarks;\n\tconst isMark = (m: 'bold' | 'italic' | 'strike' | 'underline' | 'code') =>\n\t\t!allowedMarks || allowedMarks.includes(m);\n\n\t// Soft line break. Shift-Enter inserts a <br> inside the current block\n\t// (matches Gmail); plain Enter still splits the block into a new paragraph\n\t// via StarterKit's default. HardBreak is registered standalone — like Link\n\t// and Underline above — instead of through StarterKit, so we can bind ONLY\n\t// Shift-Enter here and leave Mod-Enter free for the send shortcut.\n\tconst softBreak = HardBreak.extend({\n\t\taddKeyboardShortcuts() {\n\t\t\treturn {\n\t\t\t\t'Shift-Enter': () => this.editor.commands.setHardBreak(),\n\t\t\t};\n\t\t},\n\t});\n\n\t// Paragraph that preserves a zero block-margin from the source (Gmail-style).\n\t// Most sources (ChatGPT, typed text) use plain <p> → no margin attr → the\n\t// editor/viewer default 1em margin provides the gap. Google-Docs-style\n\t// content uses margin:0 paragraphs and a separate blank-line paragraph for\n\t// spacing; we keep that margin:0 so the blank line — not stacked margins —\n\t// provides a single-line gap, exactly like Gmail. The paste pipeline also\n\t// sets margin:'0' on every paragraph when a paste contains blank-line\n\t// paragraphs (see applyParagraphSpacing), so spacing comes from EITHER the\n\t// margin OR the blank line, never both (no double gap).\n\tconst isZeroLen = (v: string) =>\n\t\tv === '0' || v === '0px' || v === '0pt' || v === '0em';\n\tconst ParagraphWithMargin = Paragraph.extend({\n\t\taddAttributes() {\n\t\t\treturn {\n\t\t\t\t...(this.parent?.() ?? {}),\n\t\t\t\tmargin: {\n\t\t\t\t\tdefault: null,\n\t\t\t\t\tparseHTML: (el: HTMLElement) =>\n\t\t\t\t\t\tisZeroLen(el.style.marginTop) && isZeroLen(el.style.marginBottom)\n\t\t\t\t\t\t\t? '0'\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\trenderHTML: (attrs: { margin?: string | null }) =>\n\t\t\t\t\t\tattrs.margin === '0' ? { style: 'margin: 0px' } : {},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t});\n\n\tconst base = [\n\t\t// Exclude Link and Underline from StarterKit — TipTap v3 bundles both.\n\t\t// Without this, two copies of each are registered and the StarterKit copy's\n\t\t// click handler calls window.open even when openOnClick: false is set.\n\t\tStarterKit.configure({\n\t\t\tlink: false,\n\t\t\tunderline: false,\n\t\t\thardBreak: false,\n\t\t\tparagraph: false,\n\t\t\tbold: isMark('bold') ? {} : false,\n\t\t\titalic: isMark('italic') ? {} : false,\n\t\t\tstrike: isMark('strike') ? {} : false,\n\t\t\tcode: isMark('code') ? {} : false,\n\t\t}),\n\t\tParagraphWithMargin,\n\t\tsoftBreak,\n\t\t...(isMark('underline') ? [Underline] : []),\n\t\t// Extend Link to strip addPasteRules() and addInputRules() so pasting a\n\t\t// URL never creates a hyperlink regardless of autolink/linkOnPaste values.\n\t\tLink.extend({\n\t\t\taddPasteRules: () => [],\n\t\t\taddInputRules: () => [],\n\t\t}).configure({\n\t\t\topenOnClick: false,\n\t\t\tautolink: false,\n\t\t\tlinkOnPaste: false,\n\t\t\tHTMLAttributes: {\n\t\t\t\trel: 'noopener noreferrer',\n\t\t\t\tclass: 'bik-link',\n\t\t\t},\n\t\t}),\n\t\tTextStyle,\n\t\tPlaceholder.configure({\n\t\t\tplaceholder: config.placeholder ?? 'Type a message...',\n\t\t}),\n\t\t// Consumer paste callback runs first. If it returns true the event is\n\t\t// consumed and neither PlainClipboardExtension nor the editor's default\n\t\t// paste handling will run for that event.\n\t\t...(config.onPaste\n\t\t\t? [PasteExtension.configure({ onPaste: config.onPaste })]\n\t\t\t: []),\n\t\tPasteNormalizationExtension.configure({\n\t\t\tpreserveMarks: hasRichPaste,\n\t\t\t// Only email + signature (richPaste) keep the rich HTML paste pipeline.\n\t\t\t// Every other channel (WhatsApp, IG, FB, comments) forces plain-text\n\t\t\t// paste for BOTH Cmd/Ctrl+V and Cmd/Ctrl+Shift+V — no formatting.\n\t\t\tplainOnly: !hasRichPaste,\n\t\t}),\n\t\tSendShortcutExtension.configure({\n\t\t\tonSend: config.onSend,\n\t\t\tsendShortcut: config.sendShortcut,\n\t\t\textraShortcuts: config.shortcuts ?? [],\n\t\t}),\n\t\tVariableDecorationExtension,\n\t\t...(config.maxCharacters\n\t\t\t? [CharacterCount.configure({ limit: config.maxCharacters })]\n\t\t\t: []),\n\t\t// Always register SectionDividerNode so the custom <div data-section-divider>\n\t\t// node is part of the schema in every editor instance. This means imperative\n\t\t// callers (setBodyAndSections / setSectionContent) work even when no\n\t\t// `sections` prop was provided at mount time.\n\t\tSectionDividerNode,\n\t];\n\n\tconst mentionExtensions = [\n\t\t...(config.mentions?.agents\n\t\t\t? [\n\t\t\t\t\tbuildAgentMentionExtension(\n\t\t\t\t\t\tconfig.mentions.agents,\n\t\t\t\t\t\tconfig.onMentionSelected,\n\t\t\t\t\t\tconfig.renderMentionItem,\n\t\t\t\t\t\tconfig.renderMentionDropdown,\n\t\t\t\t\t\tconfig.removeMentionTriggerOnDismiss,\n\t\t\t\t\t),\n\t\t\t ]\n\t\t\t: []),\n\t\t...(config.mentions?.teams\n\t\t\t? [\n\t\t\t\t\tbuildTeamMentionExtension(\n\t\t\t\t\t\tconfig.mentions.teams,\n\t\t\t\t\t\tconfig.onMentionSelected,\n\t\t\t\t\t\tconfig.renderMentionItem,\n\t\t\t\t\t\tconfig.renderMentionDropdown,\n\t\t\t\t\t\tconfig.removeMentionTriggerOnDismiss,\n\t\t\t\t\t),\n\t\t\t ]\n\t\t\t: []),\n\t\t...(config.slashCommands\n\t\t\t? [\n\t\t\t\t\tbuildSlashCommandExtension(\n\t\t\t\t\t\tconfig.slashCommands,\n\t\t\t\t\t\tconfig.onSlashCommandSelected,\n\t\t\t\t\t\tconfig.renderSlashCommandItem,\n\t\t\t\t\t\tconfig.renderSlashCommandDropdown,\n\t\t\t\t\t),\n\t\t\t ]\n\t\t\t: []),\n\t];\n\n\tconst richExtensions = hasRichTypography\n\t\t? [\n\t\t\t\tColor,\n\t\t\t\tHighlight.configure({ multicolor: true }),\n\t\t\t\tFontFamily,\n\t\t\t\tFontSizeExtension,\n\t\t\t\tTextAlign.configure({ types: ['heading', 'paragraph'] }),\n\t\t\t\tSubscript,\n\t\t\t\tSuperscript,\n\t\t\t\tImage,\n\t\t ]\n\t\t: [Color];\n\n\treturn [...base, ...mentionExtensions, ...richExtensions];\n}\n"],"names":["buildExtensions","config","StarterKit","Placeholder","JsonHighlightExtension","SendShortcutExtension","CharacterCount","hasRichPaste","hasRichTypography","allowedMarks","isMark","m","softBreak","HardBreak","isZeroLen","v","ParagraphWithMargin","Paragraph","el","attrs","base","Underline","Link","TextStyle","PasteExtension","PasteNormalizationExtension","VariableDecorationExtension","SectionDividerNode","mentionExtensions","buildAgentMentionExtension","buildTeamMentionExtension","buildSlashCommandExtension","richExtensions","Color","Highlight","FontFamily","FontSizeExtension","TextAlign","Subscript","Superscript","Image"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwEO,SAASA,GAAgBC,GAAyB;AAIxD,MAAIA,EAAO,UAAU;AACpB,WAAO;AAAA,MACNC,EAAW,UAAU;AAAA,QACpB,MAAM;AAAA,QACN,WAAW;AAAA;AAAA;AAAA,QAGX,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAAA,CAChB;AAAA,MACDC,EAAY,UAAU;AAAA,QACrB,aAAaF,EAAO,eAAe;AAAA;AAAA;AAAA,MAAA,CACnC;AAAA,MACDG;AAAA,MACAC,EAAsB,UAAU;AAAA,QAC/B,QAAQJ,EAAO;AAAA,QACf,cAAcA,EAAO;AAAA,QACrB,gBAAgBA,EAAO,aAAa,CAAA;AAAA,MAAC,CACrC;AAAA,MACD,GAAIA,EAAO,gBACR,CAACK,EAAe,UAAU,EAAE,OAAOL,EAAO,cAAA,CAAe,CAAC,IAC1D,CAAA;AAAA,IAAC;AAIN,QAAMM,IAAeN,EAAO,UAAU,aAAa,IAC7CO,IAAoBP,EAAO,UAAU,kBAAkB,IAKvDQ,IAAeR,EAAO,UAAU,cAChCS,IAAS,CAACC,MACf,CAACF,KAAgBA,EAAa,SAASE,CAAC,GAOnCC,IAAYC,EAAU,OAAO;AAAA,IAClC,uBAAuB;AACtB,aAAO;AAAA,QACN,eAAe,MAAM,KAAK,OAAO,SAAS,aAAA;AAAA,MAAa;AAAA,IAEzD;AAAA,EAAA,CACA,GAWKC,IAAY,CAACC,MAClBA,MAAM,OAAOA,MAAM,SAASA,MAAM,SAASA,MAAM,OAC5CC,IAAsBC,EAAU,OAAO;AAAA,IAC5C,gBAAgB;AACf,aAAO;AAAA,QACN,GAAI,KAAK,SAAA,KAAc,CAAA;AAAA,QACvB,QAAQ;AAAA,UACP,SAAS;AAAA,UACT,WAAW,CAACC,MACXJ,EAAUI,EAAG,MAAM,SAAS,KAAKJ,EAAUI,EAAG,MAAM,YAAY,IAC7D,MACA;AAAA,UACJ,YAAY,CAACC,MACZA,EAAM,WAAW,MAAM,EAAE,OAAO,kBAAkB,CAAA;AAAA,QAAC;AAAA,MACrD;AAAA,IAEF;AAAA,EAAA,CACA,GAEKC,IAAO;AAAA;AAAA;AAAA;AAAA,IAIZlB,EAAW,UAAU;AAAA,MACpB,MAAM;AAAA,MACN,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,MACX,MAAMQ,EAAO,MAAM,IAAI,CAAA,IAAK;AAAA,MAC5B,QAAQA,EAAO,QAAQ,IAAI,CAAA,IAAK;AAAA,MAChC,QAAQA,EAAO,QAAQ,IAAI,CAAA,IAAK;AAAA,MAChC,MAAMA,EAAO,MAAM,IAAI,KAAK;AAAA,IAAA,CAC5B;AAAA,IACDM;AAAA,IACAJ;AAAA,IACA,GAAIF,EAAO,WAAW,IAAI,CAACW,CAAS,IAAI,CAAA;AAAA;AAAA;AAAA,IAGxCC,EAAK,OAAO;AAAA,MACX,eAAe,MAAM,CAAA;AAAA,MACrB,eAAe,MAAM,CAAA;AAAA,IAAC,CACtB,EAAE,UAAU;AAAA,MACZ,aAAa;AAAA,MACb,UAAU;AAAA,MACV,aAAa;AAAA,MACb,gBAAgB;AAAA,QACf,KAAK;AAAA,QACL,OAAO;AAAA,MAAA;AAAA,IACR,CACA;AAAA,IACDC;AAAA,IACApB,EAAY,UAAU;AAAA,MACrB,aAAaF,EAAO,eAAe;AAAA,IAAA,CACnC;AAAA;AAAA;AAAA;AAAA,IAID,GAAIA,EAAO,UACR,CAACuB,EAAe,UAAU,EAAE,SAASvB,EAAO,SAAS,CAAC,IACtD,CAAA;AAAA,IACHwB,EAA4B,UAAU;AAAA,MACrC,eAAelB;AAAA;AAAA;AAAA;AAAA,MAIf,WAAW,CAACA;AAAA,IAAA,CACZ;AAAA,IACDF,EAAsB,UAAU;AAAA,MAC/B,QAAQJ,EAAO;AAAA,MACf,cAAcA,EAAO;AAAA,MACrB,gBAAgBA,EAAO,aAAa,CAAA;AAAA,IAAC,CACrC;AAAA,IACDyB;AAAA,IACA,GAAIzB,EAAO,gBACR,CAACK,EAAe,UAAU,EAAE,OAAOL,EAAO,eAAe,CAAC,IAC1D,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKH0B;AAAA,EAAA,GAGKC,IAAoB;AAAA,IACzB,GAAI3B,EAAO,UAAU,SAClB;AAAA,MACA4B;AAAA,QACC5B,EAAO,SAAS;AAAA,QAChBA,EAAO;AAAA,QACPA,EAAO;AAAA,QACPA,EAAO;AAAA,QACPA,EAAO;AAAA,MAAA;AAAA,IACR,IAEA,CAAA;AAAA,IACH,GAAIA,EAAO,UAAU,QAClB;AAAA,MACA6B;AAAA,QACC7B,EAAO,SAAS;AAAA,QAChBA,EAAO;AAAA,QACPA,EAAO;AAAA,QACPA,EAAO;AAAA,QACPA,EAAO;AAAA,MAAA;AAAA,IACR,IAEA,CAAA;AAAA,IACH,GAAIA,EAAO,gBACR;AAAA,MACA8B;AAAA,QACC9B,EAAO;AAAA,QACPA,EAAO;AAAA,QACPA,EAAO;AAAA,QACPA,EAAO;AAAA,MAAA;AAAA,IACR,IAEA,CAAA;AAAA,EAAC,GAGC+B,IAAiBxB,IACpB;AAAA,IACAyB;AAAA,IACAC,EAAU,UAAU,EAAE,YAAY,IAAM;AAAA,IACxCC;AAAA,IACAC;AAAA,IACAC,EAAU,UAAU,EAAE,OAAO,CAAC,WAAW,WAAW,GAAG;AAAA,IACvDC;AAAA,IACAC;AAAA,IACAC;AAAA,EAAA,IAEA,CAACP,CAAK;AAET,SAAO,CAAC,GAAGb,GAAM,GAAGQ,GAAmB,GAAGI,CAAc;AACzD;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
/**
|
|
3
|
+
* Colours JSON tokens inside `codeBlock` nodes and renders a line-number gutter.
|
|
4
|
+
* A code block stores its whole multi-line body in a single text node, so token
|
|
5
|
+
* offsets map 1:1 onto document positions (`nodeStart + offset`) — no cross-node
|
|
6
|
+
* bookkeeping needed, and lines are just the `\n`-separated runs of that text.
|
|
7
|
+
*
|
|
8
|
+
* Purely decorative: the document text is never rewritten, so undo/redo,
|
|
9
|
+
* character count and the cursor all behave exactly as with plain text. The
|
|
10
|
+
* gutter numbers are `side: -1` widgets that hang into the code block's left
|
|
11
|
+
* padding (see `.bik-json-lineno` in BikEditor.styles.ts), so they never enter
|
|
12
|
+
* the document text or shift character offsets.
|
|
13
|
+
*/
|
|
14
|
+
export declare const JsonHighlightExtension: Extension<any, any>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Extension as h } from "../../../node_modules/@tiptap/core/dist/index.js";
|
|
2
|
+
import { Plugin as d, PluginKey as p, TextSelection as m } from "@tiptap/pm/state";
|
|
3
|
+
import { Decoration as u, DecorationSet as g } from "@tiptap/pm/view";
|
|
4
|
+
import { tokenizeJson as k } from "./tokenizeJson.js";
|
|
5
|
+
const y = {
|
|
6
|
+
key: "bik-json-key",
|
|
7
|
+
string: "bik-json-string",
|
|
8
|
+
number: "bik-json-number",
|
|
9
|
+
boolean: "bik-json-boolean",
|
|
10
|
+
null: "bik-json-null",
|
|
11
|
+
punctuation: "bik-json-punct"
|
|
12
|
+
};
|
|
13
|
+
function f(s) {
|
|
14
|
+
const t = document.createElement("span");
|
|
15
|
+
return t.className = "bik-json-lineno", t.textContent = String(s), t.setAttribute("contenteditable", "false"), t;
|
|
16
|
+
}
|
|
17
|
+
const w = h.create({
|
|
18
|
+
name: "jsonHighlight",
|
|
19
|
+
// Above CodeBlock (default 100) so our Backspace handler runs first and can
|
|
20
|
+
// veto CodeBlock's own "clearNodes at start" shortcut (which would lift the
|
|
21
|
+
// block to a paragraph).
|
|
22
|
+
priority: 1e3,
|
|
23
|
+
addKeyboardShortcuts() {
|
|
24
|
+
return {
|
|
25
|
+
// In JSON mode the document is a single code block. A Backspace at its very
|
|
26
|
+
// start would lift it to a plain paragraph (killing the highlight, the
|
|
27
|
+
// line-number gutter and the monospace font). There's nothing to delete
|
|
28
|
+
// before the caret there anyway, so swallow it. Mid-text Backspace (and
|
|
29
|
+
// deleting a leading `\n` from line 2+) still works — those have
|
|
30
|
+
// parentOffset > 0.
|
|
31
|
+
Backspace: () => {
|
|
32
|
+
const { selection: s } = this.editor.state, { empty: t, $from: e } = s;
|
|
33
|
+
return t && e.parent.type.name === "codeBlock" && e.parentOffset === 0;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
addProseMirrorPlugins() {
|
|
38
|
+
return [
|
|
39
|
+
new d({
|
|
40
|
+
key: new p("jsonHighlightGuard"),
|
|
41
|
+
// jsonMode invariant: the whole document is ONE codeBlock. Several
|
|
42
|
+
// default Backspace paths can lift it to a plain paragraph — and one
|
|
43
|
+
// of them, TipTap core's `clearDocument` appendTransaction (fired when
|
|
44
|
+
// the whole doc is selected then deleted, e.g. Cmd-A → Backspace),
|
|
45
|
+
// runs AFTER our Backspace shortcut, so a keymap veto can't stop it.
|
|
46
|
+
// A paragraph has no gutter and no monospace rule, so the line numbers
|
|
47
|
+
// and font vanish. Re-wrap the content back into a codeBlock whenever
|
|
48
|
+
// something lifts it, restoring the invariant.
|
|
49
|
+
appendTransaction(s, t, e) {
|
|
50
|
+
if (!s.some((a) => a.docChanged))
|
|
51
|
+
return null;
|
|
52
|
+
const { doc: r, schema: i } = e, o = i.nodes.codeBlock;
|
|
53
|
+
if (!o || r.childCount === 1 && r.firstChild?.type === o)
|
|
54
|
+
return null;
|
|
55
|
+
let l = !1;
|
|
56
|
+
if (r.forEach((a) => {
|
|
57
|
+
a.type === o && (l = !0);
|
|
58
|
+
}), l)
|
|
59
|
+
return null;
|
|
60
|
+
const n = r.textBetween(0, r.content.size, `
|
|
61
|
+
`), c = e.tr;
|
|
62
|
+
return c.replaceWith(
|
|
63
|
+
0,
|
|
64
|
+
r.content.size,
|
|
65
|
+
o.create(null, n ? i.text(n) : null)
|
|
66
|
+
), c.setSelection(m.create(c.doc, 1)), c;
|
|
67
|
+
}
|
|
68
|
+
}),
|
|
69
|
+
new d({
|
|
70
|
+
key: new p("jsonHighlight"),
|
|
71
|
+
props: {
|
|
72
|
+
// Paste beautify: if the clipboard text is valid JSON, drop in a
|
|
73
|
+
// pretty-printed version (2-space indent, matching the Beautify
|
|
74
|
+
// button) instead of the raw blob. Non-JSON pastes fall through to
|
|
75
|
+
// the default handler untouched.
|
|
76
|
+
handlePaste(s, t) {
|
|
77
|
+
const e = t.clipboardData?.getData("text/plain");
|
|
78
|
+
if (!e || !e.trim())
|
|
79
|
+
return !1;
|
|
80
|
+
let r;
|
|
81
|
+
try {
|
|
82
|
+
r = JSON.parse(e);
|
|
83
|
+
} catch {
|
|
84
|
+
return !1;
|
|
85
|
+
}
|
|
86
|
+
const i = JSON.stringify(r, null, 2);
|
|
87
|
+
if (i === e)
|
|
88
|
+
return !1;
|
|
89
|
+
const { state: o, dispatch: l } = s, { from: n, to: c } = o.selection;
|
|
90
|
+
return l(o.tr.insertText(i, n, c).scrollIntoView()), !0;
|
|
91
|
+
},
|
|
92
|
+
decorations(s) {
|
|
93
|
+
const t = [];
|
|
94
|
+
return s.doc.descendants((e, r) => {
|
|
95
|
+
if (e.type.name !== "codeBlock") return;
|
|
96
|
+
const i = e.textContent, o = r + 1;
|
|
97
|
+
for (const n of k(i))
|
|
98
|
+
t.push(
|
|
99
|
+
u.inline(o + n.start, o + n.end, {
|
|
100
|
+
class: y[n.type]
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
let l = 1;
|
|
104
|
+
t.push(
|
|
105
|
+
u.widget(o, () => f(1), {
|
|
106
|
+
side: -1,
|
|
107
|
+
key: "ln-1"
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
for (let n = 0; n < i.length; n++) {
|
|
111
|
+
if (i[n] !== `
|
|
112
|
+
`) continue;
|
|
113
|
+
l += 1;
|
|
114
|
+
const c = l;
|
|
115
|
+
t.push(
|
|
116
|
+
u.widget(o + n + 1, () => f(c), {
|
|
117
|
+
side: -1,
|
|
118
|
+
key: `ln-${c}`
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}), g.create(s.doc, t);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
export {
|
|
130
|
+
w as JsonHighlightExtension
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=JsonHighlightExtension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonHighlightExtension.js","sources":["../../../../../src/editor/extensions/jsonHighlight/JsonHighlightExtension.ts"],"sourcesContent":["import { Extension } from '@tiptap/core';\nimport { Plugin, PluginKey, TextSelection } from '@tiptap/pm/state';\nimport { Decoration, DecorationSet } from '@tiptap/pm/view';\nimport { JsonTokenType, tokenizeJson } from './tokenizeJson';\n\nconst CLASS_BY_TYPE: Record<JsonTokenType, string> = {\n\tkey: 'bik-json-key',\n\tstring: 'bik-json-string',\n\tnumber: 'bik-json-number',\n\tboolean: 'bik-json-boolean',\n\tnull: 'bik-json-null',\n\tpunctuation: 'bik-json-punct',\n};\n\n/** Line-number widget rendered in the hanging gutter at each line start. */\nfunction lineNumberWidget(n: number): HTMLElement {\n\tconst span = document.createElement('span');\n\tspan.className = 'bik-json-lineno';\n\tspan.textContent = String(n);\n\tspan.setAttribute('contenteditable', 'false');\n\treturn span;\n}\n\n/**\n * Colours JSON tokens inside `codeBlock` nodes and renders a line-number gutter.\n * A code block stores its whole multi-line body in a single text node, so token\n * offsets map 1:1 onto document positions (`nodeStart + offset`) — no cross-node\n * bookkeeping needed, and lines are just the `\\n`-separated runs of that text.\n *\n * Purely decorative: the document text is never rewritten, so undo/redo,\n * character count and the cursor all behave exactly as with plain text. The\n * gutter numbers are `side: -1` widgets that hang into the code block's left\n * padding (see `.bik-json-lineno` in BikEditor.styles.ts), so they never enter\n * the document text or shift character offsets.\n */\nexport const JsonHighlightExtension = Extension.create({\n\tname: 'jsonHighlight',\n\t// Above CodeBlock (default 100) so our Backspace handler runs first and can\n\t// veto CodeBlock's own \"clearNodes at start\" shortcut (which would lift the\n\t// block to a paragraph).\n\tpriority: 1000,\n\taddKeyboardShortcuts() {\n\t\treturn {\n\t\t\t// In JSON mode the document is a single code block. A Backspace at its very\n\t\t\t// start would lift it to a plain paragraph (killing the highlight, the\n\t\t\t// line-number gutter and the monospace font). There's nothing to delete\n\t\t\t// before the caret there anyway, so swallow it. Mid-text Backspace (and\n\t\t\t// deleting a leading `\\n` from line 2+) still works — those have\n\t\t\t// parentOffset > 0.\n\t\t\tBackspace: () => {\n\t\t\t\tconst { selection } = this.editor.state;\n\t\t\t\tconst { empty, $from } = selection;\n\t\t\t\treturn (\n\t\t\t\t\tempty &&\n\t\t\t\t\t$from.parent.type.name === 'codeBlock' &&\n\t\t\t\t\t$from.parentOffset === 0\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t},\n\taddProseMirrorPlugins() {\n\t\treturn [\n\t\t\tnew Plugin({\n\t\t\t\tkey: new PluginKey('jsonHighlightGuard'),\n\t\t\t\t// jsonMode invariant: the whole document is ONE codeBlock. Several\n\t\t\t\t// default Backspace paths can lift it to a plain paragraph — and one\n\t\t\t\t// of them, TipTap core's `clearDocument` appendTransaction (fired when\n\t\t\t\t// the whole doc is selected then deleted, e.g. Cmd-A → Backspace),\n\t\t\t\t// runs AFTER our Backspace shortcut, so a keymap veto can't stop it.\n\t\t\t\t// A paragraph has no gutter and no monospace rule, so the line numbers\n\t\t\t\t// and font vanish. Re-wrap the content back into a codeBlock whenever\n\t\t\t\t// something lifts it, restoring the invariant.\n\t\t\t\tappendTransaction(transactions, _oldState, newState) {\n\t\t\t\t\tif (!transactions.some((tr) => tr.docChanged)) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\tconst { doc, schema } = newState;\n\t\t\t\t\tconst codeBlock = schema.nodes['codeBlock'];\n\t\t\t\t\tif (!codeBlock) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\t// Common case: already a single codeBlock → nothing to do.\n\t\t\t\t\tif (doc.childCount === 1 && doc.firstChild?.type === codeBlock) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\t// If a codeBlock survives anywhere, leave it alone (don't fight\n\t\t\t\t\t// legitimate multi-node edits that shouldn't happen in jsonMode).\n\t\t\t\t\tlet hasCodeBlock = false;\n\t\t\t\t\tdoc.forEach((node) => {\n\t\t\t\t\t\tif (node.type === codeBlock) {\n\t\t\t\t\t\t\thasCodeBlock = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tif (hasCodeBlock) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\t// A lift happened: rebuild a single codeBlock from the text\n\t\t\t\t\t// (block boundaries preserved as newlines, matching JSON lines).\n\t\t\t\t\tconst text = doc.textBetween(0, doc.content.size, '\\n');\n\t\t\t\t\tconst tr = newState.tr;\n\t\t\t\t\ttr.replaceWith(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tdoc.content.size,\n\t\t\t\t\t\tcodeBlock.create(null, text ? schema.text(text) : null),\n\t\t\t\t\t);\n\t\t\t\t\ttr.setSelection(TextSelection.create(tr.doc, 1));\n\t\t\t\t\treturn tr;\n\t\t\t\t},\n\t\t\t}),\n\t\t\tnew Plugin({\n\t\t\t\tkey: new PluginKey('jsonHighlight'),\n\t\t\t\tprops: {\n\t\t\t\t\t// Paste beautify: if the clipboard text is valid JSON, drop in a\n\t\t\t\t\t// pretty-printed version (2-space indent, matching the Beautify\n\t\t\t\t\t// button) instead of the raw blob. Non-JSON pastes fall through to\n\t\t\t\t\t// the default handler untouched.\n\t\t\t\t\thandlePaste(view, event) {\n\t\t\t\t\t\tconst raw = event.clipboardData?.getData('text/plain');\n\t\t\t\t\t\tif (!raw || !raw.trim()) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet value: unknown;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvalue = JSON.parse(raw);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst pretty = JSON.stringify(value, null, 2);\n\t\t\t\t\t\t// No-op if it was already beautified (avoids a redundant tx).\n\t\t\t\t\t\tif (pretty === raw) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst { state, dispatch } = view;\n\t\t\t\t\t\tconst { from, to } = state.selection;\n\t\t\t\t\t\tdispatch(state.tr.insertText(pretty, from, to).scrollIntoView());\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t\tdecorations(state) {\n\t\t\t\t\t\tconst decorations: Decoration[] = [];\n\t\t\t\t\t\tstate.doc.descendants((node, pos) => {\n\t\t\t\t\t\t\tif (node.type.name !== 'codeBlock') return;\n\t\t\t\t\t\t\tconst text = node.textContent;\n\t\t\t\t\t\t\t// +1: step past the code-block node boundary into its text.\n\t\t\t\t\t\t\tconst base = pos + 1;\n\n\t\t\t\t\t\t\tfor (const tok of tokenizeJson(text)) {\n\t\t\t\t\t\t\t\tdecorations.push(\n\t\t\t\t\t\t\t\t\tDecoration.inline(base + tok.start, base + tok.end, {\n\t\t\t\t\t\t\t\t\t\tclass: CLASS_BY_TYPE[tok.type],\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// One line number per `\\n`-separated line (line 1 at offset 0,\n\t\t\t\t\t\t\t// then one after every newline). `white-space: pre` on the block\n\t\t\t\t\t\t\t// disables wrapping, so logical lines == visual lines.\n\t\t\t\t\t\t\tlet line = 1;\n\t\t\t\t\t\t\tdecorations.push(\n\t\t\t\t\t\t\t\tDecoration.widget(base, () => lineNumberWidget(1), {\n\t\t\t\t\t\t\t\t\tside: -1,\n\t\t\t\t\t\t\t\t\tkey: 'ln-1',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tfor (let i = 0; i < text.length; i++) {\n\t\t\t\t\t\t\t\tif (text[i] !== '\\n') continue;\n\t\t\t\t\t\t\t\tline += 1;\n\t\t\t\t\t\t\t\tconst n = line;\n\t\t\t\t\t\t\t\tdecorations.push(\n\t\t\t\t\t\t\t\t\tDecoration.widget(base + i + 1, () => lineNumberWidget(n), {\n\t\t\t\t\t\t\t\t\t\tside: -1,\n\t\t\t\t\t\t\t\t\t\tkey: `ln-${n}`,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn DecorationSet.create(state.doc, decorations);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t];\n\t},\n});\n"],"names":["CLASS_BY_TYPE","lineNumberWidget","n","span","JsonHighlightExtension","Extension","selection","empty","$from","Plugin","PluginKey","transactions","_oldState","newState","tr","doc","schema","codeBlock","hasCodeBlock","node","text","TextSelection","view","event","raw","value","pretty","state","dispatch","from","to","decorations","pos","base","tok","tokenizeJson","Decoration","line","i","DecorationSet"],"mappings":"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"}
|