@bikdotai/bik-component-library 0.0.838-beta.4 → 0.0.838-beta.6

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.
@@ -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 { isAllowedLinkUrl } from '../../helpers/sanitize';\nimport type {\n\tEditorFeatures,\n\tEditorSnapshot,\n\tKeyboardShortcut,\n\tMentionDropdownRenderProps,\n\tMentionItem,\n\tPasteData,\n\tSlashCommandDropdownRenderProps,\n\tSlashCommandItem,\n} from '../BikEditor.types';\nimport { CharacterLimitExtension } from './characterLimit/CharacterLimitExtension';\nimport { FontSizeExtension } from './FontSizeExtension';\nimport { HtmlTagStripExtension } from './htmlTagStrip/HtmlTagStripExtension';\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\t/**\n\t * Soft character limit. Unlike `maxCharacters` (which hard-blocks all input),\n\t * this blocks typing past the limit but ALLOWS paste to overflow, rendering\n\t * the excess characters greyed (`.bik-editor-overflow`). Purely visual +\n\t * typing-guard; the consumer shows the count/error and gates send itself.\n\t */\n\tcharacterLimit?: 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\t// Block javascript:/data:/vbscript: hrefs. TipTap keeps the href\n\t\t\t// attribute verbatim, so scheme validation must live here.\n\t\t\tisAllowedUri: (url) => isAllowedLinkUrl(url),\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// Strip complete HTML tags from typed text in non-email channels.\n\t\t// Email (richPaste) keeps tags because the editor legitimately renders HTML.\n\t\t...(!hasRichPaste ? [HtmlTagStripExtension] : []),\n\t\t...(config.maxCharacters\n\t\t\t? [CharacterCount.configure({ limit: config.maxCharacters })]\n\t\t\t: []),\n\t\t...(config.characterLimit\n\t\t\t? [CharacterLimitExtension.configure({ limit: config.characterLimit })]\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","url","isAllowedLinkUrl","TextStyle","PasteExtension","PasteNormalizationExtension","VariableDecorationExtension","HtmlTagStripExtension","CharacterLimitExtension","SectionDividerNode","mentionExtensions","buildAgentMentionExtension","buildTeamMentionExtension","buildSlashCommandExtension","richExtensions","Color","Highlight","FontFamily","FontSizeExtension","TextAlign","Subscript","Superscript","Image"],"mappings":"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"}
1
+ {"version":3,"file":"buildExtensions.js","sources":["../../../../src/editor/extensions/buildExtensions.ts"],"sourcesContent":["import CharacterCount from '@tiptap/extension-character-count';\nimport Color from '@tiptap/extension-color';\nimport FontFamily from '@tiptap/extension-font-family';\nimport HardBreak from '@tiptap/extension-hard-break';\nimport Highlight from '@tiptap/extension-highlight';\nimport Image from '@tiptap/extension-image';\nimport Link from '@tiptap/extension-link';\nimport Paragraph from '@tiptap/extension-paragraph';\nimport Placeholder from '@tiptap/extension-placeholder';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport TextAlign from '@tiptap/extension-text-align';\nimport { TextStyle } from '@tiptap/extension-text-style';\nimport Underline from '@tiptap/extension-underline';\nimport StarterKit from '@tiptap/starter-kit';\nimport type { ReactNode } from 'react';\nimport { isAllowedLinkUrl } from '../../helpers/sanitize';\nimport type {\n\tEditorFeatures,\n\tEditorSnapshot,\n\tKeyboardShortcut,\n\tMentionDropdownRenderProps,\n\tMentionItem,\n\tPasteData,\n\tSlashCommandDropdownRenderProps,\n\tSlashCommandItem,\n} from '../BikEditor.types';\nimport { CharacterLimitExtension } from './characterLimit/CharacterLimitExtension';\nimport { FontSizeExtension } from './FontSizeExtension';\nimport { HtmlTagStripExtension } from './htmlTagStrip/HtmlTagStripExtension';\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\t/**\n\t * Soft character limit. Unlike `maxCharacters` (which hard-blocks all input),\n\t * this blocks typing past the limit but ALLOWS paste to overflow, rendering\n\t * the excess characters greyed (`.bik-editor-overflow`). Purely visual +\n\t * typing-guard; the consumer shows the count/error and gates send itself.\n\t */\n\tcharacterLimit?: 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\t// Block javascript:/data:/vbscript: hrefs. TipTap keeps the href\n\t\t\t// attribute verbatim, so scheme validation must live here.\n\t\t\tisAllowedUri: (url) => isAllowedLinkUrl(url),\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// Strip HTML tags from typed text.\n\t\t// Non-email (WhatsApp, IG, private note): strip ALL tags.\n\t\t// Email (richPaste): strip only dangerous tags (script, iframe, form, etc.)\n\t\t// while preserving legitimate email tags (h1, table, div, etc.).\n\t\tHtmlTagStripExtension.configure({ stripAll: !hasRichPaste }),\n\t\t...(config.maxCharacters\n\t\t\t? [CharacterCount.configure({ limit: config.maxCharacters })]\n\t\t\t: []),\n\t\t...(config.characterLimit\n\t\t\t? [CharacterLimitExtension.configure({ limit: config.characterLimit })]\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","url","isAllowedLinkUrl","TextStyle","PasteExtension","PasteNormalizationExtension","VariableDecorationExtension","HtmlTagStripExtension","CharacterLimitExtension","SectionDividerNode","mentionExtensions","buildAgentMentionExtension","buildTeamMentionExtension","buildSlashCommandExtension","richExtensions","Color","Highlight","FontFamily","FontSizeExtension","TextAlign","Subscript","Superscript","Image"],"mappings":"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"}
@@ -1,15 +1,13 @@
1
1
  import { Extension } from '@tiptap/core';
2
2
  /**
3
- * Strips complete HTML tags from typed text in real time.
3
+ * Strips HTML tags from typed text in real time.
4
4
  *
5
- * Enabled for non-email channels (WhatsApp, IG, private note, etc.) where
6
- * HTML has no meaning. When the user types `<h1>Hello</h1>`, the tags are
7
- * removed as soon as the closing `>` completes each tag, leaving just `Hello`.
5
+ * Two modes controlled by `stripAll`:
6
+ * - `true` (non-email): strip ALL complete tags — `<h1>Hello</h1>` → `Hello`
7
+ * - `false` (email): strip only DANGEROUS tags — `<script>`, `<iframe>`,
8
+ * `<form>`, etc. — while preserving legitimate email tags like `<h1>`, `<table>`
8
9
  *
9
10
  * Incomplete tags like `<h` are preserved (the regex requires a closing `>`).
10
11
  * Non-tag angle brackets like `2 < 3` are also preserved.
11
- *
12
- * Uses `appendTransaction` so the strip happens in the same undo step as
13
- * the keystroke — Cmd+Z undoes both the tag and the strip together.
14
12
  */
15
13
  export declare const HtmlTagStripExtension: Extension<any, any>;
