{"version":3,"file":"content-format.cjs","sources":["../../../../packages/components/src/editor/content-format.ts"],"sourcesContent":["import { marked } from 'marked'\nimport TurndownService from 'turndown'\nimport { gfm } from 'turndown-plugin-gfm'\nimport type { QxsBlocksuiteEditorContentFormat } from './types'\n\nexport const EMPTY_EDITOR_HTML = '<p></p>'\nconst EMPTY_PARAGRAPH_HTML = '<p></p>'\nconst EMPTY_PARAGRAPH_PATTERN = /^\\s*<p>(?:\\s|<br\\s*\\/?>|&nbsp;)*<\\/p>/i\nconst EMPTY_PARAGRAPH_PATTERN_TRAILING = /<p>(?:\\s|<br\\s*\\/?>|&nbsp;)*<\\/p>\\s*$/i\nconst EMPTY_LIST_ITEM_PLACEHOLDER = 'QXSEMPTYLISTITEMTOKEN'\n\nconst turndown = new TurndownService({\n  headingStyle: 'atx',\n  codeBlockStyle: 'fenced',\n})\n\nturndown.use(gfm)\n\nfunction normalizeMarkdownListFormatting(markdown: string): string {\n  return markdown\n    .replace(/\\n[ \\t]{4,}\\n(?=(?:\\s*(?:[-*+]|\\d+\\.)\\s))/g, '\\n')\n    .replace(/^(\\s*)[-*+]\\s+/gm, '$1- ')\n    .replace(/^(\\s*\\d+\\.)\\s+/gm, '$1 ')\n}\n\nfunction injectEmptyListItemPlaceholders(html: string) {\n  if (!html.includes('<li')) {\n    return html\n  }\n\n  if (typeof DOMParser === 'undefined') {\n    return html\n      .replace(\n        /<li([^>]*)>\\s*<p([^>]*)>(?:\\s|&nbsp;|<br\\s*\\/?>)*<\\/p>\\s*<\\/li>/gi,\n        `<li$1><p$2>${EMPTY_LIST_ITEM_PLACEHOLDER}</p></li>`,\n      )\n      .replace(\n        /<li([^>]*)>\\s*<\\/li>/gi,\n        `<li$1>${EMPTY_LIST_ITEM_PLACEHOLDER}</li>`,\n      )\n  }\n\n  const document = new DOMParser().parseFromString(html, 'text/html')\n  document.querySelectorAll('li').forEach((item) => {\n    if (\n      item.textContent?.trim()\n      || item.querySelector('img,video,table,pre,blockquote,ul,ol,hr,input')\n    ) {\n      return\n    }\n\n    const paragraph = item.querySelector(':scope > p')\n    if (paragraph) {\n      paragraph.textContent = EMPTY_LIST_ITEM_PLACEHOLDER\n      return\n    }\n\n    item.textContent = EMPTY_LIST_ITEM_PLACEHOLDER\n  })\n\n  return document.body.innerHTML\n}\n\nfunction restoreEmptyListItemsInMarkdown(markdown: string) {\n  return markdown.replace(\n    new RegExp(`(^[ \\\\t]*(?:- |\\\\d+\\\\. ))${EMPTY_LIST_ITEM_PLACEHOLDER}$`, 'gm'),\n    '$1',\n  )\n}\n\nfunction countLeadingBlankLines(value: string) {\n  const lines = value.replace(/\\r\\n?/g, '\\n').split('\\n')\n  let count = 0\n  while (count < lines.length && lines[count]?.trim() === '') {\n    count += 1\n  }\n  return count\n}\n\nfunction countTrailingBlankLines(value: string) {\n  const lines = value.replace(/\\r\\n?/g, '\\n').split('\\n')\n  let count = 0\n  for (let index = lines.length - 1; index >= 0; index -= 1) {\n    if (lines[index]?.trim() !== '') {\n      break\n    }\n    count += 1\n  }\n  return count\n}\n\nfunction stripBoundaryEmptyParagraphs(value: string) {\n  let html = value\n  let leading = 0\n  let trailing = 0\n\n  while (EMPTY_PARAGRAPH_PATTERN.test(html)) {\n    html = html.replace(EMPTY_PARAGRAPH_PATTERN, '')\n    leading += 1\n  }\n\n  while (EMPTY_PARAGRAPH_PATTERN_TRAILING.test(html)) {\n    html = html.replace(EMPTY_PARAGRAPH_PATTERN_TRAILING, '')\n    trailing += 1\n  }\n\n  return {\n    html,\n    leading,\n    trailing,\n  }\n}\n\nexport function transformIncomingContentByFormat(\n  value: string | null | undefined,\n  format: QxsBlocksuiteEditorContentFormat,\n): string {\n  const source = value ?? ''\n  const trimmed = source.trim()\n\n  if (!trimmed) {\n    return EMPTY_EDITOR_HTML\n  }\n\n  if (format === 'markdown') {\n    const leadingBlankLines = countLeadingBlankLines(source)\n    const trailingBlankLines = countTrailingBlankLines(source)\n    const contentHtml = (marked.parse(trimmed, { async: false, gfm: true }) as string) || EMPTY_EDITOR_HTML\n    return `${EMPTY_PARAGRAPH_HTML.repeat(leadingBlankLines)}${contentHtml}${EMPTY_PARAGRAPH_HTML.repeat(trailingBlankLines)}`\n  }\n\n  return source || EMPTY_EDITOR_HTML\n}\n\nexport function transformOutgoingContentByFormat(\n  value: string,\n  format: QxsBlocksuiteEditorContentFormat,\n): string {\n  const trimmed = value.trim()\n\n  if (!trimmed || trimmed === EMPTY_EDITOR_HTML) {\n    return ''\n  }\n\n  if (format === 'markdown') {\n    const { html, leading, trailing } = stripBoundaryEmptyParagraphs(value)\n    const normalizedHtml = html.trim()\n    const markdown = normalizedHtml\n      ? restoreEmptyListItemsInMarkdown(\n          normalizeMarkdownListFormatting(\n            turndown.turndown(injectEmptyListItemPlaceholders(normalizedHtml)).trim(),\n          ),\n        )\n      : ''\n    return `${'\\n'.repeat(leading)}${markdown}${'\\n'.repeat(trailing)}`\n  }\n\n  return value\n}\n"],"names":["EMPTY_EDITOR_HTML","EMPTY_PARAGRAPH_HTML","EMPTY_PARAGRAPH_PATTERN","EMPTY_PARAGRAPH_PATTERN_TRAILING","EMPTY_LIST_ITEM_PLACEHOLDER","turndown","TurndownService","gfm","normalizeMarkdownListFormatting","markdown","injectEmptyListItemPlaceholders","html","document","item","paragraph","restoreEmptyListItemsInMarkdown","countLeadingBlankLines","value","lines","count","countTrailingBlankLines","index","stripBoundaryEmptyParagraphs","leading","trailing","transformIncomingContentByFormat","format","source","trimmed","leadingBlankLines","trailingBlankLines","contentHtml","marked","transformOutgoingContentByFormat","normalizedHtml"],"mappings":"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"}