{"version":3,"sources":["../../src/utils/slugify.ts","../../src/utils/sanitize.ts","../../src/utils/parse-heading-id.ts","../../src/utils/width-guard.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Convert a string to a URL-safe slug for heading IDs\n */\nexport function slugify(text: string): string {\n  return text\n    .toString()\n    .toLowerCase()\n    .trim()\n    .replace(/\\s+/g, \"-\") // Replace spaces with -\n    .replace(/[^\\w\\-]+/g, \"\") // Remove all non-word chars\n    .replace(/\\-\\-+/g, \"-\") // Replace multiple - with single -\n    .replace(/^-+/, \"\") // Trim - from start of text\n    .replace(/-+$/, \"\"); // Trim - from end of text\n}\n\n/**\n * Generate a unique ID for a heading, with collision tracking\n */\nconst usedIds = new Set<string>();\n\nexport function generateHeadingId(text: string, resetTracking = false): string {\n  if (resetTracking) {\n    usedIds.clear();\n  }\n\n  const baseSlug = slugify(text);\n  let slug = baseSlug;\n  let counter = 1;\n\n  while (usedIds.has(slug)) {\n    slug = `${baseSlug}-${counter}`;\n    counter++;\n  }\n\n  usedIds.add(slug);\n  return slug;\n}\n\n/**\n * Reset heading ID tracking (useful for testing or new renders)\n */\nexport function resetHeadingIds(): void {\n  usedIds.clear();\n}\n","/**\n * Basic HTML sanitization for markdown content\n * Removes potentially dangerous attributes and protocols\n */\n\nconst DANGEROUS_PROTOCOLS = [\"javascript:\", \"data:\", \"vbscript:\"];\nconst DANGEROUS_ATTRIBUTES = [\n  \"onerror\",\n  \"onload\",\n  \"onclick\",\n  \"onmouseover\",\n  \"onfocus\",\n  \"onblur\",\n];\n\n/**\n * Check if a URL uses a dangerous protocol\n */\nexport function isDangerousUrl(url: string): boolean {\n  const lowercaseUrl = url.toLowerCase().trim();\n  return DANGEROUS_PROTOCOLS.some((protocol) =>\n    lowercaseUrl.startsWith(protocol)\n  );\n}\n\n/**\n * Sanitize a URL by removing dangerous protocols\n */\nexport function sanitizeUrl(url: string): string {\n  if (isDangerousUrl(url)) {\n    return \"#\";\n  }\n  return url;\n}\n\n/**\n * Check if an attribute name is dangerous\n */\nexport function isDangerousAttribute(attr: string): boolean {\n  return DANGEROUS_ATTRIBUTES.some((dangerous) =>\n    attr.toLowerCase().startsWith(dangerous)\n  );\n}\n\n/**\n * Sanitize HTML attributes by removing dangerous ones\n * This is a basic implementation - for production use, consider DOMPurify\n */\nexport function sanitizeAttributes(\n  attributes: Record<string, any>\n): Record<string, any> {\n  const sanitized: Record<string, any> = {};\n\n  for (const [key, value] of Object.entries(attributes)) {\n    // Skip dangerous attributes\n    if (isDangerousAttribute(key)) {\n      continue;\n    }\n\n    // Sanitize URL attributes\n    if (\n      (key === \"href\" || key === \"src\") &&\n      typeof value === \"string\" &&\n      isDangerousUrl(value)\n    ) {\n      sanitized[key] = \"#\";\n    } else {\n      sanitized[key] = value;\n    }\n  }\n\n  return sanitized;\n}\n\n/**\n * Get default sanitization configuration\n */\nexport function getDefaultSanitizationConfig() {\n  return {\n    disableParsingRawHTML: false,\n    forceBlock: false,\n    forceInline: false,\n  };\n}\n","/**\n * Parses custom ID syntax from markdown headings\n * Supports the {#id} pattern commonly used by AI LLMs and extended markdown\n *\n * @example\n * ```ts\n * parseHeadingId('Hello World {#custom-id}')\n * // Returns: { text: 'Hello World', id: 'custom-id' }\n *\n * parseHeadingId('No ID Here')\n * // Returns: { text: 'No ID Here', id: null }\n * ```\n */\nexport function parseHeadingId(text: string): { text: string; id: string | null } {\n  // Match {#id} pattern at the end of the heading\n  const match = text.match(/^(.+?)\\s*\\{#([a-zA-Z0-9_-]+)\\}\\s*$/);\n\n  if (match) {\n    return {\n      text: match[1].trim(),\n      id: match[2],\n    };\n  }\n\n  return {\n    text,\n    id: null,\n  };\n}\n\n/**\n * Preprocesses markdown content to extract custom heading IDs\n * and store them for later use during rendering\n *\n * @param markdown - Raw markdown string\n * @returns Object with processed markdown and ID mappings\n */\nexport function preprocessHeadingIds(markdown: string): {\n  processedMarkdown: string;\n  headingIds: Map<string, string>;\n} {\n  const headingIds = new Map<string, string>();\n\n  // Match markdown headings (# through ######)\n  const processedMarkdown = markdown.replace(\n    /^(#{1,6})\\s+(.+?)(?:\\s*\\{#([a-zA-Z0-9_-]+)\\}\\s*)?$/gm,\n    (match, hashes, text, customId) => {\n      if (customId) {\n        // Store the mapping of text to custom ID\n        const cleanText = text.trim();\n        headingIds.set(cleanText, customId);\n        // Return heading without the {#id} syntax\n        return `${hashes} ${cleanText}`;\n      }\n      return match;\n    }\n  );\n\n  return {\n    processedMarkdown,\n    headingIds,\n  };\n}\n","import type { CSSProperties } from \"react\";\n\n/**\n * Width-containment styles for markdown-rendered content.\n *\n * ## Why these are inline styles and not classes\n *\n * Production customer sites are served a **safelist-compiled** Tailwind\n * stylesheet: a utility class that was not present in the extraction snapshot\n * ships with no rule at all. Inline styles are immune to that failure mode, so\n * every guard in this module is expressed as a plain style object.\n *\n * ## The failure this prevents\n *\n * A single wide descendant (a GFM table, a `<pre>` with a long line, a raw\n * `<iframe width=\"1200\">`, an unbroken URL) has a min-content width larger than\n * a phone viewport. When the markdown output is nested inside a CSS grid or flex\n * item whose `min-width` resolves to `auto`, that min-content width becomes the\n * track's base size, the *whole column* grows past the viewport, and every\n * sibling in the layout (headline, byline, floating navbar) is dragged wide with\n * it. The page then scrolls horizontally and looks broken - not just the table.\n *\n * The guards below are layered so no single element can do that:\n *\n * 1. `MARKDOWN_CONTENT_GUARD_STYLE` on the markdown wrapper - caps the content\n *    box at its parent's width, allows long tokens to break, and clips (never\n *    scrolls) anything that still escapes.\n * 2. `SCROLL_CONTAINER_GUARD_STYLE` on tables and code blocks - the wide child\n *    scrolls *inside* the article column instead of widening the page.\n * 3. `MEDIA_GUARD_STYLE` on images and iframes - intrinsic/attribute widths can\n *    never exceed the column.\n *\n * @packageDocumentation\n */\n\n/**\n * Applied to the markdown wrapper element (the `wrapper` option of\n * {@link Markdown}, a `<div>` by default).\n *\n * - `minWidth: 0` - lets the wrapper shrink when it is itself a flex/grid item.\n * - `maxWidth: \"100%\"` - never wider than the column it was given.\n * - `overflowWrap: \"break-word\"` - long URLs and unbroken words wrap instead of\n *   spilling out of the column.\n * - `overflowX: \"clip\"` - last-resort containment. `clip` is used rather than\n *   `hidden`/`auto` because it does **not** create a scroll container, so it\n *   cannot introduce a second horizontal scrollbar, cannot trap the page's\n *   scroll position, and leaves the vertical axis `visible` (so `position:\n *   sticky` and overflowing decorations elsewhere keep working). Browsers that\n *   do not support `clip` simply drop the declaration and fall back to the\n *   other guards.\n */\nexport const MARKDOWN_CONTENT_GUARD_STYLE: CSSProperties = {\n  minWidth: 0,\n  maxWidth: \"100%\",\n  overflowWrap: \"break-word\",\n  overflowX: \"clip\",\n};\n\n/**\n * Applied to the scroll wrapper around a table, and to `<pre>` blocks.\n *\n * The wide child is allowed to keep its natural width but scrolls *inside* this\n * box. `overscrollBehaviorX: \"contain\"` stops a horizontal swipe inside a table\n * from chaining out to the page/back-navigation gesture on touch devices.\n */\nexport const SCROLL_CONTAINER_GUARD_STYLE: CSSProperties = {\n  maxWidth: \"100%\",\n  minWidth: 0,\n  overflowX: \"auto\",\n  overflowY: \"hidden\",\n  overscrollBehaviorX: \"contain\",\n};\n\n/**\n * Applied to replaced elements rendered from markdown whose height must be left\n * alone - currently `<iframe>`, where the embed's declared height is meaningful.\n *\n * Markdown routinely carries raw embed HTML with a hard-coded `width=\"1200\"`,\n * which on a phone would widen the article column and the whole page.\n */\nexport const MEDIA_GUARD_STYLE: CSSProperties = {\n  maxWidth: \"100%\",\n};\n\n/**\n * Applied to `<img>`.\n *\n * An image's intrinsic width is unbounded, so it is capped to the column. `height:\n * auto` is required alongside the cap: without it, a `height` attribute (or a\n * `height` from raw HTML) is honoured literally while the width is clamped, and\n * the image renders distorted.\n *\n * Consumer sites that ship Tailwind Preflight already get `img { max-width: 100%;\n * height: auto }`. This guard makes the behaviour independent of the host\n * stylesheet.\n */\nexport const IMAGE_GUARD_STYLE: CSSProperties = {\n  maxWidth: \"100%\",\n  height: \"auto\",\n};\n\n/**\n * Applied to inline `<code>`.\n *\n * `anywhere` (rather than `break-word`) is deliberate: it also reduces the\n * element's min-content contribution, so a long inline token cannot inflate an\n * ancestor grid/flex track even before the overflow guards engage.\n */\nexport const INLINE_CODE_GUARD_STYLE: CSSProperties = {\n  overflowWrap: \"anywhere\",\n};\n\n/**\n * Merge a guard style with a caller-supplied `style` prop.\n *\n * Caller values win, so a consumer can always opt out of an individual\n * declaration (for example `style={{ overflowX: \"visible\" }}`) without having to\n * fork the component.\n */\nexport function mergeGuardStyle(\n  guard: CSSProperties,\n  style?: CSSProperties,\n): CSSProperties {\n  return style ? { ...guard, ...style } : guard;\n}\n"]}