@@ -1,32 +1,64 @@
1
- import { Extension as m } from "../../../node_modules/@tiptap/core/dist/index.js";
2
- import { Plugin as c } from "@tiptap/pm/state";
3
- const r = /<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>/g, g = m.create({
1
+ import { Extension as u } from "../../../node_modules/@tiptap/core/dist/index.js";
2
+ import { Plugin as d } from "@tiptap/pm/state";
3
+ const n = /<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>/g, f = /* @__PURE__ */ new Set([
4
+ "script",
5
+ "iframe",
6
+ "object",
7
+ "embed",
8
+ "applet",
9
+ "form",
10
+ "input",
11
+ "button",
12
+ "select",
13
+ "option",
14
+ "textarea",
15
+ "style",
16
+ "link",
17
+ "meta",
18
+ "base",
19
+ "svg",
20
+ "math",
21
+ "noscript",
22
+ "template"
23
+ ]), g = /<\/?([a-zA-Z][a-zA-Z0-9]*)/;
24
+ function x(a) {
25
+ const r = a.match(g);
26
+ return r ? f.has(r[1].toLowerCase()) : !1;
27
+ }
28
+ const E = u.create({
4
29
  name: "htmlTagStrip",
30
+ addOptions() {
31
+ return { stripAll: !0 };
32
+ },
5
33
  addProseMirrorPlugins() {
34
+ const { stripAll: a } = this.options, r = a ? () => !0 : (s) => x(s);
6
35
  return [
7
- new c({
8
- appendTransaction(l, o, a) {
9
- if (!l.some((t) => t.docChanged)) return null;
10
- const { tr: e } = a;
11
- let s = !1;
12
- return a.doc.descendants((t, n) => {
13
- if (!t.isText || !t.text || (r.lastIndex = 0, !r.test(t.text))) return;
14
- r.lastIndex = 0;
15
- const i = t.text.replace(r, "");
16
- if (i === t.text) return;
17
- const p = n + t.nodeSize;
18
- i ? e.replaceWith(
19
- e.mapping.map(n),
20
- e.mapping.map(p),
21
- a.schema.text(i, t.marks)
22
- ) : e.delete(e.mapping.map(n), e.mapping.map(p)), s = !0;
23
- }), s ? (e.setMeta("addToHistory", !1), e) : null;
36
+ new d({
37
+ appendTransaction(s, A, i) {
38
+ if (!s.some((t) => t.docChanged)) return null;
39
+ const { tr: e } = i;
40
+ let l = !1;
41
+ return i.doc.descendants((t, o) => {
42
+ if (!t.isText || !t.text || (n.lastIndex = 0, !n.test(t.text))) return;
43
+ n.lastIndex = 0;
44
+ const p = t.text.replace(
45
+ n,
46
+ (c) => r(c) ? "" : c
47
+ );
48
+ if (p === t.text) return;
49
+ const m = o + t.nodeSize;
50
+ p ? e.replaceWith(
51
+ e.mapping.map(o),
52
+ e.mapping.map(m),
53
+ i.schema.text(p, t.marks)
54
+ ) : e.delete(e.mapping.map(o), e.mapping.map(m)), l = !0;
55
+ }), l ? (e.setMeta("addToHistory", !1), e) : null;
24
56
  }
25
57
  })
26
58
  ];
27
59
  }
28
60
  });
29
61
  export {
30
- g as HtmlTagStripExtension
62
+ E as HtmlTagStripExtension
31
63
  };
32
64
  //# sourceMappingURL=HtmlTagStripExtension.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"HtmlTagStripExtension.js","sources":["../../../../../src/editor/extensions/htmlTagStrip/HtmlTagStripExtension.ts"],"sourcesContent":["import { Extension } from '@tiptap/core';\nimport { Plugin } from '@tiptap/pm/state';\n\nconst COMPLETE_TAG = /<\\/?[a-zA-Z][a-zA-Z0-9]*(?:\\s[^>]*)?\\/?>/g;\n\n/**\n * Strips complete HTML tags from typed text in real time.\n *\n * Enabled for non-email channels (WhatsApp, IG, private note, etc.) where\n * HTML has no meaning. When the user types `<h1>Hello</h1>`, the tags are\n * removed as soon as the closing `>` completes each tag, leaving just `Hello`.\n *\n * Incomplete tags like `<h` are preserved (the regex requires a closing `>`).\n * Non-tag angle brackets like `2 < 3` are also preserved.\n *\n * Uses `appendTransaction` so the strip happens in the same undo step as\n * the keystroke — Cmd+Z undoes both the tag and the strip together.\n */\nexport const HtmlTagStripExtension = Extension.create({\n\tname: 'htmlTagStrip',\n\taddProseMirrorPlugins() {\n\t\treturn [\n\t\t\tnew Plugin({\n\t\t\t\tappendTransaction(transactions, _oldState, newState) {\n\t\t\t\t\tif (!transactions.some((t) => t.docChanged)) return null;\n\n\t\t\t\t\tconst { tr } = newState;\n\t\t\t\t\tlet changed = false;\n\n\t\t\t\t\tnewState.doc.descendants((node, pos) => {\n\t\t\t\t\t\tif (!node.isText || !node.text) return;\n\t\t\t\t\t\tCOMPLETE_TAG.lastIndex = 0;\n\t\t\t\t\t\tif (!COMPLETE_TAG.test(node.text)) return;\n\n\t\t\t\t\t\tCOMPLETE_TAG.lastIndex = 0;\n\t\t\t\t\t\tconst stripped = node.text.replace(COMPLETE_TAG, '');\n\t\t\t\t\t\tif (stripped === node.text) return;\n\n\t\t\t\t\t\tconst end = pos + node.nodeSize;\n\t\t\t\t\t\tif (stripped) {\n\t\t\t\t\t\t\ttr.replaceWith(\n\t\t\t\t\t\t\t\ttr.mapping.map(pos),\n\t\t\t\t\t\t\t\ttr.mapping.map(end),\n\t\t\t\t\t\t\t\tnewState.schema.text(stripped, node.marks),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttr.delete(tr.mapping.map(pos), tr.mapping.map(end));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!changed) return null;\n\t\t\t\t\ttr.setMeta('addToHistory', false);\n\t\t\t\t\treturn tr;\n\t\t\t\t},\n\t\t\t}),\n\t\t];\n\t},\n});\n"],"names":["COMPLETE_TAG","HtmlTagStripExtension","Extension","Plugin","transactions","_oldState","newState","tr","changed","node","pos","stripped","end"],"mappings":";;AAGA,MAAMA,IAAe,6CAeRC,IAAwBC,EAAU,OAAO;AAAA,EACrD,MAAM;AAAA,EACN,wBAAwB;AACvB,WAAO;AAAA,MACN,IAAIC,EAAO;AAAA,QACV,kBAAkBC,GAAcC,GAAWC,GAAU;AACpD,cAAI,CAACF,EAAa,KAAK,CAAC,MAAM,EAAE,UAAU,EAAG,QAAO;AAEpD,gBAAM,EAAE,IAAAG,MAAOD;AACf,cAAIE,IAAU;AAwBd,iBAtBAF,EAAS,IAAI,YAAY,CAACG,GAAMC,MAAQ;AAGvC,gBAFI,CAACD,EAAK,UAAU,CAACA,EAAK,SAC1BT,EAAa,YAAY,GACrB,CAACA,EAAa,KAAKS,EAAK,IAAI,GAAG;AAEnC,YAAAT,EAAa,YAAY;AACzB,kBAAMW,IAAWF,EAAK,KAAK,QAAQT,GAAc,EAAE;AACnD,gBAAIW,MAAaF,EAAK,KAAM;AAE5B,kBAAMG,IAAMF,IAAMD,EAAK;AACvB,YAAIE,IACHJ,EAAG;AAAA,cACFA,EAAG,QAAQ,IAAIG,CAAG;AAAA,cAClBH,EAAG,QAAQ,IAAIK,CAAG;AAAA,cAClBN,EAAS,OAAO,KAAKK,GAAUF,EAAK,KAAK;AAAA,YAAA,IAG1CF,EAAG,OAAOA,EAAG,QAAQ,IAAIG,CAAG,GAAGH,EAAG,QAAQ,IAAIK,CAAG,CAAC,GAEnDJ,IAAU;AAAA,UACX,CAAC,GAEIA,KACLD,EAAG,QAAQ,gBAAgB,EAAK,GACzBA,KAFc;AAAA,QAGtB;AAAA,MAAA,CACA;AAAA,IAAA;AAAA,EAEH;AACD,CAAC;"}
1
+ {"version":3,"file":"HtmlTagStripExtension.js","sources":["../../../../../src/editor/extensions/htmlTagStrip/HtmlTagStripExtension.ts"],"sourcesContent":["import { Extension } from '@tiptap/core';\nimport { Plugin } from '@tiptap/pm/state';\n\nconst COMPLETE_TAG = /<\\/?[a-zA-Z][a-zA-Z0-9]*(?:\\s[^>]*)?\\/?>/g;\n\n// Tags that have no business in an email — stripped even in rich-paste mode.\nconst DANGEROUS_TAG_NAMES = new Set([\n\t'script',\n\t'iframe',\n\t'object',\n\t'embed',\n\t'applet',\n\t'form',\n\t'input',\n\t'button',\n\t'select',\n\t'option',\n\t'textarea',\n\t'style',\n\t'link',\n\t'meta',\n\t'base',\n\t'svg',\n\t'math',\n\t'noscript',\n\t'template',\n]);\n\nconst TAG_NAME_RE = /<\\/?([a-zA-Z][a-zA-Z0-9]*)/;\n\nfunction isDangerousTag(tag: string): boolean {\n\tconst m = tag.match(TAG_NAME_RE);\n\treturn m ? DANGEROUS_TAG_NAMES.has(m[1].toLowerCase()) : false;\n}\n\n/**\n * Strips HTML tags from typed text in real time.\n *\n * Two modes controlled by `stripAll`:\n * - `true` (non-email): strip ALL complete tags — `<h1>Hello</h1>` → `Hello`\n * - `false` (email): strip only DANGEROUS tags — `<script>`, `<iframe>`,\n * `<form>`, etc. — while preserving legitimate email tags like `<h1>`, `<table>`\n *\n * Incomplete tags like `<h` are preserved (the regex requires a closing `>`).\n * Non-tag angle brackets like `2 < 3` are also preserved.\n */\nexport const HtmlTagStripExtension = Extension.create({\n\tname: 'htmlTagStrip',\n\taddOptions() {\n\t\treturn { stripAll: true };\n\t},\n\taddProseMirrorPlugins() {\n\t\tconst { stripAll } = this.options;\n\t\tconst shouldStrip = stripAll\n\t\t\t? () => true\n\t\t\t: (tag: string) => isDangerousTag(tag);\n\n\t\treturn [\n\t\t\tnew Plugin({\n\t\t\t\tappendTransaction(transactions, _oldState, newState) {\n\t\t\t\t\tif (!transactions.some((t) => t.docChanged)) return null;\n\n\t\t\t\t\tconst { tr } = newState;\n\t\t\t\t\tlet changed = false;\n\n\t\t\t\t\tnewState.doc.descendants((node, pos) => {\n\t\t\t\t\t\tif (!node.isText || !node.text) return;\n\t\t\t\t\t\tCOMPLETE_TAG.lastIndex = 0;\n\t\t\t\t\t\tif (!COMPLETE_TAG.test(node.text)) return;\n\n\t\t\t\t\t\tCOMPLETE_TAG.lastIndex = 0;\n\t\t\t\t\t\tconst stripped = node.text.replace(COMPLETE_TAG, (match) =>\n\t\t\t\t\t\t\tshouldStrip(match) ? '' : match,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (stripped === node.text) return;\n\n\t\t\t\t\t\tconst end = pos + node.nodeSize;\n\t\t\t\t\t\tif (stripped) {\n\t\t\t\t\t\t\ttr.replaceWith(\n\t\t\t\t\t\t\t\ttr.mapping.map(pos),\n\t\t\t\t\t\t\t\ttr.mapping.map(end),\n\t\t\t\t\t\t\t\tnewState.schema.text(stripped, node.marks),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttr.delete(tr.mapping.map(pos), tr.mapping.map(end));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!changed) return null;\n\t\t\t\t\ttr.setMeta('addToHistory', false);\n\t\t\t\t\treturn tr;\n\t\t\t\t},\n\t\t\t}),\n\t\t];\n\t},\n});\n"],"names":["COMPLETE_TAG","DANGEROUS_TAG_NAMES","TAG_NAME_RE","isDangerousTag","tag","m","HtmlTagStripExtension","Extension","stripAll","shouldStrip","Plugin","transactions","_oldState","newState","tr","changed","node","pos","stripped","match","end"],"mappings":";;AAGA,MAAMA,IAAe,6CAGfC,wBAA0B,IAAI;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC,GAEKC,IAAc;AAEpB,SAASC,EAAeC,GAAsB;AAC7C,QAAMC,IAAID,EAAI,MAAMF,CAAW;AAC/B,SAAOG,IAAIJ,EAAoB,IAAII,EAAE,CAAC,EAAE,YAAA,CAAa,IAAI;AAC1D;AAaO,MAAMC,IAAwBC,EAAU,OAAO;AAAA,EACrD,MAAM;AAAA,EACN,aAAa;AACZ,WAAO,EAAE,UAAU,GAAA;AAAA,EACpB;AAAA,EACA,wBAAwB;AACvB,UAAM,EAAE,UAAAC,MAAa,KAAK,SACpBC,IAAcD,IACjB,MAAM,KACN,CAACJ,MAAgBD,EAAeC,CAAG;AAEtC,WAAO;AAAA,MACN,IAAIM,EAAO;AAAA,QACV,kBAAkBC,GAAcC,GAAWC,GAAU;AACpD,cAAI,CAACF,EAAa,KAAK,CAAC,MAAM,EAAE,UAAU,EAAG,QAAO;AAEpD,gBAAM,EAAE,IAAAG,MAAOD;AACf,cAAIE,IAAU;AA0Bd,iBAxBAF,EAAS,IAAI,YAAY,CAACG,GAAMC,MAAQ;AAGvC,gBAFI,CAACD,EAAK,UAAU,CAACA,EAAK,SAC1BhB,EAAa,YAAY,GACrB,CAACA,EAAa,KAAKgB,EAAK,IAAI,GAAG;AAEnC,YAAAhB,EAAa,YAAY;AACzB,kBAAMkB,IAAWF,EAAK,KAAK;AAAA,cAAQhB;AAAA,cAAc,CAACmB,MACjDV,EAAYU,CAAK,IAAI,KAAKA;AAAA,YAAA;AAE3B,gBAAID,MAAaF,EAAK,KAAM;AAE5B,kBAAMI,IAAMH,IAAMD,EAAK;AACvB,YAAIE,IACHJ,EAAG;AAAA,cACFA,EAAG,QAAQ,IAAIG,CAAG;AAAA,cAClBH,EAAG,QAAQ,IAAIM,CAAG;AAAA,cAClBP,EAAS,OAAO,KAAKK,GAAUF,EAAK,KAAK;AAAA,YAAA,IAG1CF,EAAG,OAAOA,EAAG,QAAQ,IAAIG,CAAG,GAAGH,EAAG,QAAQ,IAAIM,CAAG,CAAC,GAEnDL,IAAU;AAAA,UACX,CAAC,GAEIA,KACLD,EAAG,QAAQ,gBAAgB,EAAK,GACzBA,KAFc;AAAA,QAGtB;AAAA,MAAA,CACA;AAAA,IAAA;AAAA,EAEH;AACD,CAAC;"}
@@ -19,8 +19,14 @@ export declare const sanitizePlainText: (input: string | null | undefined) => st
19
19
  * restricted to safe protocols.
20
20
  */
21
21
  export declare const sanitizeBasicHtml: (input: string | null | undefined) => string;
22
+ /**
23
+ * Filter an inline style string to only safe CSS properties with safe values.
24
+ * Returns the cleaned style string (empty string if nothing survives).
25
+ */
26
+ export declare const sanitizeCssProperties: (style: string) => string;
22
27
  /**
23
28
  * Full rich text / inbound email HTML. Allowlist of tags and attributes an
24
- * email needs to render; everything else is stripped.
29
+ * email needs to render; everything else is stripped. Inline style attributes
30
+ * are filtered to a safe CSS property allowlist.
25
31
  */
26
32
  export declare const sanitizeEmailHtml: (input: string | null | undefined) => string;
@@ -1,17 +1,17 @@
1
- import e from "dompurify";
2
- const r = () => typeof window < "u", l = /<\/?[^>]+(>|$)/g, a = /* @__PURE__ */ new Set(["https:", "mailto:", "tel:"]), i = /* @__PURE__ */ new Set(["https:", "data:"]), s = (t, o) => {
1
+ import i from "dompurify";
2
+ const n = () => typeof window < "u", l = /<\/?[^>]+(>|$)/g, d = /* @__PURE__ */ new Set(["https:", "mailto:", "tel:"]), p = /* @__PURE__ */ new Set(["https:", "data:"]), c = (t, e) => {
3
3
  if (!t) return null;
4
4
  try {
5
- const n = new URL(t, "https://bik.local");
6
- return o.has(n.protocol) ? t : null;
5
+ const r = new URL(t, "https://bik.local");
6
+ return e.has(r.protocol) ? t : null;
7
7
  } catch {
8
8
  return null;
9
9
  }
10
- }, A = (t) => s(t, a) !== null, u = (t) => s(t, i) !== null, c = /<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>/g, T = (t) => t ? t.replace(c, "") : "", L = /^[=+\-@\t\r]/, h = (t) => t ? L.test(t) ? `'${t}` : t : "", _ = (t) => t ? r() ? e.sanitize(t, {
10
+ }, A = (t) => c(t, d) !== null, L = (t) => c(t, p) !== null, f = /<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>/g, O = (t) => t ? t.replace(f, "") : "", g = /^[=+\-@\t\r]/, w = (t) => t ? g.test(t) ? `'${t}` : t : "", _ = (t) => t ? n() ? i.sanitize(t, {
11
11
  ALLOWED_TAGS: [],
12
12
  ALLOWED_ATTR: [],
13
13
  KEEP_CONTENT: !0
14
- }) : t.replace(l, "") : "", f = (t) => t ? r() ? e.sanitize(t, {
14
+ }) : t.replace(l, "") : "", E = (t) => t ? n() ? i.sanitize(t, {
15
15
  ALLOWED_TAGS: [
16
16
  "p",
17
17
  "br",
@@ -32,92 +32,173 @@ const r = () => typeof window < "u", l = /<\/?[^>]+(>|$)/g, a = /* @__PURE__ */
32
32
  FORBID_TAGS: ["style"],
33
33
  FORBID_ATTR: ["style"],
34
34
  ALLOWED_URI_REGEXP: /^(?:https|mailto|tel):/i
35
- }) : t.replace(l, "") : "", E = (t) => t ? r() ? e.sanitize(t, {
36
- ALLOWED_TAGS: [
37
- "p",
38
- "br",
39
- "div",
40
- "span",
41
- "strong",
42
- "b",
43
- "em",
44
- "i",
45
- "u",
46
- "s",
47
- "ul",
48
- "ol",
49
- "li",
50
- "blockquote",
51
- "h1",
52
- "h2",
53
- "h3",
54
- "h4",
55
- "h5",
56
- "h6",
57
- "a",
58
- "img",
59
- "table",
60
- "thead",
61
- "tbody",
62
- "tfoot",
63
- "tr",
64
- "th",
65
- "td",
66
- "caption",
67
- "colgroup",
68
- "col",
69
- "center",
70
- "font",
71
- "hr",
72
- "pre",
73
- "code",
74
- "sub",
75
- "sup",
76
- "small",
77
- "big",
78
- "figure",
79
- "figcaption",
80
- "section",
81
- "article",
82
- "header",
83
- "footer",
84
- "abbr",
85
- "cite",
86
- "mark",
87
- "del",
88
- "ins"
89
- ],
90
- ALLOWED_ATTR: [
91
- "href",
92
- "target",
93
- "rel",
94
- "src",
95
- "alt",
96
- "title",
97
- "width",
98
- "height",
99
- "style",
100
- "class",
101
- "align",
102
- "valign",
103
- "colspan",
104
- "rowspan",
105
- "cellpadding",
106
- "cellspacing",
107
- "border",
108
- "color",
109
- "bgcolor"
110
- ],
111
- ALLOW_UNKNOWN_PROTOCOLS: !1
112
- }) : t.replace(l, "") : "";
35
+ }) : t.replace(l, "") : "", h = /* @__PURE__ */ new Set([
36
+ "color",
37
+ "background-color",
38
+ "background",
39
+ "font-family",
40
+ "font-size",
41
+ "font-weight",
42
+ "font-style",
43
+ "font-variant",
44
+ "font",
45
+ "line-height",
46
+ "letter-spacing",
47
+ "word-spacing",
48
+ "text-align",
49
+ "text-decoration",
50
+ "text-transform",
51
+ "text-indent",
52
+ "vertical-align",
53
+ "white-space",
54
+ "word-wrap",
55
+ "word-break",
56
+ "overflow-wrap",
57
+ "direction",
58
+ "margin",
59
+ "margin-top",
60
+ "margin-right",
61
+ "margin-bottom",
62
+ "margin-left",
63
+ "padding",
64
+ "padding-top",
65
+ "padding-right",
66
+ "padding-bottom",
67
+ "padding-left",
68
+ "border",
69
+ "border-top",
70
+ "border-right",
71
+ "border-bottom",
72
+ "border-left",
73
+ "border-width",
74
+ "border-style",
75
+ "border-color",
76
+ "border-collapse",
77
+ "border-spacing",
78
+ "border-radius",
79
+ "width",
80
+ "min-width",
81
+ "max-width",
82
+ "height",
83
+ "min-height",
84
+ "max-height",
85
+ "display",
86
+ "visibility",
87
+ "opacity",
88
+ "float",
89
+ "clear",
90
+ "overflow",
91
+ "overflow-x",
92
+ "overflow-y",
93
+ "table-layout",
94
+ "list-style",
95
+ "list-style-type",
96
+ "list-style-position",
97
+ "box-sizing"
98
+ ]), u = /url\s*\(|expression\s*\(|-moz-binding|behavior\s*:|@import|javascript:|data:/i, b = (t) => t.split(";").map((e) => e.trim()).filter((e) => {
99
+ if (!e) return !1;
100
+ const r = e.indexOf(":");
101
+ if (r < 0) return !1;
102
+ const s = e.slice(0, r).trim().toLowerCase(), o = e.slice(r + 1).trim();
103
+ return h.has(s) && !u.test(o);
104
+ }).join("; "), T = (t) => {
105
+ if (!t) return "";
106
+ if (!n()) return t.replace(l, "");
107
+ const e = (s, o) => {
108
+ if (o.attrName !== "style") return;
109
+ const a = b(o.attrValue);
110
+ a ? o.attrValue = a : o.keepAttr = !1;
111
+ };
112
+ i.addHook("uponSanitizeAttribute", e);
113
+ const r = i.sanitize(t, {
114
+ ALLOWED_TAGS: [
115
+ "p",
116
+ "br",
117
+ "div",
118
+ "span",
119
+ "strong",
120
+ "b",
121
+ "em",
122
+ "i",
123
+ "u",
124
+ "s",
125
+ "ul",
126
+ "ol",
127
+ "li",
128
+ "blockquote",
129
+ "h1",
130
+ "h2",
131
+ "h3",
132
+ "h4",
133
+ "h5",
134
+ "h6",
135
+ "a",
136
+ "img",
137
+ "table",
138
+ "thead",
139
+ "tbody",
140
+ "tfoot",
141
+ "tr",
142
+ "th",
143
+ "td",
144
+ "caption",
145
+ "colgroup",
146
+ "col",
147
+ "center",
148
+ "font",
149
+ "hr",
150
+ "pre",
151
+ "code",
152
+ "sub",
153
+ "sup",
154
+ "small",
155
+ "big",
156
+ "figure",
157
+ "figcaption",
158
+ "section",
159
+ "article",
160
+ "header",
161
+ "footer",
162
+ "abbr",
163
+ "cite",
164
+ "mark",
165
+ "del",
166
+ "ins"
167
+ ],
168
+ ALLOWED_ATTR: [
169
+ "href",
170
+ "target",
171
+ "rel",
172
+ "src",
173
+ "alt",
174
+ "title",
175
+ "width",
176
+ "height",
177
+ "style",
178
+ "class",
179
+ "align",
180
+ "valign",
181
+ "colspan",
182
+ "rowspan",
183
+ "cellpadding",
184
+ "cellspacing",
185
+ "border",
186
+ "color",
187
+ "bgcolor"
188
+ ],
189
+ ALLOW_UNKNOWN_PROTOCOLS: !1
190
+ });
191
+ return i.removeHook("uponSanitizeAttribute"), r;
192
+ };
113
193
  export {
114
- h as escapeFormulaInjection,
115
- u as isAllowedImageUrl,
194
+ w as escapeFormulaInjection,
195
+ L as isAllowedImageUrl,
116
196
  A as isAllowedLinkUrl,
117
- s as isAllowedUrl,
118
- f as sanitizeBasicHtml,
119
- E as sanitizeEmailHtml,
197
+ c as isAllowedUrl,
198
+ E as sanitizeBasicHtml,
199
+ b as sanitizeCssProperties,
200
+ T as sanitizeEmailHtml,
120
201
  _ as sanitizePlainText,
121
- T as stripHtmlTags
202
+ O as stripHtmlTags
122
203
  };
123
204
  //# sourceMappingURL=sanitize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.js","sources":["../../../src/helpers/sanitize.ts"],"sourcesContent":["import DOMPurify from 'dompurify';\n\n/**\n * Central HTML/CSS-injection sanitisation for the component library.\n *\n * Two categories:\n *\n * INPUT layer (onChange) — strips tags, keeps plain text:\n * - stripHtmlTags: remove every HTML tag, return raw text\n * - escapeFormulaInjection: prefix CSV-dangerous leading chars with '\n *\n * RENDER layer (dangerouslySetInnerHTML / innerHTML) — DOMPurify-based:\n * - sanitizePlainText: ALLOWED_TAGS: [] (for sinks that must show plain text)\n * - sanitizeBasicHtml: formatting allowlist (chat bubbles, rich previews)\n * - sanitizeEmailHtml: full email allowlist (tables, images, inline style)\n */\n\nconst hasDOM = (): boolean => typeof window !== 'undefined';\n\nconst REGEX_TAG_STRIP = /<\\/?[^>]+(>|$)/g;\n\nconst ALLOWED_LINK_PROTOCOLS = new Set(['https:', 'mailto:', 'tel:']);\nconst ALLOWED_IMG_PROTOCOLS = new Set(['https:', 'data:']);\n\nexport const isAllowedUrl = (\n\turl: string | null | undefined,\n\tallowed: Set<string>,\n): string | null => {\n\tif (!url) return null;\n\ttry {\n\t\tconst parsed = new URL(url, 'https://bik.local');\n\t\treturn allowed.has(parsed.protocol) ? url : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nexport const isAllowedLinkUrl = (url?: string | null): boolean =>\n\tisAllowedUrl(url, ALLOWED_LINK_PROTOCOLS) !== null;\n\nexport const isAllowedImageUrl = (url?: string | null): boolean =>\n\tisAllowedUrl(url, ALLOWED_IMG_PROTOCOLS) !== null;\n\n// ---------------------------------------------------------------------------\n// INPUT layer — for onChange handlers on plain-text inputs\n// ---------------------------------------------------------------------------\n\n/**\n * Strip complete HTML tags from the input, return the raw text content.\n * Uses a regex that only matches tags with both < and >, so partial input\n * like \"<h\" while typing is preserved (no closing >, not a tag yet).\n *\n * \"<h1>Hello</h1>\" → \"Hello\"\n * \"<h\" → \"<h\" (incomplete, kept)\n * \"2 < 3\" → \"2 < 3\"\n * \"<script>x</script>\" → \"x\"\n */\nconst COMPLETE_TAG = /<\\/?[a-zA-Z][a-zA-Z0-9]*(?:\\s[^>]*)?\\/?>/g;\n\nexport const stripHtmlTags = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\treturn input.replace(COMPLETE_TAG, '');\n};\n\nconst CSV_FORMULA_PREFIX = /^[=+\\-@\\t\\r]/;\n\n/**\n * Escape CSV/Excel formula injection. Prefixes values starting with a\n * formula-triggering character (=, +, -, @, tab, CR) with a single quote\n * so spreadsheet software treats them as literal text.\n */\nexport const escapeFormulaInjection = (\n\tinput: string | null | undefined,\n): string => {\n\tif (!input) return '';\n\treturn CSV_FORMULA_PREFIX.test(input) ? `'${input}` : input;\n};\n\n// ---------------------------------------------------------------------------\n// RENDER layer — for dangerouslySetInnerHTML / innerHTML sinks\n// ---------------------------------------------------------------------------\n\n/**\n * Render-safe plain text. Strips every tag and returns safe HTML (with\n * entities). Use at render sinks that must show plain text via innerHTML.\n * For input onChange, use stripHtmlTags instead.\n */\nexport const sanitizePlainText = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\tif (!hasDOM()) return input.replace(REGEX_TAG_STRIP, '');\n\treturn DOMPurify.sanitize(input, {\n\t\tALLOWED_TAGS: [],\n\t\tALLOWED_ATTR: [],\n\t\tKEEP_CONTENT: true,\n\t});\n};\n\n/**\n * Basic rich text. A small formatting allowlist; no style tag/attr, links\n * restricted to safe protocols.\n */\nexport const sanitizeBasicHtml = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\tif (!hasDOM()) return input.replace(REGEX_TAG_STRIP, '');\n\treturn DOMPurify.sanitize(input, {\n\t\tALLOWED_TAGS: [\n\t\t\t'p',\n\t\t\t'br',\n\t\t\t'b',\n\t\t\t'strong',\n\t\t\t'i',\n\t\t\t'em',\n\t\t\t'u',\n\t\t\t's',\n\t\t\t'span',\n\t\t\t'a',\n\t\t\t'ul',\n\t\t\t'ol',\n\t\t\t'li',\n\t\t\t'blockquote',\n\t\t],\n\t\tALLOWED_ATTR: ['href', 'target', 'rel', 'class'],\n\t\tFORBID_TAGS: ['style'],\n\t\tFORBID_ATTR: ['style'],\n\t\tALLOWED_URI_REGEXP: /^(?:https|mailto|tel):/i,\n\t});\n};\n\n/**\n * Full rich text / inbound email HTML. Allowlist of tags and attributes an\n * email needs to render; everything else is stripped.\n */\nexport const sanitizeEmailHtml = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\tif (!hasDOM()) return input.replace(REGEX_TAG_STRIP, '');\n\treturn DOMPurify.sanitize(input, {\n\t\tALLOWED_TAGS: [\n\t\t\t'p',\n\t\t\t'br',\n\t\t\t'div',\n\t\t\t'span',\n\t\t\t'strong',\n\t\t\t'b',\n\t\t\t'em',\n\t\t\t'i',\n\t\t\t'u',\n\t\t\t's',\n\t\t\t'ul',\n\t\t\t'ol',\n\t\t\t'li',\n\t\t\t'blockquote',\n\t\t\t'h1',\n\t\t\t'h2',\n\t\t\t'h3',\n\t\t\t'h4',\n\t\t\t'h5',\n\t\t\t'h6',\n\t\t\t'a',\n\t\t\t'img',\n\t\t\t'table',\n\t\t\t'thead',\n\t\t\t'tbody',\n\t\t\t'tfoot',\n\t\t\t'tr',\n\t\t\t'th',\n\t\t\t'td',\n\t\t\t'caption',\n\t\t\t'colgroup',\n\t\t\t'col',\n\t\t\t'center',\n\t\t\t'font',\n\t\t\t'hr',\n\t\t\t'pre',\n\t\t\t'code',\n\t\t\t'sub',\n\t\t\t'sup',\n\t\t\t'small',\n\t\t\t'big',\n\t\t\t'figure',\n\t\t\t'figcaption',\n\t\t\t'section',\n\t\t\t'article',\n\t\t\t'header',\n\t\t\t'footer',\n\t\t\t'abbr',\n\t\t\t'cite',\n\t\t\t'mark',\n\t\t\t'del',\n\t\t\t'ins',\n\t\t],\n\t\tALLOWED_ATTR: [\n\t\t\t'href',\n\t\t\t'target',\n\t\t\t'rel',\n\t\t\t'src',\n\t\t\t'alt',\n\t\t\t'title',\n\t\t\t'width',\n\t\t\t'height',\n\t\t\t'style',\n\t\t\t'class',\n\t\t\t'align',\n\t\t\t'valign',\n\t\t\t'colspan',\n\t\t\t'rowspan',\n\t\t\t'cellpadding',\n\t\t\t'cellspacing',\n\t\t\t'border',\n\t\t\t'color',\n\t\t\t'bgcolor',\n\t\t],\n\t\tALLOW_UNKNOWN_PROTOCOLS: false,\n\t});\n};\n"],"names":["hasDOM","REGEX_TAG_STRIP","ALLOWED_LINK_PROTOCOLS","ALLOWED_IMG_PROTOCOLS","isAllowedUrl","url","allowed","parsed","isAllowedLinkUrl","isAllowedImageUrl","COMPLETE_TAG","stripHtmlTags","input","CSV_FORMULA_PREFIX","escapeFormulaInjection","sanitizePlainText","DOMPurify","sanitizeBasicHtml","sanitizeEmailHtml"],"mappings":";AAiBA,MAAMA,IAAS,MAAe,OAAO,SAAW,KAE1CC,IAAkB,mBAElBC,IAAyB,oBAAI,IAAI,CAAC,UAAU,WAAW,MAAM,CAAC,GAC9DC,IAAwB,oBAAI,IAAI,CAAC,UAAU,OAAO,CAAC,GAE5CC,IAAe,CAC3BC,GACAC,MACmB;AACnB,MAAI,CAACD,EAAK,QAAO;AACjB,MAAI;AACH,UAAME,IAAS,IAAI,IAAIF,GAAK,mBAAmB;AAC/C,WAAOC,EAAQ,IAAIC,EAAO,QAAQ,IAAIF,IAAM;AAAA,EAC7C,QAAQ;AACP,WAAO;AAAA,EACR;AACD,GAEaG,IAAmB,CAACH,MAChCD,EAAaC,GAAKH,CAAsB,MAAM,MAElCO,IAAoB,CAACJ,MACjCD,EAAaC,GAAKF,CAAqB,MAAM,MAgBxCO,IAAe,6CAERC,IAAgB,CAACC,MACxBA,IACEA,EAAM,QAAQF,GAAc,EAAE,IADlB,IAIdG,IAAqB,gBAOdC,IAAyB,CACrCF,MAEKA,IACEC,EAAmB,KAAKD,CAAK,IAAI,IAAIA,CAAK,KAAKA,IADnC,IAaPG,IAAoB,CAACH,MAC5BA,IACAZ,EAAA,IACEgB,EAAU,SAASJ,GAAO;AAAA,EAChC,cAAc,CAAA;AAAA,EACd,cAAc,CAAA;AAAA,EACd,cAAc;AAAA,CACd,IALqBA,EAAM,QAAQX,GAAiB,EAAE,IADpC,IAaPgB,IAAoB,CAACL,MAC5BA,IACAZ,EAAA,IACEgB,EAAU,SAASJ,GAAO;AAAA,EAChC,cAAc;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAED,cAAc,CAAC,QAAQ,UAAU,OAAO,OAAO;AAAA,EAC/C,aAAa,CAAC,OAAO;AAAA,EACrB,aAAa,CAAC,OAAO;AAAA,EACrB,oBAAoB;AAAA,CACpB,IAtBqBA,EAAM,QAAQX,GAAiB,EAAE,IADpC,IA8BPiB,IAAoB,CAACN,MAC5BA,IACAZ,EAAA,IACEgB,EAAU,SAASJ,GAAO;AAAA,EAChC,cAAc;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAED,cAAc;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAED,yBAAyB;AAAA,CACzB,IA9EqBA,EAAM,QAAQX,GAAiB,EAAE,IADpC;"}
1
+ {"version":3,"file":"sanitize.js","sources":["../../../src/helpers/sanitize.ts"],"sourcesContent":["import DOMPurify from 'dompurify';\n\n/**\n * Central HTML/CSS-injection sanitisation for the component library.\n *\n * Two categories:\n *\n * INPUT layer (onChange) — strips tags, keeps plain text:\n * - stripHtmlTags: remove every HTML tag, return raw text\n * - escapeFormulaInjection: prefix CSV-dangerous leading chars with '\n *\n * RENDER layer (dangerouslySetInnerHTML / innerHTML) — DOMPurify-based:\n * - sanitizePlainText: ALLOWED_TAGS: [] (for sinks that must show plain text)\n * - sanitizeBasicHtml: formatting allowlist (chat bubbles, rich previews)\n * - sanitizeEmailHtml: full email allowlist (tables, images, inline style)\n */\n\nconst hasDOM = (): boolean => typeof window !== 'undefined';\n\nconst REGEX_TAG_STRIP = /<\\/?[^>]+(>|$)/g;\n\nconst ALLOWED_LINK_PROTOCOLS = new Set(['https:', 'mailto:', 'tel:']);\nconst ALLOWED_IMG_PROTOCOLS = new Set(['https:', 'data:']);\n\nexport const isAllowedUrl = (\n\turl: string | null | undefined,\n\tallowed: Set<string>,\n): string | null => {\n\tif (!url) return null;\n\ttry {\n\t\tconst parsed = new URL(url, 'https://bik.local');\n\t\treturn allowed.has(parsed.protocol) ? url : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nexport const isAllowedLinkUrl = (url?: string | null): boolean =>\n\tisAllowedUrl(url, ALLOWED_LINK_PROTOCOLS) !== null;\n\nexport const isAllowedImageUrl = (url?: string | null): boolean =>\n\tisAllowedUrl(url, ALLOWED_IMG_PROTOCOLS) !== null;\n\n// ---------------------------------------------------------------------------\n// INPUT layer — for onChange handlers on plain-text inputs\n// ---------------------------------------------------------------------------\n\n/**\n * Strip complete HTML tags from the input, return the raw text content.\n * Uses a regex that only matches tags with both < and >, so partial input\n * like \"<h\" while typing is preserved (no closing >, not a tag yet).\n *\n * \"<h1>Hello</h1>\" → \"Hello\"\n * \"<h\" → \"<h\" (incomplete, kept)\n * \"2 < 3\" → \"2 < 3\"\n * \"<script>x</script>\" → \"x\"\n */\nconst COMPLETE_TAG = /<\\/?[a-zA-Z][a-zA-Z0-9]*(?:\\s[^>]*)?\\/?>/g;\n\nexport const stripHtmlTags = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\treturn input.replace(COMPLETE_TAG, '');\n};\n\nconst CSV_FORMULA_PREFIX = /^[=+\\-@\\t\\r]/;\n\n/**\n * Escape CSV/Excel formula injection. Prefixes values starting with a\n * formula-triggering character (=, +, -, @, tab, CR) with a single quote\n * so spreadsheet software treats them as literal text.\n */\nexport const escapeFormulaInjection = (\n\tinput: string | null | undefined,\n): string => {\n\tif (!input) return '';\n\treturn CSV_FORMULA_PREFIX.test(input) ? `'${input}` : input;\n};\n\n// ---------------------------------------------------------------------------\n// RENDER layer — for dangerouslySetInnerHTML / innerHTML sinks\n// ---------------------------------------------------------------------------\n\n/**\n * Render-safe plain text. Strips every tag and returns safe HTML (with\n * entities). Use at render sinks that must show plain text via innerHTML.\n * For input onChange, use stripHtmlTags instead.\n */\nexport const sanitizePlainText = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\tif (!hasDOM()) return input.replace(REGEX_TAG_STRIP, '');\n\treturn DOMPurify.sanitize(input, {\n\t\tALLOWED_TAGS: [],\n\t\tALLOWED_ATTR: [],\n\t\tKEEP_CONTENT: true,\n\t});\n};\n\n/**\n * Basic rich text. A small formatting allowlist; no style tag/attr, links\n * restricted to safe protocols.\n */\nexport const sanitizeBasicHtml = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\tif (!hasDOM()) return input.replace(REGEX_TAG_STRIP, '');\n\treturn DOMPurify.sanitize(input, {\n\t\tALLOWED_TAGS: [\n\t\t\t'p',\n\t\t\t'br',\n\t\t\t'b',\n\t\t\t'strong',\n\t\t\t'i',\n\t\t\t'em',\n\t\t\t'u',\n\t\t\t's',\n\t\t\t'span',\n\t\t\t'a',\n\t\t\t'ul',\n\t\t\t'ol',\n\t\t\t'li',\n\t\t\t'blockquote',\n\t\t],\n\t\tALLOWED_ATTR: ['href', 'target', 'rel', 'class'],\n\t\tFORBID_TAGS: ['style'],\n\t\tFORBID_ATTR: ['style'],\n\t\tALLOWED_URI_REGEXP: /^(?:https|mailto|tel):/i,\n\t});\n};\n\n// CSS properties safe for email rendering. Everything else is stripped from\n// inline style attributes to block position-overlay, url()-exfiltration,\n// expression(), and -moz-binding attacks.\nconst SAFE_CSS_PROPERTIES = new Set([\n\t'color',\n\t'background-color',\n\t'background',\n\t'font-family',\n\t'font-size',\n\t'font-weight',\n\t'font-style',\n\t'font-variant',\n\t'font',\n\t'line-height',\n\t'letter-spacing',\n\t'word-spacing',\n\t'text-align',\n\t'text-decoration',\n\t'text-transform',\n\t'text-indent',\n\t'vertical-align',\n\t'white-space',\n\t'word-wrap',\n\t'word-break',\n\t'overflow-wrap',\n\t'direction',\n\t'margin',\n\t'margin-top',\n\t'margin-right',\n\t'margin-bottom',\n\t'margin-left',\n\t'padding',\n\t'padding-top',\n\t'padding-right',\n\t'padding-bottom',\n\t'padding-left',\n\t'border',\n\t'border-top',\n\t'border-right',\n\t'border-bottom',\n\t'border-left',\n\t'border-width',\n\t'border-style',\n\t'border-color',\n\t'border-collapse',\n\t'border-spacing',\n\t'border-radius',\n\t'width',\n\t'min-width',\n\t'max-width',\n\t'height',\n\t'min-height',\n\t'max-height',\n\t'display',\n\t'visibility',\n\t'opacity',\n\t'float',\n\t'clear',\n\t'overflow',\n\t'overflow-x',\n\t'overflow-y',\n\t'table-layout',\n\t'list-style',\n\t'list-style-type',\n\t'list-style-position',\n\t'box-sizing',\n]);\n\n// Values that indicate CSS injection attempts regardless of property.\nconst DANGEROUS_CSS_VALUE =\n\t/url\\s*\\(|expression\\s*\\(|-moz-binding|behavior\\s*:|@import|javascript:|data:/i;\n\n/**\n * Filter an inline style string to only safe CSS properties with safe values.\n * Returns the cleaned style string (empty string if nothing survives).\n */\nexport const sanitizeCssProperties = (style: string): string => {\n\treturn style\n\t\t.split(';')\n\t\t.map((decl) => decl.trim())\n\t\t.filter((decl) => {\n\t\t\tif (!decl) return false;\n\t\t\tconst colonIdx = decl.indexOf(':');\n\t\t\tif (colonIdx < 0) return false;\n\t\t\tconst prop = decl.slice(0, colonIdx).trim().toLowerCase();\n\t\t\tconst value = decl.slice(colonIdx + 1).trim();\n\t\t\treturn SAFE_CSS_PROPERTIES.has(prop) && !DANGEROUS_CSS_VALUE.test(value);\n\t\t})\n\t\t.join('; ');\n};\n\n/**\n * Full rich text / inbound email HTML. Allowlist of tags and attributes an\n * email needs to render; everything else is stripped. Inline style attributes\n * are filtered to a safe CSS property allowlist.\n */\nexport const sanitizeEmailHtml = (input: string | null | undefined): string => {\n\tif (!input) return '';\n\tif (!hasDOM()) return input.replace(REGEX_TAG_STRIP, '');\n\n\tconst hook = (\n\t\t_node: Element,\n\t\tdata: { attrName: string; attrValue: string; keepAttr: boolean },\n\t) => {\n\t\tif (data.attrName !== 'style') return;\n\t\tconst clean = sanitizeCssProperties(data.attrValue);\n\t\tif (clean) {\n\t\t\tdata.attrValue = clean;\n\t\t} else {\n\t\t\tdata.keepAttr = false;\n\t\t}\n\t};\n\tDOMPurify.addHook('uponSanitizeAttribute', hook);\n\n\tconst result = DOMPurify.sanitize(input, {\n\t\tALLOWED_TAGS: [\n\t\t\t'p',\n\t\t\t'br',\n\t\t\t'div',\n\t\t\t'span',\n\t\t\t'strong',\n\t\t\t'b',\n\t\t\t'em',\n\t\t\t'i',\n\t\t\t'u',\n\t\t\t's',\n\t\t\t'ul',\n\t\t\t'ol',\n\t\t\t'li',\n\t\t\t'blockquote',\n\t\t\t'h1',\n\t\t\t'h2',\n\t\t\t'h3',\n\t\t\t'h4',\n\t\t\t'h5',\n\t\t\t'h6',\n\t\t\t'a',\n\t\t\t'img',\n\t\t\t'table',\n\t\t\t'thead',\n\t\t\t'tbody',\n\t\t\t'tfoot',\n\t\t\t'tr',\n\t\t\t'th',\n\t\t\t'td',\n\t\t\t'caption',\n\t\t\t'colgroup',\n\t\t\t'col',\n\t\t\t'center',\n\t\t\t'font',\n\t\t\t'hr',\n\t\t\t'pre',\n\t\t\t'code',\n\t\t\t'sub',\n\t\t\t'sup',\n\t\t\t'small',\n\t\t\t'big',\n\t\t\t'figure',\n\t\t\t'figcaption',\n\t\t\t'section',\n\t\t\t'article',\n\t\t\t'header',\n\t\t\t'footer',\n\t\t\t'abbr',\n\t\t\t'cite',\n\t\t\t'mark',\n\t\t\t'del',\n\t\t\t'ins',\n\t\t],\n\t\tALLOWED_ATTR: [\n\t\t\t'href',\n\t\t\t'target',\n\t\t\t'rel',\n\t\t\t'src',\n\t\t\t'alt',\n\t\t\t'title',\n\t\t\t'width',\n\t\t\t'height',\n\t\t\t'style',\n\t\t\t'class',\n\t\t\t'align',\n\t\t\t'valign',\n\t\t\t'colspan',\n\t\t\t'rowspan',\n\t\t\t'cellpadding',\n\t\t\t'cellspacing',\n\t\t\t'border',\n\t\t\t'color',\n\t\t\t'bgcolor',\n\t\t],\n\t\tALLOW_UNKNOWN_PROTOCOLS: false,\n\t});\n\n\tDOMPurify.removeHook('uponSanitizeAttribute');\n\treturn result;\n};\n"],"names":["hasDOM","REGEX_TAG_STRIP","ALLOWED_LINK_PROTOCOLS","ALLOWED_IMG_PROTOCOLS","isAllowedUrl","url","allowed","parsed","isAllowedLinkUrl","isAllowedImageUrl","COMPLETE_TAG","stripHtmlTags","input","CSV_FORMULA_PREFIX","escapeFormulaInjection","sanitizePlainText","DOMPurify","sanitizeBasicHtml","SAFE_CSS_PROPERTIES","DANGEROUS_CSS_VALUE","sanitizeCssProperties","style","decl","colonIdx","prop","value","sanitizeEmailHtml","hook","_node","data","clean","result"],"mappings":"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"}