/**
* Markdown paste detection for the docs editor.
*
* Conservatively detects whether pasted plain text is markdown.
* Requires 2+ signals or 1 strong signal (heading, code block, table)
* to avoid false positives on normal text.
*
* Also detects when clipboard HTML contains unrendered markdown link
* syntax, indicating the source didn't convert markdown to rich text.
*/
const PATTERNS: readonly RegExp[] = [
/^#{1,6}\s+\S/m, // ATX headings (strong)
/^\s*[-*+]\s+\S/m, // Unordered lists
/^\s*\d+\.\s+\S/m, // Ordered lists
/^\s*```/m, // Fenced code blocks (strong)
/^\s*>\s+\S/m, // Blockquotes
/\[.+?\]\(.+?\)/, // Links
/!\[.*?\]\(.+?\)/, // Images
/^\s*[-*_]{3,}\s*$/m, // Horizontal rules
/^\s*\|.+\|.+\|/m, // Tables
/^\s*- \[([ xX])\]/m, // Task lists
];
// Strong solo signals — a single match is enough
const HEADING_RE = /^#{1,6}\s+\S/m;
const CODE_FENCE_RE = /^\s*```/m;
const TABLE_WITH_SEPARATOR_RE = /^\s*\|.+\|.+\|\s*\n\s*\|[\s:|-]+\|/m;
const MARKDOWN_LINK_RE = /\[.+?\]\(.+?\)/;
const TASK_LIST_RE = /^\s*- \[([ xX])\]/m;
export function looksLikeMarkdown(text: string): boolean {
if (!text || !text.trim()) return false;
let signals = 0;
for (const p of PATTERNS) {
if (p.test(text)) signals++;
}
if (signals >= 2) return true;
// Strong solo signals
if (HEADING_RE.test(text)) return true;
if (CODE_FENCE_RE.test(text)) return true;
if (TABLE_WITH_SEPARATOR_RE.test(text)) return true;
if (MARKDOWN_LINK_RE.test(text)) return true;
if (TASK_LIST_RE.test(text)) return true;
return false;
}
/**
* Check whether clipboard HTML contains unrendered markdown link syntax.
* When apps copy content, they may include both text/html and text/plain.
* If the HTML contains literal `[text](url)` strings (not rendered as tags),
* the plain text markdown path should be preferred.
*/
// Looser task list pattern for HTML context (no start-of-line anchor)
const RAW_TASK_LIST_IN_HTML = /- \[([ xX])\]\s/;
export function htmlContainsRawMarkdown(html: string): boolean {
if (!html) return false;
// Strip actual tags and their content to avoid false positives,
// then check if raw markdown link syntax remains in the text content.
const stripped = html.replace(/]*>.*?<\/a>/gi, '');
return MARKDOWN_LINK_RE.test(stripped) || RAW_TASK_LIST_IN_HTML.test(stripped);
}