@bikdotai/bik-component-library 0.0.823 → 0.0.824-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/editor/BikEditor.js +1 -1
- package/dist/cjs/editor/BikEditor.js.map +1 -1
- package/dist/cjs/editor/BikEditor.styles.js +122 -2
- 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 +3 -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/BIK AI specific/WandSparkles.js +2 -0
- package/dist/cjs/icons/BIK AI specific/WandSparkles.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/editor/BikEditor.js +254 -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 +128 -6
- 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 +64 -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/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/index.d.ts +1 -0
- package/dist/esm/icons/index.d.ts +1 -1
- package/dist/esm/icons/index.js +18 -16
- 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.types.js","sources":["../../../src/editor/BikEditor.types.ts"],"sourcesContent":["/**\n * ┌─────────────────────────────────────────────────────────────────────────┐\n * │ PUBLIC API LAYER — zero editor-framework imports │\n * │ │\n * │ This file is the consumer contract for BikEditor. It has NO dependency │\n * │ on TipTap, Lexical, Slate, or any other rich-text library. │\n * │ │\n * │ If the underlying editor is ever swapped, ONLY the implementation files │\n * │ change — this file, src/editor/index.ts, and all consumer code stay as- │\n * │ is. The files that would change are: │\n * │ • BikEditor.tsx — wires props into the editor instance │\n * │ • BikEditor.utils.ts — HTML / document-state adapters │\n * │ • extensions/ — extension / plugin layer │\n * └─────────────────────────────────────────────────────────────────────────┘\n */\nimport React from 'react';\n\n/**\n * Opt in to specific editor capabilities. Both default to `false`.\n *\n * @example\n * // Email editor with full rich text support\n * <BikEditor features={{ richPaste: true, richTypography: true }} />\n */\nexport interface EditorFeatures {\n\t/** Preserve HTML formatting on paste. Default: `false`. */\n\trichPaste?: boolean;\n\t/** Enable font family, font size, text alignment, highlight, subscript, superscript, and image. Default: `false`. */\n\trichTypography?: boolean;\n\t/**\n\t * Restrict which inline marks are active in the editor.\n\t * When set, only the listed marks are registered — all others are disabled\n\t * (no rendering, no keyboard shortcuts, no paste preservation).\n\t * Omit to allow all marks (default).\n\t *\n\t * @example\n\t * // LiveChat: bold only, no italic / underline / strike\n\t * features={{ allowedMarks: ['bold'] }}\n\t */\n\tallowedMarks?: Array<'bold' | 'italic' | 'strike' | 'underline' | 'code'>;\n}\n\n/**\n * A named content section placed below the main body, separated by an invisible divider.\n *\n * Sections let you split a single editor instance into independently readable\n * and writable zones — for example: body / signature / forwarded mail.\n *\n * @example\n * sections={[\n * { id: 'signature', content: '<p>Best regards,<br/>Alice</p>' },\n * { id: 'forwarded', content: '<p>--- Forwarded message ---</p>' },\n * ]}\n */\nexport interface EditorSection {\n\t/**\n\t * Unique identifier. Used in ref methods: `getSectionContent(id)`,\n\t * `setSectionContent(id, html)`, `focusSection(id)`, `clearSection(id)`.\n\t * The string `'body'` is reserved for the main content above all dividers.\n\t */\n\tid: string;\n\t/** Initial HTML content of this section. */\n\tcontent: string;\n}\n\n/**\n * Data passed to the `onPaste` callback.\n * Return `true` from your handler to suppress BikEditor's default paste handling\n * (rich-paste and plain-text stripping won't run for that event).\n */\nexport interface PasteData {\n\t/** All files in the clipboard (images, PDFs, etc.). */\n\tfiles: File[];\n\t/** Image files only — a filtered subset of `files`. */\n\timages: File[];\n\t/** Plain text from the clipboard. */\n\ttext: string;\n\t/** HTML from the clipboard (empty string if the source doesn't provide HTML). */\n\thtml: string;\n\t/** The raw browser ClipboardEvent — for advanced use. */\n\tevent: ClipboardEvent;\n}\n\n/**\n * A custom keyboard shortcut registered with the editor.\n *\n * @example\n * shortcuts={[\n * // Mod = Cmd on Mac, Ctrl on Windows/Linux\n * { key: 'Enter', modifiers: ['mod'], handler: handleSend },\n * { key: 's', modifiers: ['mod'], handler: handleSave },\n * { key: 'k', modifiers: ['mod', 'shift'], handler: openLinkModal },\n * ]}\n */\nexport interface KeyboardShortcut {\n\t/** Key name. Use the same strings as KeyboardEvent.key, e.g. `'Enter'`, `'s'`, `'k'`. */\n\tkey: string;\n\t/**\n\t * Modifier keys.\n\t * `'mod'` maps to Cmd on macOS and Ctrl on Windows/Linux — prefer it over\n\t * `'ctrl'` for cross-platform shortcuts.\n\t */\n\tmodifiers?: Array<'mod' | 'ctrl' | 'shift' | 'alt'>;\n\t/** Called when the shortcut fires. Returning nothing is fine. */\n\thandler: () => void;\n}\n\n/** Content object returned by onChange, onSend, getContent(), and getSectionContent(). */\nexport interface EditorSnapshot {\n\t/** Serialised HTML. Safe to pass back to setContent() or setSectionContent(). */\n\thtml: string;\n\t/** Plain text, stripped of all marks and block nodes. */\n\ttext: string;\n\t/** True when the document contains no user-visible content. */\n\tisEmpty: boolean;\n\t/** Character count at this point in time. */\n\tcharacterCount: number;\n}\n\n/** One item shown in the @agent or #team mention dropdown. */\nexport interface MentionItem {\n\t/** Unique identifier stored as a node attribute. Used to look up the item later. */\n\tid: string | number;\n\t/** Display name shown in the dropdown and inserted into the document. */\n\tlabel: string;\n\t/** Avatar image URL. Shown in the built-in row when renderMentionItem is not provided. */\n\tavatarUrl?: string;\n\t/** When true, a green presence dot appears in the built-in row. */\n\tisOnline?: boolean;\n\t/** Any extra data your app needs. Passed back as-is in onMentionSelected. */\n\tmeta?: Record<string, unknown>;\n}\n\n/** One item shown in the / slash-command dropdown. */\nexport interface SlashCommandItem {\n\t/** Unique identifier. */\n\tid: string;\n\t/** Short command name shown as the primary label in the menu. */\n\tlabel: string;\n\t/** Optional one-line description shown below the label in the built-in row. */\n\tdescription?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Feature config objects — pass one object per feature instead of N flat props\n// ---------------------------------------------------------------------------\n\n/**\n * Props passed to the `renderDropdown` function in `MentionConfig`.\n * `activeIndex` is the keyboard-navigated row — highlight it in your UI.\n * Call `onSelect(item)` when the user picks one.\n */\nexport interface MentionDropdownRenderProps {\n\titems: MentionItem[];\n\t/** The text the user typed after the `@` or `#` trigger. */\n\tquery: string;\n\t/**\n\t * Index of the currently highlighted row (arrow-key navigation).\n\t * The editor manages this automatically — just use it for visual highlighting.\n\t */\n\tactiveIndex: number;\n\t/** Call this when the user selects an item (click, touch, etc.). */\n\tonSelect: (item: MentionItem) => void;\n\t/**\n\t * Register a keyboard handler so a CUSTOM dropdown can own arrow-key / Enter\n\t * navigation (e.g. a multi-level picker whose visible rows differ from the\n\t * flat `items` list). Return `true` from the handler to consume the key.\n\t * Pass `null` to unregister. When not registered, the editor falls back to\n\t * navigating the flat `items` list.\n\t */\n\tregisterKeyHandler?: (\n\t\thandler: ((event: KeyboardEvent) => boolean) | null,\n\t) => void;\n\t/**\n\t * Dismiss the dropdown WITHOUT selecting an item: closes the menu and strips\n\t * the typed `@`/`#` trigger + query. Use for custom actions in a multi-level\n\t * picker (e.g. a \"Create new\" row that opens a drawer instead of inserting a\n\t * mention).\n\t */\n\tonClose?: () => void;\n}\n\n/**\n * Everything related to @ and # mention dropdowns, in one place.\n *\n * @example\n * mentions={{\n * agents: [{ id: 1, label: 'Alice', avatarUrl: '...' }],\n * teams: [{ id: 't1', label: 'Support' }],\n * onSelect: (item, char) => console.log(char, item),\n * }}\n */\nexport interface MentionConfig {\n\t/** Items shown when the user types `@`. */\n\tagents?: MentionItem[];\n\t/** Items shown when the user types `#`. */\n\tteams?: MentionItem[];\n\t/** Called when the user picks an item. `char` is `'@'` or `'#'`. */\n\tonSelect?: (item: MentionItem, char: '@' | '#') => void;\n\t/**\n\t * Custom renderer for each row in the dropdown.\n\t * `isActive` is `true` when that row is currently highlighted by keyboard\n\t * navigation (arrows move it, Enter selects it). Use it to apply a highlight\n\t * style so the user can see which item they're about to select.\n\t * Omit to use the built-in avatar + name row.\n\t *\n\t * Use `renderDropdown` instead when you need a completely different structure.\n\t *\n\t * @example\n\t * renderItem={(item, isActive) => (\n\t * <div style={{ background: isActive ? '#f0f0f0' : undefined }}>\n\t * {item.label}\n\t * </div>\n\t * )}\n\t */\n\trenderItem?: (item: MentionItem, isActive: boolean) => React.ReactNode;\n\t/**\n\t * Render the entire dropdown yourself instead of individual rows.\n\t * The editor still manages keyboard navigation — `activeIndex` tells you\n\t * which row is highlighted, and `onSelect(item)` confirms the selection.\n\t * Takes priority over `renderItem` when both are provided.\n\t *\n\t * @example\n\t * renderDropdown={({ items, query, activeIndex, onSelect }) => (\n\t * <MyDropdown\n\t * items={items}\n\t * highlightedIndex={activeIndex}\n\t * onPick={onSelect}\n\t * />\n\t * )}\n\t */\n\trenderDropdown?: (props: MentionDropdownRenderProps) => React.ReactNode;\n\t/**\n\t * When true, a typed trigger char (`@` / `#`) and its query are removed if the\n\t * suggestion menu closes without a selection (click-outside / no match). Default\n\t * false — the typed text is kept.\n\t */\n\tremoveTriggerOnDismiss?: boolean;\n}\n\n/**\n * Props passed to the `renderDropdown` function in `SlashCommandConfig`.\n */\nexport interface SlashCommandDropdownRenderProps {\n\titems: SlashCommandItem[];\n\t/** The text the user typed after `/`. */\n\tquery: string;\n\t/** Index of the currently highlighted row. Use for visual highlighting. */\n\tactiveIndex: number;\n\t/** Call this when the user selects an item. */\n\tonSelect: (item: SlashCommandItem) => void;\n}\n\n/**\n * Everything related to the `/` slash-command menu, in one place.\n *\n * @example\n * slashCommands={{\n * items: [{ id: 'ai-reply', label: 'AI Reply', description: 'Generate a reply' }],\n * onSelect: (cmd) => triggerAI(cmd.id),\n * }}\n */\nexport interface SlashCommandConfig {\n\t/** The list of commands. Filtered client-side as the user types after `/`. */\n\titems: SlashCommandItem[];\n\t/** Called when the user selects a command. The `/` trigger is removed from the doc. */\n\tonSelect?: (command: SlashCommandItem) => void;\n\t/**\n\t * Custom renderer for each row.\n\t * `isActive` is `true` when that row is highlighted by keyboard navigation.\n\t * Omit to use the built-in label + description row.\n\t *\n\t * Use `renderDropdown` instead when you need a completely different structure.\n\t */\n\trenderItem?: (item: SlashCommandItem, isActive: boolean) => React.ReactNode;\n\t/**\n\t * Render the entire slash-command dropdown yourself.\n\t * The editor manages keyboard navigation — use `activeIndex` to highlight\n\t * the active row and call `onSelect(item)` to confirm selection.\n\t * Takes priority over `renderItem` when both are provided.\n\t *\n\t * @example\n\t * renderDropdown={({ items, activeIndex, onSelect }) => (\n\t * <MyCmdPalette commands={items} active={activeIndex} onPick={onSelect} />\n\t * )}\n\t */\n\trenderDropdown?: (props: SlashCommandDropdownRenderProps) => React.ReactNode;\n}\n\n/**\n * Configuration for how hyperlinks behave in the editor.\n *\n * @example\n * link={{ renderTooltip: ({ href, text, onOpen, onRemove }) => (\n * <MyLinkPopover href={href} onOpen={onOpen} onRemove={onRemove} />\n * )}}\n */\nexport interface LinkConfig {\n\t/**\n\t * Custom tooltip rendered when the user clicks a link.\n\t * Receives the href, display text, bounding rect (for positioning),\n\t * and ready-to-call action handlers.\n\t * Omit to use the minimal built-in tooltip.\n\t */\n\trenderTooltip?: (props: LinkTooltipProps) => React.ReactNode;\n}\n\n/**\n * Snapshot of all active marks and block types at the current cursor or selection.\n * Returned by onSelectionChange and ref.getActiveFormats().\n *\n * Keep this in component state and pass it to BikEditorToolbar (or your own toolbar)\n * to keep button active-states in sync.\n *\n * @example\n * const [formats, setFormats] = useState(DEFAULT_ACTIVE_FORMATS);\n * <BikEditor onSelectionChange={setFormats} ... />\n * <BikEditorToolbar activeFormats={formats} ... />\n */\nexport interface FormatState {\n\tbold: boolean;\n\titalic: boolean;\n\tunderline: boolean;\n\tstrike: boolean;\n\tbulletList: boolean;\n\torderedList: boolean;\n\tblockquote: boolean;\n\tcodeBlock: boolean;\n\t/** Non-null when the cursor is inside (or selection spans) a hyperlink. */\n\tlink: { href: string } | null;\n\ttextAlign: 'left' | 'center' | 'right' | 'justify' | null;\n\tfontFamily: string | null;\n\tfontSize: string | null;\n\t/** Active text colour as a CSS colour string, or null if none. */\n\tcolor: string | null;\n\t/** Active highlight (background) colour, or null if none. */\n\thighlight: string | null;\n\t/** True when the cursor/selection is in a superscript. */\n\tsuperscript: boolean;\n\t/** True when the cursor/selection is in a subscript. */\n\tsubscript: boolean;\n}\n\nexport const DEFAULT_FORMAT_STATE: FormatState = {\n\tbold: false,\n\titalic: false,\n\tunderline: false,\n\tstrike: false,\n\tbulletList: false,\n\torderedList: false,\n\tblockquote: false,\n\tcodeBlock: false,\n\tlink: null,\n\ttextAlign: null,\n\tfontFamily: null,\n\tfontSize: null,\n\tcolor: null,\n\thighlight: null,\n\tsuperscript: false,\n\tsubscript: false,\n};\n\n/**\n * All formatting and insertion commands. Access via `editorRef.current.actions`.\n *\n * Every method focuses the editor internally — no need to call `.focus()` first.\n *\n * @example\n * editorRef.current?.actions.toggleBold();\n * editorRef.current?.actions.setLink('https://example.com', 'Visit example');\n */\nexport interface EditorActions {\n\t// ── Basic marks ───────────────────────────────────────────────────────────\n\ttoggleBold: () => void;\n\ttoggleItalic: () => void;\n\ttoggleUnderline: () => void;\n\ttoggleStrike: () => void;\n\n\t// ── Blocks ────────────────────────────────────────────────────────────────\n\ttoggleBulletList: () => void;\n\ttoggleOrderedList: () => void;\n\ttoggleBlockquote: () => void;\n\ttoggleCodeBlock: () => void;\n\n\t// ── Alignment & typography ────────────────────────────────────────────────\n\tsetTextAlign: (align: 'left' | 'center' | 'right' | 'justify') => void;\n\t/** Pass a CSS font-family value, e.g. `'Inter, sans-serif'`. Only works when richTypography is on. */\n\tsetFontFamily: (font: string) => void;\n\t/** Pass a CSS size value, e.g. `'16px'` or `'1.25rem'`. Only works when richTypography is on. */\n\tsetFontSize: (size: string) => void;\n\n\t// ── Colour ────────────────────────────────────────────────────────────────\n\t/** Sets the text colour. Pass any CSS colour value, e.g. `'#e53e3e'`. */\n\tsetColor: (color: string) => void;\n\t/** Sets the highlight (background) colour. */\n\tsetHighlight: (color: string) => void;\n\t/** Removes the text colour mark from the current selection. */\n\tunsetColor: () => void;\n\t/** Removes the highlight mark from the current selection. */\n\tunsetHighlight: () => void;\n\n\t// ── Links ─────────────────────────────────────────────────────────────────\n\t/**\n\t * Applies a hyperlink to the current selection, or updates the link the\n\t * cursor is already inside.\n\t *\n\t * @param href The URL.\n\t * @param text Optional display text. When provided, the visible text of the\n\t * linked range is replaced atomically in the same undo step.\n\t *\n\t * @example\n\t * actions.setLink('https://example.com');\n\t * actions.setLink('https://example.com', 'Visit example');\n\t */\n\tsetLink: (href: string, text?: string) => void;\n\t/** Updates only the href of the link at the current cursor. */\n\tupdateLink: (href: string) => void;\n\t/** Removes the link mark but keeps the visible text. */\n\tremoveLink: () => void;\n\n\t// ── Insertions ────────────────────────────────────────────────────────────\n\t/** Inserts an emoji character at the cursor. */\n\tinsertEmoji: (emoji: string) => void;\n\t/**\n\t * Inserts a `{{variableName}}` token at the cursor.\n\t * The token renders with a distinct highlight inside the editor.\n\t */\n\tinsertVariable: (variableName: string) => void;\n\t/** Inserts arbitrary HTML at the current cursor position. */\n\tinsertHtml: (html: string) => void;\n\n\t// ── History ───────────────────────────────────────────────────────────────\n\tundo: () => void;\n\tredo: () => void;\n\tcanUndo: () => boolean;\n\tcanRedo: () => boolean;\n\n\t// ── Rich typography (richTypography feature required) ─────────────────────\n\t/** Toggles superscript on the selection. Only works when richTypography is on. */\n\ttoggleSuperscript: () => void;\n\t/** Toggles subscript on the selection. Only works when richTypography is on. */\n\ttoggleSubscript: () => void;\n\t/** Inserts an inline image at the cursor. Only works when richTypography is on. */\n\tinsertImage: (src: string) => void;\n}\n\n/** Props passed to your renderLinkTooltip render prop. */\nexport interface LinkTooltipProps {\n\t/** The href of the active link. */\n\thref: string;\n\t/** The display text of the link. */\n\ttext: string;\n\t/** Bounding rect of the link element — use this to position your popover. */\n\trect: DOMRect;\n\t/** Opens the link in a new tab. */\n\tonOpen: () => void;\n\t/** Removes the link mark but keeps the visible text. */\n\tonRemove: () => void;\n\t/** Hides the bubble menu. Call this before opening an edit modal so the tooltip doesn't linger. */\n\tonHide: () => void;\n}\n\n/**\n * Imperative handle exposed by BikEditor.\n *\n * Obtain via `useRef<BikEditorRef>()` and pass to `<BikEditor ref={...} />`.\n * All methods are safe to call after the first render.\n *\n * @example\n * const ref = useRef<BikEditorRef>(null);\n * <BikEditor ref={ref} ... />\n * ref.current?.clearContent();\n * ref.current?.actions.toggleBold();\n */\nexport interface BikEditorRef {\n\t// ── Focus ─────────────────────────────────────────────────────────────────\n\t/** Moves focus into the editor. Optionally pass a position: 'start', 'end', 'all', or a numeric offset. */\n\tfocus: (position?: 'start' | 'end' | 'all' | number | boolean | null) => void;\n\t/** Removes focus from the editor. */\n\tblur: () => void;\n\n\t// ── Content ───────────────────────────────────────────────────────────────\n\t/** Clears the entire document and fires onChange. */\n\tclearContent: () => void;\n\t/**\n\t * Replaces the full document with the given HTML and fires onChange.\n\t * Overwrites all sections. Use setSectionContent() / setBodyContent() to\n\t * update a single section without touching the others.\n\t */\n\tsetContent: (html: string) => void;\n\t/** Inserts HTML at the current cursor position without replacing anything. */\n\tinsertContent: (html: string) => void;\n\t/**\n\t * Inserts HTML inline at the current cursor position, unwrapping block\n\t * wrappers (e.g. `<p>`) so the content merges into the surrounding paragraph\n\t * instead of creating new blocks. Use for macros, quick replies, etc.\n\t */\n\tinsertInlineContent: (html: string) => void;\n\t/** Inserts HTML at the very start of the document. */\n\tinsertAtStart: (html: string) => void;\n\t/** Inserts HTML as a new block at the end of the document. */\n\tinsertBlock: (html: string) => void;\n\t/**\n\t * Appends HTML inline inside the last paragraph of the document.\n\t * Ideal for streaming — call repeatedly with each text chunk.\n\t * Use `appendBodyContent` instead if the editor has named sections.\n\t */\n\tappendInline: (html: string) => void;\n\t/** @deprecated Use `insertBlock` */\n\tinsertAtEnd: (html: string) => void;\n\t/** @deprecated Use `appendInline` */\n\tappendContent: (html: string) => void;\n\t/** Returns the full document as `{ html, text, isEmpty, characterCount }`. */\n\tgetContent: () => EditorSnapshot;\n\t/**\n\t * Returns the raw TipTap document as a plain JSON object.\n\t * Returns `null` when the editor is not yet mounted.\n\t */\n\tgetJSON: () => Record<string, unknown> | null;\n\t/**\n\t * Walks the document and returns all mention IDs currently in the editor,\n\t * split by type. IDs are deduplicated and returned as arrays.\n\t *\n\t * @example\n\t * const { agentIds, teamIds } = ref.current?.getMentions() ?? { agentIds: [], teamIds: [] };\n\t */\n\tgetMentions: () => { agentIds: string[]; teamIds: string[] };\n\n\t// ── Selection ─────────────────────────────────────────────────────────────\n\t/**\n\t * Returns the current cursor / selection position as a `{ from, to }` pair.\n\t * When nothing is selected, `from === to` (a collapsed cursor).\n\t * Pass the returned value to `insertAtPosition()` to insert at an exact spot.\n\t *\n\t * @example\n\t * const { from } = ref.current?.getCursorPosition() ?? { from: 0, to: 0 };\n\t * ref.current?.insertAtPosition(from, '<strong>inserted</strong>');\n\t */\n\tgetCursorPosition: () => { from: number; to: number };\n\t/**\n\t * Inserts HTML at an explicit document position rather than at the current cursor.\n\t * Use `getCursorPosition()` to get the position, or any value from\n\t * `findSectionStartPos` / `findSectionEndPos` if you need section-relative insertion.\n\t *\n\t * @example\n\t * const { from } = ref.current!.getCursorPosition();\n\t * ref.current?.insertAtPosition(from, '<em>hello</em>');\n\t */\n\tinsertAtPosition: (pos: number, html: string) => void;\n\t/**\n\t * Returns the currently selected plain text.\n\t * When the cursor is inside a link with nothing selected, returns the full\n\t * link text — useful for pre-filling a link modal's text field.\n\t */\n\tgetSelectedText: () => string;\n\n\t// ── Character count ───────────────────────────────────────────────────────\n\t/**\n\t * Returns the current character count and the configured limit (or null).\n\t * Use this to render your own character counter anywhere you like.\n\t *\n\t * @example\n\t * const { count, limit } = ref.current?.getCharacterCount() ?? { count: 0, limit: null };\n\t * <span style={{ color: count >= (limit ?? Infinity) ? 'red' : 'inherit' }}>\n\t * {count}{limit != null ? ` / ${limit}` : ''}\n\t * </span>\n\t */\n\tgetCharacterCount: () => { count: number; limit: number | null };\n\n\t// ── Sections ─────────────────────────────────────────────────────────────\n\t/**\n\t * Returns content for a named section.\n\t * Use `'body'` to get the main content above all section dividers.\n\t *\n\t * @example\n\t * const sig = ref.current?.getSectionContent('signature');\n\t */\n\tgetSectionContent: (sectionId: string) => EditorSnapshot;\n\t/**\n\t * Replaces the content of a named section without touching other sections.\n\t * If the section doesn't exist yet it is appended at the end.\n\t *\n\t * @example\n\t * ref.current?.setSectionContent('signature', '<p>New signature</p>');\n\t * ref.current?.setSectionContent('body', rephrased); // same as setBodyContent\n\t */\n\tsetSectionContent: (sectionId: string, html: string) => void;\n\t/**\n\t * Moves the cursor to the start of a named section.\n\t * Use `'body'` to focus the main content area.\n\t */\n\tfocusSection: (sectionId: string) => void;\n\t/** Clears all content in a named section (replaces with an empty paragraph). */\n\tclearSection: (sectionId: string) => void;\n\n\t// ── Convenience body shorthands ───────────────────────────────────────────\n\t/** Shorthand for `getSectionContent('body')`. */\n\tgetBodyContent: () => EditorSnapshot;\n\t/** Shorthand for `setSectionContent('body', html)`. */\n\tsetBodyContent: (html: string) => void;\n\t/**\n\t * Atomically sets the body AND all sections in a single editor update.\n\t * Sections are laid out in the exact order supplied — use this instead of\n\t * multiple `setSectionContent()` calls whenever order matters (e.g. forward\n\t * mail where signature must appear before the forwarded thread).\n\t *\n\t * @example\n\t * ref.current?.setBodyAndSections('<p></p>', [\n\t * { id: 'signature', content: '<p>Alice</p>' },\n\t * { id: 'forwarded', content: '<p>--- Forwarded ---</p>' },\n\t * ]);\n\t */\n\tsetBodyAndSections: (\n\t\tbody: string,\n\t\tsections: Array<{ id: string; content: string }>,\n\t) => void;\n\t/**\n\t * Appends HTML to the end of the body section without moving the cursor.\n\t * Ideal for streaming AI-generated content — call repeatedly with each chunk.\n\t * Does NOT clear existing content; call `setBodyContent('')` first if needed.\n\t *\n\t * @example\n\t * // Stream AI response chunk-by-chunk\n\t * ref.current?.setBodyContent('');\n\t * for await (const chunk of aiStream) {\n\t * ref.current?.appendBodyContent(chunk);\n\t * }\n\t */\n\tappendBodyContent: (html: string) => void;\n\n\t/**\n\t * Inserts HTML at the start of a named section (or `'body'`).\n\t * Inserts at the very start of the document when `sectionId` is `'body'`.\n\t */\n\tinsertAtSectionStart: (sectionId: string, html: string) => void;\n\t/**\n\t * Inserts HTML at the end of a named section (or `'body'`).\n\t * For `'body'`, inserts just before the first section divider (or at end of doc).\n\t */\n\tinsertAtSectionEnd: (sectionId: string, html: string) => void;\n\n\t// ── Formats & actions ─────────────────────────────────────────────────────\n\t/** Returns a snapshot of all active marks at the current cursor or selection. */\n\tgetActiveFormats: () => FormatState;\n\t/** All formatting and insertion commands. See EditorActions. */\n\tactions: EditorActions;\n}\n\n/**\n * Props for `<BikEditor />`. All props are optional.\n */\nexport interface BikEditorProps {\n\t// ── Features ──────────────────────────────────────────────────────────────\n\t/**\n\t * Opt in to specific editor capabilities. Both default to `false`.\n\t *\n\t * @example\n\t * <BikEditor features={{ richPaste: true, richTypography: true }} />\n\t */\n\tfeatures?: EditorFeatures;\n\n\t// ── Content ───────────────────────────────────────────────────────────────\n\t/**\n\t * HTML loaded on mount. Ignored after first render.\n\t * Use `ref.setContent()` to change content imperatively later.\n\t */\n\tinitialContent?: string;\n\t/**\n\t * Named sections placed below the main body.\n\t * Each section is separated from the previous one by an invisible divider node.\n\t * The array order determines the visual order in the document.\n\t *\n\t * These are only the INITIAL values — use `ref.setSectionContent()` to update\n\t * a section after mount without re-mounting the editor.\n\t *\n\t * @example\n\t * sections={[\n\t * { id: 'signature', content: '<p>Best regards, Alice</p>' },\n\t * { id: 'forwarded', content: '<p>--- Forwarded message ---</p>' },\n\t * ]}\n\t */\n\tsections?: EditorSection[];\n\t/** Placeholder text shown when the editor is empty. */\n\tplaceholder?: string;\n\n\t// ── Behaviour ─────────────────────────────────────────────────────────────\n\t/** Makes the editor read-only. The toolbar still renders but actions are no-ops. */\n\tdisabled?: boolean;\n\t/**\n\t * Hard character limit. The editor refuses to accept input beyond this point.\n\t * The limit is enforced silently — no UI is rendered. Use\n\t * `ref.getCharacterCount()` or `onChange.characterCount` to display it yourself.\n\t */\n\tmaxCharacters?: number;\n\n\t// ── Keyboard shortcuts ────────────────────────────────────────────────────\n\t/**\n\t * Custom keyboard shortcuts. BikEditor registers no shortcuts of its own —\n\t * every shortcut must be explicitly listed here.\n\t *\n\t * @example\n\t * shortcuts={[\n\t * { key: 'Enter', modifiers: ['mod'], handler: handleSend },\n\t * { key: 's', modifiers: ['mod', 'shift'], handler: handleDraft },\n\t * ]}\n\t */\n\tshortcuts?: KeyboardShortcut[];\n\t/**\n\t * Shortcut key that fires `onSend`. No key is bound to `onSend` unless you\n\t * set this explicitly. Requires `onSend` to also be provided.\n\t *\n\t * `'mod'` maps to Cmd on macOS and Ctrl on Windows/Linux.\n\t *\n\t * @example\n\t * // Single shortcut\n\t * sendShortcut={{ key: 'Enter', modifiers: ['mod'] }}\n\t * // Multiple shortcuts\n\t * sendShortcut={[\n\t * { key: 'Enter', modifiers: ['mod'] },\n\t * { key: 's', modifiers: ['mod'] },\n\t * ]}\n\t */\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\n\t// ── Mentions ─────────────────────────────────────────────────────────────\n\t/**\n\t * Enables `@` (agent) and `#` (team) mention dropdowns.\n\t * Bundle all mention-related config in one object so it's easy to find.\n\t *\n\t * @example\n\t * mentions={{\n\t * agents: myAgents,\n\t * teams: myTeams,\n\t * onSelect: (item, char) => track(char, item.id),\n\t * }}\n\t */\n\tmentions?: MentionConfig;\n\n\t// ── Slash commands ────────────────────────────────────────────────────────\n\t/**\n\t * Enables the `/` slash-command menu.\n\t * Bundle all slash-command config in one object.\n\t *\n\t * @example\n\t * slashCommands={{\n\t * items: MY_COMMANDS,\n\t * onSelect: (cmd) => handleCommand(cmd.id),\n\t * }}\n\t */\n\tslashCommands?: SlashCommandConfig;\n\n\t// ── Link ─────────────────────────────────────────────────────────────────\n\t/**\n\t * Link-related configuration — currently just the click tooltip.\n\t *\n\t * @example\n\t * link={{ renderTooltip: ({ href, onOpen, onRemove }) => (\n\t * <MyTooltip href={href} onOpen={onOpen} onRemove={onRemove} />\n\t * )}}\n\t */\n\tlink?: LinkConfig;\n\n\t// ── Callbacks ─────────────────────────────────────────────────────────────\n\t/**\n\t * Called once, immediately after the editor finishes initializing.\n\t * Any imperative ref methods called before this fires are queued internally\n\t * and executed automatically once the editor is ready — consumers do not\n\t * need timers or readiness guards.\n\t *\n\t * @example\n\t * <BikEditor\n\t * ref={editorRef}\n\t * onReady={() => editorRef.current?.setContent(savedDraft)}\n\t * />\n\t */\n\tonReady?: () => void;\n\t/** Called on every document change. */\n\tonChange?: (content: EditorSnapshot) => void;\n\t/**\n\t * Called when the user presses the key combo defined by `sendShortcut`.\n\t * Only fires when both `onSend` and `sendShortcut` are provided.\n\t */\n\tonSend?: (content: EditorSnapshot) => void;\n\t/** Called when the editor gains focus. */\n\tonFocus?: () => void;\n\t/** Called when the editor loses focus. */\n\tonBlur?: () => void;\n\t/**\n\t * Called on every paste event. Return `true` to fully suppress BikEditor's\n\t * own paste handling (useful when you want to handle images or HTML yourself).\n\t *\n\t * @example\n\t * onPaste={({ images }) => {\n\t * if (images.length) {\n\t * uploadImages(images).then(urls => {\n\t * urls.forEach(url => ref.current?.actions.insertHtml(`<img src=\"${url}\" />`));\n\t * });\n\t * return true; // suppress default — don't embed the raw image data\n\t * }\n\t * }}\n\t */\n\tonPaste?: (data: PasteData) => boolean | void;\n\t/**\n\t * Called on every cursor move or selection change with a snapshot of all\n\t * active marks. Keep in state and pass to your toolbar to sync button states.\n\t */\n\tonSelectionChange?: (activeFormats: FormatState) => void;\n\n\t// ── Styling ───────────────────────────────────────────────────────────────\n\t// Outer wrapper — use these to control the shell (border, border-radius,\n\t// background, shadow, etc.).\n\t/** Extra class name applied to the outer wrapper div. */\n\tclassName?: string;\n\t/** Inline styles applied to the outer wrapper div. */\n\tstyle?: React.CSSProperties;\n\n\t// Inner editable area — use these to control the content div itself\n\t// (padding, font, line-height, etc.).\n\t/** Extra class name applied to the content-editable area (`.ProseMirror` in TipTap). */\n\teditorClassName?: string;\n\t/** Inline styles applied to the content-editable area. */\n\teditorStyle?: React.CSSProperties;\n\n\t// Sizing — applied to the content-editable area.\n\t/** Minimum height of the editable area. CSS value, e.g. `'80px'`. */\n\tminHeight?: string;\n\t/** Maximum height. The editor scrolls internally when exceeded. CSS value, e.g. `'400px'`. */\n\tmaxHeight?: string;\n}\n"],"names":["DEFAULT_FORMAT_STATE"],"mappings":"gFAuVO,MAAMA,EAAoC,CAChD,KAAM,GACN,OAAQ,GACR,UAAW,GACX,OAAQ,GACR,WAAY,GACZ,YAAa,GACb,WAAY,GACZ,UAAW,GACX,KAAM,KACN,UAAW,KACX,WAAY,KACZ,SAAU,KACV,MAAO,KACP,UAAW,KACX,YAAa,GACb,UAAW,EACZ"}
|
|
1
|
+
{"version":3,"file":"BikEditor.types.js","sources":["../../../src/editor/BikEditor.types.ts"],"sourcesContent":["/**\n * ┌─────────────────────────────────────────────────────────────────────────┐\n * │ PUBLIC API LAYER — zero editor-framework imports │\n * │ │\n * │ This file is the consumer contract for BikEditor. It has NO dependency │\n * │ on TipTap, Lexical, Slate, or any other rich-text library. │\n * │ │\n * │ If the underlying editor is ever swapped, ONLY the implementation files │\n * │ change — this file, src/editor/index.ts, and all consumer code stay as- │\n * │ is. The files that would change are: │\n * │ • BikEditor.tsx — wires props into the editor instance │\n * │ • BikEditor.utils.ts — HTML / document-state adapters │\n * │ • extensions/ — extension / plugin layer │\n * └─────────────────────────────────────────────────────────────────────────┘\n */\nimport React from 'react';\n\n/**\n * Opt in to specific editor capabilities. Both default to `false`.\n *\n * @example\n * // Email editor with full rich text support\n * <BikEditor features={{ richPaste: true, richTypography: true }} />\n */\nexport interface EditorFeatures {\n\t/** Preserve HTML formatting on paste. Default: `false`. */\n\trichPaste?: boolean;\n\t/** Enable font family, font size, text alignment, highlight, subscript, superscript, and image. Default: `false`. */\n\trichTypography?: boolean;\n\t/**\n\t * Restrict which inline marks are active in the editor.\n\t * When set, only the listed marks are registered — all others are disabled\n\t * (no rendering, no keyboard shortcuts, no paste preservation).\n\t * Omit to allow all marks (default).\n\t *\n\t * @example\n\t * // LiveChat: bold only, no italic / underline / strike\n\t * features={{ allowedMarks: ['bold'] }}\n\t */\n\tallowedMarks?: Array<'bold' | 'italic' | 'strike' | 'underline' | 'code'>;\n\t/**\n\t * Turn the editor into a Postman-style JSON body editor: the whole document\n\t * becomes one syntax-highlighted JSON code block. Disables the rich-text stack\n\t * (mentions, typography, rich paste, sections). Drive it via `ref.json.*`.\n\t * Default: `false`.\n\t *\n\t * @example\n\t * <BikEditor features={{ jsonMode: true }} initialContent='{ \"id\": 1 }' />\n\t */\n\tjsonMode?: boolean;\n}\n\n/**\n * A named content section placed below the main body, separated by an invisible divider.\n *\n * Sections let you split a single editor instance into independently readable\n * and writable zones — for example: body / signature / forwarded mail.\n *\n * @example\n * sections={[\n * { id: 'signature', content: '<p>Best regards,<br/>Alice</p>' },\n * { id: 'forwarded', content: '<p>--- Forwarded message ---</p>' },\n * ]}\n */\nexport interface EditorSection {\n\t/**\n\t * Unique identifier. Used in ref methods: `getSectionContent(id)`,\n\t * `setSectionContent(id, html)`, `focusSection(id)`, `clearSection(id)`.\n\t * The string `'body'` is reserved for the main content above all dividers.\n\t */\n\tid: string;\n\t/** Initial HTML content of this section. */\n\tcontent: string;\n}\n\n/**\n * Data passed to the `onPaste` callback.\n * Return `true` from your handler to suppress BikEditor's default paste handling\n * (rich-paste and plain-text stripping won't run for that event).\n */\nexport interface PasteData {\n\t/** All files in the clipboard (images, PDFs, etc.). */\n\tfiles: File[];\n\t/** Image files only — a filtered subset of `files`. */\n\timages: File[];\n\t/** Plain text from the clipboard. */\n\ttext: string;\n\t/** HTML from the clipboard (empty string if the source doesn't provide HTML). */\n\thtml: string;\n\t/** The raw browser ClipboardEvent — for advanced use. */\n\tevent: ClipboardEvent;\n}\n\n/**\n * A custom keyboard shortcut registered with the editor.\n *\n * @example\n * shortcuts={[\n * // Mod = Cmd on Mac, Ctrl on Windows/Linux\n * { key: 'Enter', modifiers: ['mod'], handler: handleSend },\n * { key: 's', modifiers: ['mod'], handler: handleSave },\n * { key: 'k', modifiers: ['mod', 'shift'], handler: openLinkModal },\n * ]}\n */\nexport interface KeyboardShortcut {\n\t/** Key name. Use the same strings as KeyboardEvent.key, e.g. `'Enter'`, `'s'`, `'k'`. */\n\tkey: string;\n\t/**\n\t * Modifier keys.\n\t * `'mod'` maps to Cmd on macOS and Ctrl on Windows/Linux — prefer it over\n\t * `'ctrl'` for cross-platform shortcuts.\n\t */\n\tmodifiers?: Array<'mod' | 'ctrl' | 'shift' | 'alt'>;\n\t/** Called when the shortcut fires. Returning nothing is fine. */\n\thandler: () => void;\n}\n\n/** Content object returned by onChange, onSend, getContent(), and getSectionContent(). */\nexport interface EditorSnapshot {\n\t/** Serialised HTML. Safe to pass back to setContent() or setSectionContent(). */\n\thtml: string;\n\t/** Plain text, stripped of all marks and block nodes. */\n\ttext: string;\n\t/** True when the document contains no user-visible content. */\n\tisEmpty: boolean;\n\t/** Character count at this point in time. */\n\tcharacterCount: number;\n}\n\n/**\n * Result of parsing the JSON body (features.jsonMode). Returned by `ref.json.parse()`.\n * Empty / whitespace-only content is treated as valid with a `null` value.\n */\nexport interface JsonParseResult {\n\t/** True when the current text is valid JSON. */\n\tvalid: boolean;\n\t/** The parsed value (`null` when empty or invalid). */\n\tvalue: unknown;\n\t/**\n\t * Human-readable error message, cleaned of the raw parser noise\n\t * (e.g. `\"Bad control character in string literal\"` instead of\n\t * `\"… in JSON at position 94 (line 7 column 9)\"`). `null` when valid.\n\t */\n\terror: string | null;\n\t/** 1-based line of the error, or `null` when valid / not locatable. */\n\tline: number | null;\n\t/** 1-based column of the error, or `null` when valid / not locatable. */\n\tcolumn: number | null;\n}\n\n/**\n * JSON body controls, available on the ref when `features.jsonMode` is on.\n * Access via `editorRef.current.json`.\n *\n * @example\n * editorRef.current?.json?.format(); // pretty-print (2-space indent)\n * const { valid, value } = editorRef.current!.json!.parse();\n */\nexport interface JsonBodyApi {\n\t/** Current raw text of the JSON body. */\n\tgetText: () => string;\n\t/** Replace the whole JSON body with `raw` and fire onChange. */\n\tsetText: (raw: string) => void;\n\t/** Parse the current text without throwing — see JsonParseResult. */\n\tparse: () => JsonParseResult;\n\t/** True when the current text is valid JSON. */\n\tisValid: () => boolean;\n\t/**\n\t * Pretty-print the current text in place. No-op when the text is invalid.\n\t * @param indent Number of spaces per level. Default `2`.\n\t */\n\tformat: (indent?: number) => void;\n\t/** Collapse the current text to a single line. No-op when invalid. */\n\tminify: () => void;\n}\n\n/** One item shown in the @agent or #team mention dropdown. */\nexport interface MentionItem {\n\t/** Unique identifier stored as a node attribute. Used to look up the item later. */\n\tid: string | number;\n\t/** Display name shown in the dropdown and inserted into the document. */\n\tlabel: string;\n\t/** Avatar image URL. Shown in the built-in row when renderMentionItem is not provided. */\n\tavatarUrl?: string;\n\t/** When true, a green presence dot appears in the built-in row. */\n\tisOnline?: boolean;\n\t/** Any extra data your app needs. Passed back as-is in onMentionSelected. */\n\tmeta?: Record<string, unknown>;\n}\n\n/** One item shown in the / slash-command dropdown. */\nexport interface SlashCommandItem {\n\t/** Unique identifier. */\n\tid: string;\n\t/** Short command name shown as the primary label in the menu. */\n\tlabel: string;\n\t/** Optional one-line description shown below the label in the built-in row. */\n\tdescription?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Feature config objects — pass one object per feature instead of N flat props\n// ---------------------------------------------------------------------------\n\n/**\n * Props passed to the `renderDropdown` function in `MentionConfig`.\n * `activeIndex` is the keyboard-navigated row — highlight it in your UI.\n * Call `onSelect(item)` when the user picks one.\n */\nexport interface MentionDropdownRenderProps {\n\titems: MentionItem[];\n\t/** The text the user typed after the `@` or `#` trigger. */\n\tquery: string;\n\t/**\n\t * Index of the currently highlighted row (arrow-key navigation).\n\t * The editor manages this automatically — just use it for visual highlighting.\n\t */\n\tactiveIndex: number;\n\t/** Call this when the user selects an item (click, touch, etc.). */\n\tonSelect: (item: MentionItem) => void;\n\t/**\n\t * Register a keyboard handler so a CUSTOM dropdown can own arrow-key / Enter\n\t * navigation (e.g. a multi-level picker whose visible rows differ from the\n\t * flat `items` list). Return `true` from the handler to consume the key.\n\t * Pass `null` to unregister. When not registered, the editor falls back to\n\t * navigating the flat `items` list.\n\t */\n\tregisterKeyHandler?: (\n\t\thandler: ((event: KeyboardEvent) => boolean) | null,\n\t) => void;\n\t/**\n\t * Dismiss the dropdown WITHOUT selecting an item: closes the menu and strips\n\t * the typed `@`/`#` trigger + query. Use for custom actions in a multi-level\n\t * picker (e.g. a \"Create new\" row that opens a drawer instead of inserting a\n\t * mention).\n\t */\n\tonClose?: () => void;\n}\n\n/**\n * Everything related to @ and # mention dropdowns, in one place.\n *\n * @example\n * mentions={{\n * agents: [{ id: 1, label: 'Alice', avatarUrl: '...' }],\n * teams: [{ id: 't1', label: 'Support' }],\n * onSelect: (item, char) => console.log(char, item),\n * }}\n */\nexport interface MentionConfig {\n\t/** Items shown when the user types `@`. */\n\tagents?: MentionItem[];\n\t/** Items shown when the user types `#`. */\n\tteams?: MentionItem[];\n\t/** Called when the user picks an item. `char` is `'@'` or `'#'`. */\n\tonSelect?: (item: MentionItem, char: '@' | '#') => void;\n\t/**\n\t * Custom renderer for each row in the dropdown.\n\t * `isActive` is `true` when that row is currently highlighted by keyboard\n\t * navigation (arrows move it, Enter selects it). Use it to apply a highlight\n\t * style so the user can see which item they're about to select.\n\t * Omit to use the built-in avatar + name row.\n\t *\n\t * Use `renderDropdown` instead when you need a completely different structure.\n\t *\n\t * @example\n\t * renderItem={(item, isActive) => (\n\t * <div style={{ background: isActive ? '#f0f0f0' : undefined }}>\n\t * {item.label}\n\t * </div>\n\t * )}\n\t */\n\trenderItem?: (item: MentionItem, isActive: boolean) => React.ReactNode;\n\t/**\n\t * Render the entire dropdown yourself instead of individual rows.\n\t * The editor still manages keyboard navigation — `activeIndex` tells you\n\t * which row is highlighted, and `onSelect(item)` confirms the selection.\n\t * Takes priority over `renderItem` when both are provided.\n\t *\n\t * @example\n\t * renderDropdown={({ items, query, activeIndex, onSelect }) => (\n\t * <MyDropdown\n\t * items={items}\n\t * highlightedIndex={activeIndex}\n\t * onPick={onSelect}\n\t * />\n\t * )}\n\t */\n\trenderDropdown?: (props: MentionDropdownRenderProps) => React.ReactNode;\n\t/**\n\t * When true, a typed trigger char (`@` / `#`) and its query are removed if the\n\t * suggestion menu closes without a selection (click-outside / no match). Default\n\t * false — the typed text is kept.\n\t */\n\tremoveTriggerOnDismiss?: boolean;\n}\n\n/**\n * Props passed to the `renderDropdown` function in `SlashCommandConfig`.\n */\nexport interface SlashCommandDropdownRenderProps {\n\titems: SlashCommandItem[];\n\t/** The text the user typed after `/`. */\n\tquery: string;\n\t/** Index of the currently highlighted row. Use for visual highlighting. */\n\tactiveIndex: number;\n\t/** Call this when the user selects an item. */\n\tonSelect: (item: SlashCommandItem) => void;\n}\n\n/**\n * Everything related to the `/` slash-command menu, in one place.\n *\n * @example\n * slashCommands={{\n * items: [{ id: 'ai-reply', label: 'AI Reply', description: 'Generate a reply' }],\n * onSelect: (cmd) => triggerAI(cmd.id),\n * }}\n */\nexport interface SlashCommandConfig {\n\t/** The list of commands. Filtered client-side as the user types after `/`. */\n\titems: SlashCommandItem[];\n\t/** Called when the user selects a command. The `/` trigger is removed from the doc. */\n\tonSelect?: (command: SlashCommandItem) => void;\n\t/**\n\t * Custom renderer for each row.\n\t * `isActive` is `true` when that row is highlighted by keyboard navigation.\n\t * Omit to use the built-in label + description row.\n\t *\n\t * Use `renderDropdown` instead when you need a completely different structure.\n\t */\n\trenderItem?: (item: SlashCommandItem, isActive: boolean) => React.ReactNode;\n\t/**\n\t * Render the entire slash-command dropdown yourself.\n\t * The editor manages keyboard navigation — use `activeIndex` to highlight\n\t * the active row and call `onSelect(item)` to confirm selection.\n\t * Takes priority over `renderItem` when both are provided.\n\t *\n\t * @example\n\t * renderDropdown={({ items, activeIndex, onSelect }) => (\n\t * <MyCmdPalette commands={items} active={activeIndex} onPick={onSelect} />\n\t * )}\n\t */\n\trenderDropdown?: (props: SlashCommandDropdownRenderProps) => React.ReactNode;\n}\n\n/**\n * Configuration for how hyperlinks behave in the editor.\n *\n * @example\n * link={{ renderTooltip: ({ href, text, onOpen, onRemove }) => (\n * <MyLinkPopover href={href} onOpen={onOpen} onRemove={onRemove} />\n * )}}\n */\nexport interface LinkConfig {\n\t/**\n\t * Custom tooltip rendered when the user clicks a link.\n\t * Receives the href, display text, bounding rect (for positioning),\n\t * and ready-to-call action handlers.\n\t * Omit to use the minimal built-in tooltip.\n\t */\n\trenderTooltip?: (props: LinkTooltipProps) => React.ReactNode;\n}\n\n/**\n * Snapshot of all active marks and block types at the current cursor or selection.\n * Returned by onSelectionChange and ref.getActiveFormats().\n *\n * Keep this in component state and pass it to BikEditorToolbar (or your own toolbar)\n * to keep button active-states in sync.\n *\n * @example\n * const [formats, setFormats] = useState(DEFAULT_ACTIVE_FORMATS);\n * <BikEditor onSelectionChange={setFormats} ... />\n * <BikEditorToolbar activeFormats={formats} ... />\n */\nexport interface FormatState {\n\tbold: boolean;\n\titalic: boolean;\n\tunderline: boolean;\n\tstrike: boolean;\n\tbulletList: boolean;\n\torderedList: boolean;\n\tblockquote: boolean;\n\tcodeBlock: boolean;\n\t/** Non-null when the cursor is inside (or selection spans) a hyperlink. */\n\tlink: { href: string } | null;\n\ttextAlign: 'left' | 'center' | 'right' | 'justify' | null;\n\tfontFamily: string | null;\n\tfontSize: string | null;\n\t/** Active text colour as a CSS colour string, or null if none. */\n\tcolor: string | null;\n\t/** Active highlight (background) colour, or null if none. */\n\thighlight: string | null;\n\t/** True when the cursor/selection is in a superscript. */\n\tsuperscript: boolean;\n\t/** True when the cursor/selection is in a subscript. */\n\tsubscript: boolean;\n}\n\nexport const DEFAULT_FORMAT_STATE: FormatState = {\n\tbold: false,\n\titalic: false,\n\tunderline: false,\n\tstrike: false,\n\tbulletList: false,\n\torderedList: false,\n\tblockquote: false,\n\tcodeBlock: false,\n\tlink: null,\n\ttextAlign: null,\n\tfontFamily: null,\n\tfontSize: null,\n\tcolor: null,\n\thighlight: null,\n\tsuperscript: false,\n\tsubscript: false,\n};\n\n/**\n * All formatting and insertion commands. Access via `editorRef.current.actions`.\n *\n * Every method focuses the editor internally — no need to call `.focus()` first.\n *\n * @example\n * editorRef.current?.actions.toggleBold();\n * editorRef.current?.actions.setLink('https://example.com', 'Visit example');\n */\nexport interface EditorActions {\n\t// ── Basic marks ───────────────────────────────────────────────────────────\n\ttoggleBold: () => void;\n\ttoggleItalic: () => void;\n\ttoggleUnderline: () => void;\n\ttoggleStrike: () => void;\n\n\t// ── Blocks ────────────────────────────────────────────────────────────────\n\ttoggleBulletList: () => void;\n\ttoggleOrderedList: () => void;\n\ttoggleBlockquote: () => void;\n\ttoggleCodeBlock: () => void;\n\n\t// ── Alignment & typography ────────────────────────────────────────────────\n\tsetTextAlign: (align: 'left' | 'center' | 'right' | 'justify') => void;\n\t/** Pass a CSS font-family value, e.g. `'Inter, sans-serif'`. Only works when richTypography is on. */\n\tsetFontFamily: (font: string) => void;\n\t/** Pass a CSS size value, e.g. `'16px'` or `'1.25rem'`. Only works when richTypography is on. */\n\tsetFontSize: (size: string) => void;\n\n\t// ── Colour ────────────────────────────────────────────────────────────────\n\t/** Sets the text colour. Pass any CSS colour value, e.g. `'#e53e3e'`. */\n\tsetColor: (color: string) => void;\n\t/** Sets the highlight (background) colour. */\n\tsetHighlight: (color: string) => void;\n\t/** Removes the text colour mark from the current selection. */\n\tunsetColor: () => void;\n\t/** Removes the highlight mark from the current selection. */\n\tunsetHighlight: () => void;\n\n\t// ── Links ─────────────────────────────────────────────────────────────────\n\t/**\n\t * Applies a hyperlink to the current selection, or updates the link the\n\t * cursor is already inside.\n\t *\n\t * @param href The URL.\n\t * @param text Optional display text. When provided, the visible text of the\n\t * linked range is replaced atomically in the same undo step.\n\t *\n\t * @example\n\t * actions.setLink('https://example.com');\n\t * actions.setLink('https://example.com', 'Visit example');\n\t */\n\tsetLink: (href: string, text?: string) => void;\n\t/** Updates only the href of the link at the current cursor. */\n\tupdateLink: (href: string) => void;\n\t/** Removes the link mark but keeps the visible text. */\n\tremoveLink: () => void;\n\n\t// ── Insertions ────────────────────────────────────────────────────────────\n\t/** Inserts an emoji character at the cursor. */\n\tinsertEmoji: (emoji: string) => void;\n\t/**\n\t * Inserts a `{{variableName}}` token at the cursor.\n\t * The token renders with a distinct highlight inside the editor.\n\t */\n\tinsertVariable: (variableName: string) => void;\n\t/** Inserts arbitrary HTML at the current cursor position. */\n\tinsertHtml: (html: string) => void;\n\n\t// ── History ───────────────────────────────────────────────────────────────\n\tundo: () => void;\n\tredo: () => void;\n\tcanUndo: () => boolean;\n\tcanRedo: () => boolean;\n\n\t// ── Rich typography (richTypography feature required) ─────────────────────\n\t/** Toggles superscript on the selection. Only works when richTypography is on. */\n\ttoggleSuperscript: () => void;\n\t/** Toggles subscript on the selection. Only works when richTypography is on. */\n\ttoggleSubscript: () => void;\n\t/** Inserts an inline image at the cursor. Only works when richTypography is on. */\n\tinsertImage: (src: string) => void;\n}\n\n/** Props passed to your renderLinkTooltip render prop. */\nexport interface LinkTooltipProps {\n\t/** The href of the active link. */\n\thref: string;\n\t/** The display text of the link. */\n\ttext: string;\n\t/** Bounding rect of the link element — use this to position your popover. */\n\trect: DOMRect;\n\t/** Opens the link in a new tab. */\n\tonOpen: () => void;\n\t/** Removes the link mark but keeps the visible text. */\n\tonRemove: () => void;\n\t/** Hides the bubble menu. Call this before opening an edit modal so the tooltip doesn't linger. */\n\tonHide: () => void;\n}\n\n/**\n * Imperative handle exposed by BikEditor.\n *\n * Obtain via `useRef<BikEditorRef>()` and pass to `<BikEditor ref={...} />`.\n * All methods are safe to call after the first render.\n *\n * @example\n * const ref = useRef<BikEditorRef>(null);\n * <BikEditor ref={ref} ... />\n * ref.current?.clearContent();\n * ref.current?.actions.toggleBold();\n */\nexport interface BikEditorRef {\n\t// ── Focus ─────────────────────────────────────────────────────────────────\n\t/** Moves focus into the editor. Optionally pass a position: 'start', 'end', 'all', or a numeric offset. */\n\tfocus: (position?: 'start' | 'end' | 'all' | number | boolean | null) => void;\n\t/** Removes focus from the editor. */\n\tblur: () => void;\n\n\t// ── Content ───────────────────────────────────────────────────────────────\n\t/** Clears the entire document and fires onChange. */\n\tclearContent: () => void;\n\t/**\n\t * Replaces the full document with the given HTML and fires onChange.\n\t * Overwrites all sections. Use setSectionContent() / setBodyContent() to\n\t * update a single section without touching the others.\n\t */\n\tsetContent: (html: string) => void;\n\t/** Inserts HTML at the current cursor position without replacing anything. */\n\tinsertContent: (html: string) => void;\n\t/**\n\t * Inserts HTML inline at the current cursor position, unwrapping block\n\t * wrappers (e.g. `<p>`) so the content merges into the surrounding paragraph\n\t * instead of creating new blocks. Use for macros, quick replies, etc.\n\t */\n\tinsertInlineContent: (html: string) => void;\n\t/** Inserts HTML at the very start of the document. */\n\tinsertAtStart: (html: string) => void;\n\t/** Inserts HTML as a new block at the end of the document. */\n\tinsertBlock: (html: string) => void;\n\t/**\n\t * Appends HTML inline inside the last paragraph of the document.\n\t * Ideal for streaming — call repeatedly with each text chunk.\n\t * Use `appendBodyContent` instead if the editor has named sections.\n\t */\n\tappendInline: (html: string) => void;\n\t/** @deprecated Use `insertBlock` */\n\tinsertAtEnd: (html: string) => void;\n\t/** @deprecated Use `appendInline` */\n\tappendContent: (html: string) => void;\n\t/** Returns the full document as `{ html, text, isEmpty, characterCount }`. */\n\tgetContent: () => EditorSnapshot;\n\t/**\n\t * Returns the raw TipTap document as a plain JSON object.\n\t * Returns `null` when the editor is not yet mounted.\n\t */\n\tgetJSON: () => Record<string, unknown> | null;\n\t/**\n\t * Walks the document and returns all mention IDs currently in the editor,\n\t * split by type. IDs are deduplicated and returned as arrays.\n\t *\n\t * @example\n\t * const { agentIds, teamIds } = ref.current?.getMentions() ?? { agentIds: [], teamIds: [] };\n\t */\n\tgetMentions: () => { agentIds: string[]; teamIds: string[] };\n\n\t// ── Selection ─────────────────────────────────────────────────────────────\n\t/**\n\t * Returns the current cursor / selection position as a `{ from, to }` pair.\n\t * When nothing is selected, `from === to` (a collapsed cursor).\n\t * Pass the returned value to `insertAtPosition()` to insert at an exact spot.\n\t *\n\t * @example\n\t * const { from } = ref.current?.getCursorPosition() ?? { from: 0, to: 0 };\n\t * ref.current?.insertAtPosition(from, '<strong>inserted</strong>');\n\t */\n\tgetCursorPosition: () => { from: number; to: number };\n\t/**\n\t * Inserts HTML at an explicit document position rather than at the current cursor.\n\t * Use `getCursorPosition()` to get the position, or any value from\n\t * `findSectionStartPos` / `findSectionEndPos` if you need section-relative insertion.\n\t *\n\t * @example\n\t * const { from } = ref.current!.getCursorPosition();\n\t * ref.current?.insertAtPosition(from, '<em>hello</em>');\n\t */\n\tinsertAtPosition: (pos: number, html: string) => void;\n\t/**\n\t * Returns the currently selected plain text.\n\t * When the cursor is inside a link with nothing selected, returns the full\n\t * link text — useful for pre-filling a link modal's text field.\n\t */\n\tgetSelectedText: () => string;\n\n\t// ── Character count ───────────────────────────────────────────────────────\n\t/**\n\t * Returns the current character count and the configured limit (or null).\n\t * Use this to render your own character counter anywhere you like.\n\t *\n\t * @example\n\t * const { count, limit } = ref.current?.getCharacterCount() ?? { count: 0, limit: null };\n\t * <span style={{ color: count >= (limit ?? Infinity) ? 'red' : 'inherit' }}>\n\t * {count}{limit != null ? ` / ${limit}` : ''}\n\t * </span>\n\t */\n\tgetCharacterCount: () => { count: number; limit: number | null };\n\n\t// ── Sections ─────────────────────────────────────────────────────────────\n\t/**\n\t * Returns content for a named section.\n\t * Use `'body'` to get the main content above all section dividers.\n\t *\n\t * @example\n\t * const sig = ref.current?.getSectionContent('signature');\n\t */\n\tgetSectionContent: (sectionId: string) => EditorSnapshot;\n\t/**\n\t * Replaces the content of a named section without touching other sections.\n\t * If the section doesn't exist yet it is appended at the end.\n\t *\n\t * @example\n\t * ref.current?.setSectionContent('signature', '<p>New signature</p>');\n\t * ref.current?.setSectionContent('body', rephrased); // same as setBodyContent\n\t */\n\tsetSectionContent: (sectionId: string, html: string) => void;\n\t/**\n\t * Moves the cursor to the start of a named section.\n\t * Use `'body'` to focus the main content area.\n\t */\n\tfocusSection: (sectionId: string) => void;\n\t/** Clears all content in a named section (replaces with an empty paragraph). */\n\tclearSection: (sectionId: string) => void;\n\n\t// ── Convenience body shorthands ───────────────────────────────────────────\n\t/** Shorthand for `getSectionContent('body')`. */\n\tgetBodyContent: () => EditorSnapshot;\n\t/** Shorthand for `setSectionContent('body', html)`. */\n\tsetBodyContent: (html: string) => void;\n\t/**\n\t * Atomically sets the body AND all sections in a single editor update.\n\t * Sections are laid out in the exact order supplied — use this instead of\n\t * multiple `setSectionContent()` calls whenever order matters (e.g. forward\n\t * mail where signature must appear before the forwarded thread).\n\t *\n\t * @example\n\t * ref.current?.setBodyAndSections('<p></p>', [\n\t * { id: 'signature', content: '<p>Alice</p>' },\n\t * { id: 'forwarded', content: '<p>--- Forwarded ---</p>' },\n\t * ]);\n\t */\n\tsetBodyAndSections: (\n\t\tbody: string,\n\t\tsections: Array<{ id: string; content: string }>,\n\t) => void;\n\t/**\n\t * Appends HTML to the end of the body section without moving the cursor.\n\t * Ideal for streaming AI-generated content — call repeatedly with each chunk.\n\t * Does NOT clear existing content; call `setBodyContent('')` first if needed.\n\t *\n\t * @example\n\t * // Stream AI response chunk-by-chunk\n\t * ref.current?.setBodyContent('');\n\t * for await (const chunk of aiStream) {\n\t * ref.current?.appendBodyContent(chunk);\n\t * }\n\t */\n\tappendBodyContent: (html: string) => void;\n\n\t/**\n\t * Inserts HTML at the start of a named section (or `'body'`).\n\t * Inserts at the very start of the document when `sectionId` is `'body'`.\n\t */\n\tinsertAtSectionStart: (sectionId: string, html: string) => void;\n\t/**\n\t * Inserts HTML at the end of a named section (or `'body'`).\n\t * For `'body'`, inserts just before the first section divider (or at end of doc).\n\t */\n\tinsertAtSectionEnd: (sectionId: string, html: string) => void;\n\n\t// ── Formats & actions ─────────────────────────────────────────────────────\n\t/** Returns a snapshot of all active marks at the current cursor or selection. */\n\tgetActiveFormats: () => FormatState;\n\t/** All formatting and insertion commands. See EditorActions. */\n\tactions: EditorActions;\n\n\t// ── JSON body (features.jsonMode required) ─────────────────────────────────\n\t/**\n\t * JSON body controls. Present only when `features.jsonMode` is on\n\t * (`undefined` otherwise). See JsonBodyApi.\n\t *\n\t * @example\n\t * editorRef.current?.json?.format();\n\t */\n\tjson?: JsonBodyApi;\n}\n\n/**\n * Props for `<BikEditor />`. All props are optional.\n */\nexport interface BikEditorProps {\n\t// ── Features ──────────────────────────────────────────────────────────────\n\t/**\n\t * Opt in to specific editor capabilities. Both default to `false`.\n\t *\n\t * @example\n\t * <BikEditor features={{ richPaste: true, richTypography: true }} />\n\t */\n\tfeatures?: EditorFeatures;\n\n\t// ── Content ───────────────────────────────────────────────────────────────\n\t/**\n\t * HTML loaded on mount. Ignored after first render.\n\t * Use `ref.setContent()` to change content imperatively later.\n\t */\n\tinitialContent?: string;\n\t/**\n\t * Named sections placed below the main body.\n\t * Each section is separated from the previous one by an invisible divider node.\n\t * The array order determines the visual order in the document.\n\t *\n\t * These are only the INITIAL values — use `ref.setSectionContent()` to update\n\t * a section after mount without re-mounting the editor.\n\t *\n\t * @example\n\t * sections={[\n\t * { id: 'signature', content: '<p>Best regards, Alice</p>' },\n\t * { id: 'forwarded', content: '<p>--- Forwarded message ---</p>' },\n\t * ]}\n\t */\n\tsections?: EditorSection[];\n\t/** Placeholder text shown when the editor is empty. */\n\tplaceholder?: string;\n\n\t// ── Behaviour ─────────────────────────────────────────────────────────────\n\t/** Makes the editor read-only. The toolbar still renders but actions are no-ops. */\n\tdisabled?: boolean;\n\t/**\n\t * Hard character limit. The editor refuses to accept input beyond this point.\n\t * The limit is enforced silently — no UI is rendered. Use\n\t * `ref.getCharacterCount()` or `onChange.characterCount` to display it yourself.\n\t */\n\tmaxCharacters?: number;\n\n\t// ── Keyboard shortcuts ────────────────────────────────────────────────────\n\t/**\n\t * Custom keyboard shortcuts. BikEditor registers no shortcuts of its own —\n\t * every shortcut must be explicitly listed here.\n\t *\n\t * @example\n\t * shortcuts={[\n\t * { key: 'Enter', modifiers: ['mod'], handler: handleSend },\n\t * { key: 's', modifiers: ['mod', 'shift'], handler: handleDraft },\n\t * ]}\n\t */\n\tshortcuts?: KeyboardShortcut[];\n\t/**\n\t * Shortcut key that fires `onSend`. No key is bound to `onSend` unless you\n\t * set this explicitly. Requires `onSend` to also be provided.\n\t *\n\t * `'mod'` maps to Cmd on macOS and Ctrl on Windows/Linux.\n\t *\n\t * @example\n\t * // Single shortcut\n\t * sendShortcut={{ key: 'Enter', modifiers: ['mod'] }}\n\t * // Multiple shortcuts\n\t * sendShortcut={[\n\t * { key: 'Enter', modifiers: ['mod'] },\n\t * { key: 's', modifiers: ['mod'] },\n\t * ]}\n\t */\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\n\t// ── Mentions ─────────────────────────────────────────────────────────────\n\t/**\n\t * Enables `@` (agent) and `#` (team) mention dropdowns.\n\t * Bundle all mention-related config in one object so it's easy to find.\n\t *\n\t * @example\n\t * mentions={{\n\t * agents: myAgents,\n\t * teams: myTeams,\n\t * onSelect: (item, char) => track(char, item.id),\n\t * }}\n\t */\n\tmentions?: MentionConfig;\n\n\t// ── Slash commands ────────────────────────────────────────────────────────\n\t/**\n\t * Enables the `/` slash-command menu.\n\t * Bundle all slash-command config in one object.\n\t *\n\t * @example\n\t * slashCommands={{\n\t * items: MY_COMMANDS,\n\t * onSelect: (cmd) => handleCommand(cmd.id),\n\t * }}\n\t */\n\tslashCommands?: SlashCommandConfig;\n\n\t// ── Link ─────────────────────────────────────────────────────────────────\n\t/**\n\t * Link-related configuration — currently just the click tooltip.\n\t *\n\t * @example\n\t * link={{ renderTooltip: ({ href, onOpen, onRemove }) => (\n\t * <MyTooltip href={href} onOpen={onOpen} onRemove={onRemove} />\n\t * )}}\n\t */\n\tlink?: LinkConfig;\n\n\t// ── JSON body (features.jsonMode) ──────────────────────────────────────────\n\t/**\n\t * Custom renderer for the JSON parse error shown below the editor in\n\t * `features.jsonMode`. Receives the current {@link JsonParseResult} (always\n\t * an invalid one — it's only called when the JSON is broken). Omit to use the\n\t * built-in inline error banner; the error is never rendered when the JSON is\n\t * valid or when `jsonMode` is off.\n\t *\n\t * @example\n\t * renderJsonError={(err) => (\n\t * <MyBanner>Line {err.line}: {err.error}</MyBanner>\n\t * )}\n\t */\n\trenderJsonError?: (error: JsonParseResult) => React.ReactNode;\n\n\t// ── Callbacks ─────────────────────────────────────────────────────────────\n\t/**\n\t * Called once, immediately after the editor finishes initializing.\n\t * Any imperative ref methods called before this fires are queued internally\n\t * and executed automatically once the editor is ready — consumers do not\n\t * need timers or readiness guards.\n\t *\n\t * @example\n\t * <BikEditor\n\t * ref={editorRef}\n\t * onReady={() => editorRef.current?.setContent(savedDraft)}\n\t * />\n\t */\n\tonReady?: () => void;\n\t/** Called on every document change. */\n\tonChange?: (content: EditorSnapshot) => void;\n\t/**\n\t * Called when the user presses the key combo defined by `sendShortcut`.\n\t * Only fires when both `onSend` and `sendShortcut` are provided.\n\t */\n\tonSend?: (content: EditorSnapshot) => void;\n\t/** Called when the editor gains focus. */\n\tonFocus?: () => void;\n\t/** Called when the editor loses focus. */\n\tonBlur?: () => void;\n\t/**\n\t * Called on every paste event. Return `true` to fully suppress BikEditor's\n\t * own paste handling (useful when you want to handle images or HTML yourself).\n\t *\n\t * @example\n\t * onPaste={({ images }) => {\n\t * if (images.length) {\n\t * uploadImages(images).then(urls => {\n\t * urls.forEach(url => ref.current?.actions.insertHtml(`<img src=\"${url}\" />`));\n\t * });\n\t * return true; // suppress default — don't embed the raw image data\n\t * }\n\t * }}\n\t */\n\tonPaste?: (data: PasteData) => boolean | void;\n\t/**\n\t * Called on every cursor move or selection change with a snapshot of all\n\t * active marks. Keep in state and pass to your toolbar to sync button states.\n\t */\n\tonSelectionChange?: (activeFormats: FormatState) => void;\n\n\t// ── Styling ───────────────────────────────────────────────────────────────\n\t// Outer wrapper — use these to control the shell (border, border-radius,\n\t// background, shadow, etc.).\n\t/** Extra class name applied to the outer wrapper div. */\n\tclassName?: string;\n\t/** Inline styles applied to the outer wrapper div. */\n\tstyle?: React.CSSProperties;\n\n\t// Inner editable area — use these to control the content div itself\n\t// (padding, font, line-height, etc.).\n\t/** Extra class name applied to the content-editable area (`.ProseMirror` in TipTap). */\n\teditorClassName?: string;\n\t/** Inline styles applied to the content-editable area. */\n\teditorStyle?: React.CSSProperties;\n\n\t// Sizing — applied to the content-editable area.\n\t/** Minimum height of the editable area. CSS value, e.g. `'80px'`. */\n\tminHeight?: string;\n\t/** Maximum height. The editor scrolls internally when exceeded. CSS value, e.g. `'400px'`. */\n\tmaxHeight?: string;\n}\n"],"names":["DEFAULT_FORMAT_STATE"],"mappings":"gFAgZO,MAAMA,EAAoC,CAChD,KAAM,GACN,OAAQ,GACR,UAAW,GACX,OAAQ,GACR,WAAY,GACZ,YAAa,GACb,WAAY,GACZ,UAAW,GACX,KAAM,KACN,UAAW,KACX,WAAY,KACZ,SAAU,KACV,MAAO,KACP,UAAW,KACX,YAAa,GACb,UAAW,EACZ"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("../node_modules/prosemirror-model/dist/index.js"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("../node_modules/prosemirror-model/dist/index.js"),x=require("./extensions/plainClipboard/pasteUtils.js");function h(e){if(!e||typeof document>"u")return e;const t=document.createElement("div");return t.innerHTML=e,t.querySelectorAll("span").forEach(i=>{const n=i.getAttribute("data-type");if(i.hasAttribute("style")||i.hasAttribute("data-variable")||n!==null&&n.startsWith("mention"))return;const r=i.parentNode;if(r){for(;i.firstChild;)r.insertBefore(i.firstChild,i);r.removeChild(i)}}),t.querySelectorAll("p").forEach(i=>{i.querySelectorAll("br").length===1&&(i.textContent??"").trim()===""&&(i.innerHTML="")}),t.innerHTML}function u(e){const t=document.createElement("div");t.innerHTML=e;const i=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);let n;for(;n=i.nextNode();)n.nodeValue&&n.nodeValue.includes(" ")&&(n.nodeValue=n.nodeValue.replace(/ {2}/g," "));const r=t.querySelectorAll("p");for(const o of Array.from(r)){if(o.childNodes.length===0){o.appendChild(document.createElement("br"));continue}o.lastChild instanceof HTMLBRElement&&o.childNodes.length>1&&o.appendChild(document.createElement("br"))}return t.innerHTML}function S(e,t){const i=h(t),n=document.createElement("div");n.innerHTML=i;const r=a.DOMParser.fromSchema(e.schema).parseSlice(n,{preserveWhitespace:!0});if(r.size===0)return;const{from:o,to:c}=e.state.selection,s=e.state.tr.replaceRange(o,c,r);e.view.dispatch(s)}const d=e=>`<div data-section-divider="${e}" style="display:none;height:0;line-height:0;overflow:hidden;"></div>`;function A(e,t){let i=e;for(const n of t)i+=d(n.id)+n.content;return i}function p(e){return e?g(e).get("body")??e:""}function C(e){if(!e)return"";const t=p(e);return e.substring(t.length)}function g(e){const t=new Map,i=/<div[^>]*data-section-divider="([^"]*)[^>]*>\s*<\/div>/g,n=[];let r;for(;(r=i.exec(e))!==null;)n.push({id:r[1],index:r.index,endIndex:r.index+r[0].length});if(n.length===0)return t.set("body",e),t;t.set("body",e.slice(0,n[0].index));for(let o=0;o<n.length;o++){const c=n[o].endIndex,s=o+1<n.length?n[o+1].index:e.length;t.set(n[o].id,e.slice(c,s))}return t}function v(e,t){const i=f(e,t);if(i===-1)return{html:"",text:"",isEmpty:!0,characterCount:0};const n=m(e,t),r=e.state.doc.slice(i,n),o=document.createElement("div"),c=a.DOMSerializer.fromSchema(e.schema);o.appendChild(c.serializeFragment(r.content));const s=u(o.innerHTML),l=o.textContent??"";return{html:s,text:l,isEmpty:!l.trim(),characterCount:l.length}}function H(e,t){const i=document.createElement("div");return i.innerHTML=t,a.DOMParser.fromSchema(e.schema).parse(i,{preserveWhitespace:!0}).content}function k(e,t,i){const n=f(e,t);if(n===-1){const l=e.state.doc.content.size,y=d(t)+i;e.commands.insertContentAt(l,y,{updateSelection:!1});return}const r=m(e,t),o=H(e,i),{tr:c}=e.state,s=t==="body"?0:n;c.replaceWith(s,r,o),c.setMeta("addToHistory",!1),e.view.dispatch(c)}function T(e){return v(e,"body")}function E(e){return{bold:e.isActive("bold"),italic:e.isActive("italic"),underline:e.isActive("underline"),strike:e.isActive("strike"),bulletList:e.isActive("bulletList"),orderedList:e.isActive("orderedList"),blockquote:e.isActive("blockquote"),codeBlock:e.isActive("codeBlock"),link:(()=>{if(e.isActive("link"))return{href:e.getAttributes("link").href??""};const{$from:t}=e.state.selection;if(t.pos>0){const n=t.doc.resolve(t.pos).marks().find(r=>r.type.name==="link");if(n)return{href:n.attrs.href??""};{const o=t.nodeBefore?.marks.find(c=>c.type.name==="link");if(o)return{href:o.attrs.href??""}}}return null})(),textAlign:e.isActive({textAlign:"center"})?"center":e.isActive({textAlign:"right"})?"right":e.isActive({textAlign:"justify"})?"justify":e.isActive({textAlign:"left"})?"left":null,fontFamily:e.getAttributes("textStyle").fontFamily??null,fontSize:e.getAttributes("textStyle").fontSize??null,color:e.getAttributes("textStyle").color??null,highlight:e.isActive("highlight")?e.getAttributes("highlight").color??"default":null,superscript:e.isActive("superscript"),subscript:e.isActive("subscript")}}function B(e){const t=u(e.getHTML()),i=x.normalizeHardBreaks(e.state.doc.content),n=e.schema.node("doc",null,i),r=n.textBetween(0,n.content.size,`
|
|
2
2
|
|
|
3
3
|
`,`
|
|
4
|
-
`);return{html:t,text:r,isEmpty:e.isEmpty,characterCount:r.length}}function f(e,t){if(t==="body")return 1;let
|
|
4
|
+
`);return{html:t,text:r,isEmpty:e.isEmpty,characterCount:r.length}}function f(e,t){if(t==="body")return 1;let i=-1;return e.state.doc.descendants((n,r)=>{if(i!==-1)return!1;n.type.name==="sectionDivider"&&n.attrs.sectionId===t&&(i=r+n.nodeSize)}),i}function m(e,t){const i=e.state.doc,n=i.content.size;if(t==="body"){let c=-1;return i.descendants((s,l)=>{if(c!==-1)return!1;s.type.name==="sectionDivider"&&(c=l)}),c!==-1?c:n}let r=!1,o=-1;return i.descendants((c,s)=>{if(o!==-1)return!1;if(!r)return c.type.name==="sectionDivider"&&c.attrs.sectionId===t&&(r=!0),!1;c.type.name==="sectionDivider"&&(o=s)}),o!==-1?o:n}function M(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function b(e){return`<pre><code>${M(e??"")}</code></pre>`}function P(e){let t="",i=!1;return e.state.doc.descendants(n=>{if(i)return!1;if(n.type.name==="codeBlock")return t=n.textContent,i=!0,!1}),t}function L(e,t){e.commands.setContent(b(t),{emitUpdate:!0})}function z(e,t){const n=e.slice(0,Math.max(0,t)).split(`
|
|
5
|
+
`);return{line:n.length,column:(n[n.length-1]?.length??0)+1}}function D(e,t){const i=e.replace(/\s*in JSON at position[\s\S]*$/i,"").trim()||e,n=e.match(/line (\d+) column (\d+)/i);if(n)return{message:i,line:Number(n[1]),column:Number(n[2])};const r=e.match(/position (\d+)/i);if(r){const{line:o,column:c}=z(t,Number(r[1]));return{message:i,line:o,column:c}}return{message:i,line:null,column:null}}function N(e){if(!e.trim())return{valid:!0,value:null,error:null,line:null,column:null};try{return{valid:!0,value:JSON.parse(e),error:null,line:null,column:null}}catch(t){const{message:i,line:n,column:r}=D(t.message,e);return{valid:!1,value:null,error:i,line:n,column:r}}}exports.SECTION_DIVIDER_HTML=d;exports.buildJsonCodeBlockHtml=b;exports.buildSectionedContent=A;exports.extractActiveFormats=E;exports.extractAllSectionsFromHtml=g;exports.extractBodyContent=T;exports.extractContent=B;exports.extractSectionContent=v;exports.findSectionEndPos=m;exports.findSectionStartPos=f;exports.getBodyHtml=p;exports.getCodeBlockText=P;exports.getSectionsHtml=C;exports.insertInlineHtml=S;exports.normalizeHtml=h;exports.parseJson=N;exports.setCodeBlockText=L;exports.setSectionContentInEditor=k;exports.toPortableHtml=u;
|
|
5
6
|
//# sourceMappingURL=BikEditor.utils.js.map
|
|
@@ -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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"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const q=require("@tiptap/extension-character-count"),E=require("@tiptap/extension-color"),b=require("@tiptap/extension-font-family"),k=require("@tiptap/extension-hard-break"),M=require("@tiptap/extension-highlight"),C=require("@tiptap/extension-image"),y=require("@tiptap/extension-link"),P=require("../../node_modules/@tiptap/extension-paragraph/dist/index.js"),T=require("@tiptap/extension-placeholder"),g=require("@tiptap/extension-subscript"),D=require("@tiptap/extension-superscript"),H=require("@tiptap/extension-text-align"),L=require("@tiptap/extension-text-style"),B=require("@tiptap/extension-underline"),F=require("@tiptap/starter-kit"),I=require("./FontSizeExtension.js"),O=require("./jsonHighlight/JsonHighlightExtension.js"),i=require("./mention/MentionExtension.js"),w=require("./paste/PasteExtension.js"),z=require("./plainClipboard/PasteNormalizationExtension.js"),A=require("./sectionDivider/SectionDividerNode.js"),l=require("./sendShortcut/SendShortcutExtension.js"),R=require("./slashCommand/SlashCommandExtension.js"),N=require("./variable/VariableDecorationExtension.js"),t=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=t(q),d=t(E),j=t(b),v=t(k),K=t(M),J=t(C),U=t(y),c=t(T),V=t(g),W=t(D),Z=t(H),G=t(B),h=t(F);function Q(e){if(e.features?.jsonMode)return[h.default.configure({link:!1,underline:!1,bold:!1,italic:!1,strike:!1,code:!1,heading:!1,bulletList:!1,orderedList:!1,blockquote:!1,horizontalRule:!1}),c.default.configure({placeholder:e.placeholder??`{
|
|
2
|
+
|
|
3
|
+
}`}),O.JsonHighlightExtension,l.SendShortcutExtension.configure({onSend:e.onSend,sendShortcut:e.sendShortcut,extraShortcuts:e.shortcuts??[]}),...e.maxCharacters?[u.default.configure({limit:e.maxCharacters})]:[]];const s=e.features?.richPaste??!1,m=e.features?.richTypography??!1,a=e.features?.allowedMarks,r=n=>!a||a.includes(n),f=v.default.extend({addKeyboardShortcuts(){return{"Shift-Enter":()=>this.editor.commands.setHardBreak()}}}),o=n=>n==="0"||n==="0px"||n==="0pt"||n==="0em",S=P.default.extend({addAttributes(){return{...this.parent?.()??{},margin:{default:null,parseHTML:n=>o(n.style.marginTop)&&o(n.style.marginBottom)?"0":null,renderHTML:n=>n.margin==="0"?{style:"margin: 0px"}:{}}}}}),p=[h.default.configure({link:!1,underline:!1,hardBreak:!1,paragraph:!1,bold:r("bold")?{}:!1,italic:r("italic")?{}:!1,strike:r("strike")?{}:!1,code:r("code")?{}:!1}),S,f,...r("underline")?[G.default]:[],U.default.extend({addPasteRules:()=>[],addInputRules:()=>[]}).configure({openOnClick:!1,autolink:!1,linkOnPaste:!1,HTMLAttributes:{rel:"noopener noreferrer",class:"bik-link"}}),L.TextStyle,c.default.configure({placeholder:e.placeholder??"Type a message..."}),...e.onPaste?[w.PasteExtension.configure({onPaste:e.onPaste})]:[],z.PasteNormalizationExtension.configure({preserveMarks:s,plainOnly:!s}),l.SendShortcutExtension.configure({onSend:e.onSend,sendShortcut:e.sendShortcut,extraShortcuts:e.shortcuts??[]}),N.VariableDecorationExtension,...e.maxCharacters?[u.default.configure({limit:e.maxCharacters})]:[],A.SectionDividerNode],x=[...e.mentions?.agents?[i.buildAgentMentionExtension(e.mentions.agents,e.onMentionSelected,e.renderMentionItem,e.renderMentionDropdown,e.removeMentionTriggerOnDismiss)]:[],...e.mentions?.teams?[i.buildTeamMentionExtension(e.mentions.teams,e.onMentionSelected,e.renderMentionItem,e.renderMentionDropdown,e.removeMentionTriggerOnDismiss)]:[],...e.slashCommands?[R.buildSlashCommandExtension(e.slashCommands,e.onSlashCommandSelected,e.renderSlashCommandItem,e.renderSlashCommandDropdown)]:[]],_=m?[d.default,K.default.configure({multicolor:!0}),j.default,I.FontSizeExtension,Z.default.configure({types:["heading","paragraph"]}),V.default,W.default,J.default]:[d.default];return[...p,...x,..._]}exports.buildExtensions=Q;
|
|
2
4
|
//# 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":"mvCAuEO,SAASA,EAAgBC,EAAyB,CACxD,MAAMC,EAAeD,EAAO,UAAU,WAAa,GAC7CE,EAAoBF,EAAO,UAAU,gBAAkB,GAKvDG,EAAeH,EAAO,UAAU,aAChCI,EAAUC,GACf,CAACF,GAAgBA,EAAa,SAASE,CAAC,EAOnCC,EAAYC,EAAAA,QAAU,OAAO,CAClC,sBAAuB,CACtB,MAAO,CACN,cAAe,IAAM,KAAK,OAAO,SAAS,aAAA,CAAa,CAEzD,CAAA,CACA,EAWKC,EAAaC,GAClBA,IAAM,KAAOA,IAAM,OAASA,IAAM,OAASA,IAAM,MAC5CC,EAAsBC,EAAAA,QAAU,OAAO,CAC5C,eAAgB,CACf,MAAO,CACN,GAAI,KAAK,SAAA,GAAc,CAAA,EACvB,OAAQ,CACP,QAAS,KACT,UAAYC,GACXJ,EAAUI,EAAG,MAAM,SAAS,GAAKJ,EAAUI,EAAG,MAAM,YAAY,EAC7D,IACA,KACJ,WAAaC,GACZA,EAAM,SAAW,IAAM,CAAE,MAAO,eAAkB,CAAA,CAAC,CACrD,CAEF,CAAA,CACA,EAEKC,EAAO,CAIZC,EAAAA,QAAW,UAAU,CACpB,KAAM,GACN,UAAW,GACX,UAAW,GACX,UAAW,GACX,KAAMX,EAAO,MAAM,EAAI,CAAA,EAAK,GAC5B,OAAQA,EAAO,QAAQ,EAAI,CAAA,EAAK,GAChC,OAAQA,EAAO,QAAQ,EAAI,CAAA,EAAK,GAChC,KAAMA,EAAO,MAAM,EAAI,GAAK,EAAA,CAC5B,EACDM,EACAJ,EACA,GAAIF,EAAO,WAAW,EAAI,CAACY,EAAAA,OAAS,EAAI,CAAA,EAGxCC,EAAAA,QAAK,OAAO,CACX,cAAe,IAAM,CAAA,EACrB,cAAe,IAAM,CAAA,CAAC,CACtB,EAAE,UAAU,CACZ,YAAa,GACb,SAAU,GACV,YAAa,GACb,eAAgB,CACf,IAAK,sBACL,MAAO,UAAA,CACR,CACA,EACDC,EAAAA,UACAC,EAAAA,QAAY,UAAU,CACrB,YAAanB,EAAO,aAAe,mBAAA,CACnC,EAID,GAAIA,EAAO,QACR,CAACoB,EAAAA,eAAe,UAAU,CAAE,QAASpB,EAAO,QAAS,CAAC,EACtD,CAAA,EACHqB,EAAAA,4BAA4B,UAAU,CACrC,cAAepB,EAIf,UAAW,CAACA,CAAA,CACZ,EACDqB,EAAAA,sBAAsB,UAAU,CAC/B,OAAQtB,EAAO,OACf,aAAcA,EAAO,aACrB,eAAgBA,EAAO,WAAa,CAAA,CAAC,CACrC,EACDuB,EAAAA,4BACA,GAAIvB,EAAO,cACR,CAACwB,EAAAA,QAAe,UAAU,CAAE,MAAOxB,EAAO,cAAe,CAAC,EAC1D,CAAA,EAKHyB,EAAAA,kBAAA,EAGKC,EAAoB,CACzB,GAAI1B,EAAO,UAAU,OAClB,CACA2B,EAAAA,2BACC3B,EAAO,SAAS,OAChBA,EAAO,kBACPA,EAAO,kBACPA,EAAO,sBACPA,EAAO,6BAAA,CACR,EAEA,CAAA,EACH,GAAIA,EAAO,UAAU,MAClB,CACA4B,EAAAA,0BACC5B,EAAO,SAAS,MAChBA,EAAO,kBACPA,EAAO,kBACPA,EAAO,sBACPA,EAAO,6BAAA,CACR,EAEA,CAAA,EACH,GAAIA,EAAO,cACR,CACA6B,EAAAA,2BACC7B,EAAO,cACPA,EAAO,uBACPA,EAAO,uBACPA,EAAO,0BAAA,CACR,EAEA,CAAA,CAAC,EAGC8B,EAAiB5B,EACpB,CACA6B,EAAAA,QACAC,EAAAA,QAAU,UAAU,CAAE,WAAY,GAAM,EACxCC,EAAAA,QACAC,EAAAA,kBACAC,EAAAA,QAAU,UAAU,CAAE,MAAO,CAAC,UAAW,WAAW,EAAG,EACvDC,EAAAA,QACAC,EAAAA,QACAC,EAAAA,OAAA,EAEA,CAACP,EAAAA,OAAK,EAET,MAAO,CAAC,GAAGjB,EAAM,GAAGY,EAAmB,GAAGI,CAAc,CACzD"}
|
|
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":"0yCAwEO,SAASA,EAAgBC,EAAyB,CAIxD,GAAIA,EAAO,UAAU,SACpB,MAAO,CACNC,EAAAA,QAAW,UAAU,CACpB,KAAM,GACN,UAAW,GAGX,KAAM,GACN,OAAQ,GACR,OAAQ,GACR,KAAM,GACN,QAAS,GACT,WAAY,GACZ,YAAa,GACb,WAAY,GACZ,eAAgB,EAAA,CAChB,EACDC,EAAAA,QAAY,UAAU,CACrB,YAAaF,EAAO,aAAe;AAAA;AAAA,EAAA,CACnC,EACDG,EAAAA,uBACAC,EAAAA,sBAAsB,UAAU,CAC/B,OAAQJ,EAAO,OACf,aAAcA,EAAO,aACrB,eAAgBA,EAAO,WAAa,CAAA,CAAC,CACrC,EACD,GAAIA,EAAO,cACR,CAACK,UAAe,UAAU,CAAE,MAAOL,EAAO,aAAA,CAAe,CAAC,EAC1D,CAAA,CAAC,EAIN,MAAMM,EAAeN,EAAO,UAAU,WAAa,GAC7CO,EAAoBP,EAAO,UAAU,gBAAkB,GAKvDQ,EAAeR,EAAO,UAAU,aAChCS,EAAUC,GACf,CAACF,GAAgBA,EAAa,SAASE,CAAC,EAOnCC,EAAYC,EAAAA,QAAU,OAAO,CAClC,sBAAuB,CACtB,MAAO,CACN,cAAe,IAAM,KAAK,OAAO,SAAS,aAAA,CAAa,CAEzD,CAAA,CACA,EAWKC,EAAaC,GAClBA,IAAM,KAAOA,IAAM,OAASA,IAAM,OAASA,IAAM,MAC5CC,EAAsBC,EAAAA,QAAU,OAAO,CAC5C,eAAgB,CACf,MAAO,CACN,GAAI,KAAK,SAAA,GAAc,CAAA,EACvB,OAAQ,CACP,QAAS,KACT,UAAYC,GACXJ,EAAUI,EAAG,MAAM,SAAS,GAAKJ,EAAUI,EAAG,MAAM,YAAY,EAC7D,IACA,KACJ,WAAaC,GACZA,EAAM,SAAW,IAAM,CAAE,MAAO,eAAkB,CAAA,CAAC,CACrD,CAEF,CAAA,CACA,EAEKC,EAAO,CAIZlB,EAAAA,QAAW,UAAU,CACpB,KAAM,GACN,UAAW,GACX,UAAW,GACX,UAAW,GACX,KAAMQ,EAAO,MAAM,EAAI,CAAA,EAAK,GAC5B,OAAQA,EAAO,QAAQ,EAAI,CAAA,EAAK,GAChC,OAAQA,EAAO,QAAQ,EAAI,CAAA,EAAK,GAChC,KAAMA,EAAO,MAAM,EAAI,GAAK,EAAA,CAC5B,EACDM,EACAJ,EACA,GAAIF,EAAO,WAAW,EAAI,CAACW,EAAAA,OAAS,EAAI,CAAA,EAGxCC,EAAAA,QAAK,OAAO,CACX,cAAe,IAAM,CAAA,EACrB,cAAe,IAAM,CAAA,CAAC,CACtB,EAAE,UAAU,CACZ,YAAa,GACb,SAAU,GACV,YAAa,GACb,eAAgB,CACf,IAAK,sBACL,MAAO,UAAA,CACR,CACA,EACDC,EAAAA,UACApB,EAAAA,QAAY,UAAU,CACrB,YAAaF,EAAO,aAAe,mBAAA,CACnC,EAID,GAAIA,EAAO,QACR,CAACuB,EAAAA,eAAe,UAAU,CAAE,QAASvB,EAAO,QAAS,CAAC,EACtD,CAAA,EACHwB,EAAAA,4BAA4B,UAAU,CACrC,cAAelB,EAIf,UAAW,CAACA,CAAA,CACZ,EACDF,EAAAA,sBAAsB,UAAU,CAC/B,OAAQJ,EAAO,OACf,aAAcA,EAAO,aACrB,eAAgBA,EAAO,WAAa,CAAA,CAAC,CACrC,EACDyB,EAAAA,4BACA,GAAIzB,EAAO,cACR,CAACK,EAAAA,QAAe,UAAU,CAAE,MAAOL,EAAO,cAAe,CAAC,EAC1D,CAAA,EAKH0B,EAAAA,kBAAA,EAGKC,EAAoB,CACzB,GAAI3B,EAAO,UAAU,OAClB,CACA4B,EAAAA,2BACC5B,EAAO,SAAS,OAChBA,EAAO,kBACPA,EAAO,kBACPA,EAAO,sBACPA,EAAO,6BAAA,CACR,EAEA,CAAA,EACH,GAAIA,EAAO,UAAU,MAClB,CACA6B,EAAAA,0BACC7B,EAAO,SAAS,MAChBA,EAAO,kBACPA,EAAO,kBACPA,EAAO,sBACPA,EAAO,6BAAA,CACR,EAEA,CAAA,EACH,GAAIA,EAAO,cACR,CACA8B,EAAAA,2BACC9B,EAAO,cACPA,EAAO,uBACPA,EAAO,uBACPA,EAAO,0BAAA,CACR,EAEA,CAAA,CAAC,EAGC+B,EAAiBxB,EACpB,CACAyB,EAAAA,QACAC,EAAAA,QAAU,UAAU,CAAE,WAAY,GAAM,EACxCC,EAAAA,QACAC,EAAAA,kBACAC,EAAAA,QAAU,UAAU,CAAE,MAAO,CAAC,UAAW,WAAW,EAAG,EACvDC,EAAAA,QACAC,EAAAA,QACAC,EAAAA,OAAA,EAEA,CAACP,EAAAA,OAAK,EAET,MAAO,CAAC,GAAGb,EAAM,GAAGQ,EAAmB,GAAGI,CAAc,CACzD"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("../../../node_modules/@tiptap/core/dist/index.js"),u=require("@tiptap/pm/state"),i=require("@tiptap/pm/view"),b=require("./tokenizeJson.js"),k={key:"bik-json-key",string:"bik-json-string",number:"bik-json-number",boolean:"bik-json-boolean",null:"bik-json-null",punctuation:"bik-json-punct"};function a(t){const n=document.createElement("span");return n.className="bik-json-lineno",n.textContent=String(t),n.setAttribute("contenteditable","false"),n}const h=d.Extension.create({name:"jsonHighlight",addProseMirrorPlugins(){return[new u.Plugin({key:new u.PluginKey("jsonHighlight"),props:{decorations(t){const n=[];return t.doc.descendants((r,g)=>{if(r.type.name!=="codeBlock")return;const s=r.textContent,o=g+1;for(const e of b.tokenizeJson(s))n.push(i.Decoration.inline(o+e.start,o+e.end,{class:k[e.type]}));let c=1;n.push(i.Decoration.widget(o,()=>a(1),{side:-1,key:"ln-1"}));for(let e=0;e<s.length;e++){if(s[e]!==`
|
|
2
|
+
`)continue;c+=1;const l=c;n.push(i.Decoration.widget(o+e+1,()=>a(l),{side:-1,key:`ln-${l}`}))}}),i.DecorationSet.create(t.doc,n)}}})]}});exports.JsonHighlightExtension=h;
|
|
3
|
+
//# 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 } 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\taddProseMirrorPlugins() {\n\t\treturn [\n\t\t\tnew Plugin({\n\t\t\t\tkey: new PluginKey('jsonHighlight'),\n\t\t\t\tprops: {\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","Plugin","PluginKey","state","decorations","node","pos","text","base","tok","tokenizeJson","Decoration","line","i","DecorationSet"],"mappings":"8OAKMA,EAA+C,CACpD,IAAK,eACL,OAAQ,kBACR,OAAQ,kBACR,QAAS,mBACT,KAAM,gBACN,YAAa,gBACd,EAGA,SAASC,EAAiBC,EAAwB,CACjD,MAAMC,EAAO,SAAS,cAAc,MAAM,EAC1C,OAAAA,EAAK,UAAY,kBACjBA,EAAK,YAAc,OAAOD,CAAC,EAC3BC,EAAK,aAAa,kBAAmB,OAAO,EACrCA,CACR,CAcO,MAAMC,EAAyBC,EAAAA,UAAU,OAAO,CACtD,KAAM,gBACN,uBAAwB,CACvB,MAAO,CACN,IAAIC,SAAO,CACV,IAAK,IAAIC,EAAAA,UAAU,eAAe,EAClC,MAAO,CACN,YAAYC,EAAO,CAClB,MAAMC,EAA4B,CAAA,EAClC,OAAAD,EAAM,IAAI,YAAY,CAACE,EAAMC,IAAQ,CACpC,GAAID,EAAK,KAAK,OAAS,YAAa,OACpC,MAAME,EAAOF,EAAK,YAEZG,EAAOF,EAAM,EAEnB,UAAWG,KAAOC,eAAaH,CAAI,EAClCH,EAAY,KACXO,EAAAA,WAAW,OAAOH,EAAOC,EAAI,MAAOD,EAAOC,EAAI,IAAK,CACnD,MAAOd,EAAcc,EAAI,IAAI,CAAA,CAC7B,CAAA,EAOH,IAAIG,EAAO,EACXR,EAAY,KACXO,EAAAA,WAAW,OAAOH,EAAM,IAAMZ,EAAiB,CAAC,EAAG,CAClD,KAAM,GACN,IAAK,MAAA,CACL,CAAA,EAEF,QAASiB,EAAI,EAAGA,EAAIN,EAAK,OAAQM,IAAK,CACrC,GAAIN,EAAKM,CAAC,IAAM;AAAA,EAAM,SACtBD,GAAQ,EACR,MAAMf,EAAIe,EACVR,EAAY,KACXO,EAAAA,WAAW,OAAOH,EAAOK,EAAI,EAAG,IAAMjB,EAAiBC,CAAC,EAAG,CAC1D,KAAM,GACN,IAAK,MAAMA,CAAC,EAAA,CACZ,CAAA,CAEH,CACD,CAAC,EACMiB,EAAAA,cAAc,OAAOX,EAAM,IAAKC,CAAW,CACnD,CAAA,CACD,CACA,CAAA,CAEH,CACD,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=/("(?:\\.|[^"\\])*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false)\b|\bnull\b|([{}[\],:])/g;function a(t,l){let e=l;for(;/\s/.test(t.charAt(e));)e++;return t.charAt(e)===":"}function b(t){const l=[];u.lastIndex=0;let e;for(;(e=u.exec(t))!==null;){const[o,r,c,d]=e,s=e.index,i=s+o.length;let n;r!==void 0?n=a(t,i)?"key":"string":c!==void 0?n="number":d!==void 0?n="boolean":o==="null"?n="null":n="punctuation",l.push({start:s,end:i,type:n})}return l}exports.tokenizeJson=b;
|
|
2
|
+
//# sourceMappingURL=tokenizeJson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizeJson.js","sources":["../../../../../src/editor/extensions/jsonHighlight/tokenizeJson.ts"],"sourcesContent":["/**\n * Minimal, dependency-free JSON tokenizer used for syntax highlighting.\n *\n * It is a LEXER, not a parser — it never rejects input. Given any string it\n * returns the ranges it recognises as JSON tokens and leaves everything else\n * (whitespace, malformed fragments) undecorated. This is exactly what a code\n * editor wants: highlight the valid-looking parts even while the user is still\n * typing invalid JSON.\n *\n * Offsets are 0-based indexes into `text`; `end` is exclusive.\n */\nexport type JsonTokenType =\n\t| 'key'\n\t| 'string'\n\t| 'number'\n\t| 'boolean'\n\t| 'null'\n\t| 'punctuation';\n\nexport interface JsonToken {\n\tstart: number;\n\tend: number;\n\ttype: JsonTokenType;\n}\n\n// One regex, alternation ordered so strings are matched before anything else.\n// Group 1 = string body (kept so we can peek past it for the `:` that marks a key).\nconst TOKEN_RE =\n\t/(\"(?:\\\\.|[^\"\\\\])*\")|(-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)|\\b(true|false)\\b|\\bnull\\b|([{}[\\],:])/g;\n\n/** Returns true if the next non-whitespace char after `index` is a colon. */\nfunction isFollowedByColon(text: string, index: number): boolean {\n\tlet i = index;\n\t// charAt returns '' past the end, so the loop terminates without bounds checks.\n\twhile (/\\s/.test(text.charAt(i))) i++;\n\treturn text.charAt(i) === ':';\n}\n\nexport function tokenizeJson(text: string): JsonToken[] {\n\tconst tokens: JsonToken[] = [];\n\tTOKEN_RE.lastIndex = 0;\n\tlet match: RegExpExecArray | null;\n\twhile ((match = TOKEN_RE.exec(text)) !== null) {\n\t\tconst [full, str, num, bool] = match;\n\t\tconst start = match.index;\n\t\tconst end = start + full.length;\n\t\tlet type: JsonTokenType;\n\t\tif (str !== undefined) {\n\t\t\t// A string is a KEY when the next meaningful char is `:` (object member).\n\t\t\ttype = isFollowedByColon(text, end) ? 'key' : 'string';\n\t\t} else if (num !== undefined) {\n\t\t\ttype = 'number';\n\t\t} else if (bool !== undefined) {\n\t\t\ttype = 'boolean';\n\t\t} else if (full === 'null') {\n\t\t\ttype = 'null';\n\t\t} else {\n\t\t\ttype = 'punctuation';\n\t\t}\n\t\ttokens.push({ start, end, type });\n\t}\n\treturn tokens;\n}\n"],"names":["TOKEN_RE","isFollowedByColon","text","index","i","tokenizeJson","tokens","match","full","str","num","bool","start","end","type"],"mappings":"gFA2BA,MAAMA,EACL,gGAGD,SAASC,EAAkBC,EAAcC,EAAwB,CAChE,IAAIC,EAAID,EAER,KAAO,KAAK,KAAKD,EAAK,OAAOE,CAAC,CAAC,GAAGA,IAClC,OAAOF,EAAK,OAAOE,CAAC,IAAM,GAC3B,CAEO,SAASC,EAAaH,EAA2B,CACvD,MAAMI,EAAsB,CAAA,EAC5BN,EAAS,UAAY,EACrB,IAAIO,EACJ,MAAQA,EAAQP,EAAS,KAAKE,CAAI,KAAO,MAAM,CAC9C,KAAM,CAACM,EAAMC,EAAKC,EAAKC,CAAI,EAAIJ,EACzBK,EAAQL,EAAM,MACdM,EAAMD,EAAQJ,EAAK,OACzB,IAAIM,EACAL,IAAQ,OAEXK,EAAOb,EAAkBC,EAAMW,CAAG,EAAI,MAAQ,SACpCH,IAAQ,OAClBI,EAAO,SACGH,IAAS,OACnBG,EAAO,UACGN,IAAS,OACnBM,EAAO,OAEPA,EAAO,cAERR,EAAO,KAAK,CAAE,MAAAM,EAAO,IAAAC,EAAK,KAAAC,EAAM,CACjC,CACA,OAAOR,CACR"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),l=({size:t=24,color:r="currentColor",...i})=>{const s={width:t,height:t,color:r,...i};return e.jsxs("svg",{viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg",...s,children:[e.jsx("g",{clipPath:"url(#clip0_758_122311)",children:e.jsx("path",{d:"M9.3332 4.66659L11.3332 6.66658M3.3332 3.99992V6.66658M12.6665 9.33325V11.9999M6.66653 1.33325V2.66659M4.66653 5.33325H1.99987M13.9999 10.6666H11.3332M7.3332 1.99992H5.99987M14.4265 2.42659L13.5732 1.57325C13.4982 1.49746 13.4089 1.4373 13.3105 1.39624C13.2121 1.35518 13.1065 1.33404 12.9999 1.33404C12.8932 1.33404 12.7877 1.35518 12.6893 1.39624C12.5908 1.4373 12.5015 1.49746 12.4265 1.57325L1.5732 12.4266C1.49741 12.5016 1.43725 12.5909 1.39619 12.6893C1.35513 12.7877 1.33398 12.8933 1.33398 12.9999C1.33398 13.1066 1.35513 13.2121 1.39619 13.3105C1.43725 13.4089 1.49741 13.4982 1.5732 13.5733L2.42653 14.4266C2.50108 14.5032 2.59022 14.5641 2.6887 14.6057C2.78717 14.6472 2.89298 14.6687 2.99987 14.6687C3.10676 14.6687 3.21256 14.6472 3.31104 14.6057C3.40951 14.5641 3.49865 14.5032 3.5732 14.4266L14.4265 3.57325C14.5031 3.4987 14.564 3.40956 14.6056 3.31109C14.6472 3.21262 14.6686 3.10681 14.6686 2.99992C14.6686 2.89303 14.6472 2.78722 14.6056 2.68875C14.564 2.59028 14.5031 2.50113 14.4265 2.42659Z",stroke:"#616161",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"})}),e.jsx("defs",{children:e.jsx("clipPath",{id:"clip0_758_122311",children:e.jsx("rect",{width:16,height:16,fill:"white"})})})]})};exports.default=l;
|
|
2
|
+
//# sourceMappingURL=WandSparkles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WandSparkles.js","sources":["../../../../src/icons/BIK AI specific/WandSparkles.tsx"],"sourcesContent":["import type { SVGProps } from 'react';\n\ninterface Props extends SVGProps<SVGSVGElement> {\n\tsize?: number | string;\n\tcolor?: string;\n}\nconst SvgWandSparkles = ({\n\tsize = 24,\n\tcolor = 'currentColor',\n\t...rest\n}: Props) => {\n\tconst props = {\n\t\twidth: size,\n\t\theight: size,\n\t\tcolor,\n\t\t...rest,\n\t};\n\treturn (\n\t\t<svg\n\t\t\tviewBox=\"0 0 16 16\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t{...props}\n\t\t>\n\t\t\t<g clipPath=\"url(#clip0_758_122311)\">\n\t\t\t\t<path\n\t\t\t\t\td=\"M9.3332 4.66659L11.3332 6.66658M3.3332 3.99992V6.66658M12.6665 9.33325V11.9999M6.66653 1.33325V2.66659M4.66653 5.33325H1.99987M13.9999 10.6666H11.3332M7.3332 1.99992H5.99987M14.4265 2.42659L13.5732 1.57325C13.4982 1.49746 13.4089 1.4373 13.3105 1.39624C13.2121 1.35518 13.1065 1.33404 12.9999 1.33404C12.8932 1.33404 12.7877 1.35518 12.6893 1.39624C12.5908 1.4373 12.5015 1.49746 12.4265 1.57325L1.5732 12.4266C1.49741 12.5016 1.43725 12.5909 1.39619 12.6893C1.35513 12.7877 1.33398 12.8933 1.33398 12.9999C1.33398 13.1066 1.35513 13.2121 1.39619 13.3105C1.43725 13.4089 1.49741 13.4982 1.5732 13.5733L2.42653 14.4266C2.50108 14.5032 2.59022 14.5641 2.6887 14.6057C2.78717 14.6472 2.89298 14.6687 2.99987 14.6687C3.10676 14.6687 3.21256 14.6472 3.31104 14.6057C3.40951 14.5641 3.49865 14.5032 3.5732 14.4266L14.4265 3.57325C14.5031 3.4987 14.564 3.40956 14.6056 3.31109C14.6472 3.21262 14.6686 3.10681 14.6686 2.99992C14.6686 2.89303 14.6472 2.78722 14.6056 2.68875C14.564 2.59028 14.5031 2.50113 14.4265 2.42659Z\"\n\t\t\t\t\tstroke=\"#616161\"\n\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t/>\n\t\t\t</g>\n\t\t\t<defs>\n\t\t\t\t<clipPath id=\"clip0_758_122311\">\n\t\t\t\t\t<rect width={16} height={16} fill=\"white\" />\n\t\t\t\t</clipPath>\n\t\t\t</defs>\n\t\t</svg>\n\t);\n};\nexport default SvgWandSparkles;\n"],"names":["SvgWandSparkles","size","color","rest","props","jsxs","jsx"],"mappings":"iJAMMA,EAAkB,CAAC,CACxB,KAAAC,EAAO,GACP,MAAAC,EAAQ,eACR,GAAGC,CACJ,IAAa,CACZ,MAAMC,EAAQ,CACb,MAAOH,EACP,OAAQA,EACR,MAAAC,EACA,GAAGC,CAAA,EAEJ,OACCE,EAAAA,KAAC,MAAA,CACA,QAAQ,YACR,KAAK,OACL,MAAM,6BACL,GAAGD,EAEJ,SAAA,CAAAE,EAAAA,IAAC,IAAA,CAAE,SAAS,yBACX,SAAAA,EAAAA,IAAC,OAAA,CACA,EAAE,w/BACF,OAAO,UACP,YAAa,IACb,cAAc,QACd,eAAe,OAAA,CAAA,EAEjB,EACAA,MAAC,OAAA,CACA,SAAAA,EAAAA,IAAC,WAAA,CAAS,GAAG,mBACZ,SAAAA,EAAAA,IAAC,OAAA,CAAK,MAAO,GAAI,OAAQ,GAAI,KAAK,QAAQ,EAC3C,CAAA,CACD,CAAA,CAAA,CAAA,CAGH"}
|