/** * Markdown autoformat rule definitions for the docs editor. * * Pure data: each rule describes a regex pattern and what action it triggers. * This keeps the logic testable without requiring a TipTap instance. * * Most markdown autoformat rules are already provided by TipTap's built-in * extensions (via StarterKit and individual extensions). This module defines * only the additional rules that are NOT built-in, plus exports a complete * inventory of all active rules for documentation and testing. */ import type { AutoformatRule, AutoformatMatch, ParsedLink } from './types.js'; /** * Regex for markdown-style links: [text](url) * Captures: [1] = full match with optional leading char, [2] = link text, [3] = URL * * This input rule is NOT provided by @tiptap/extension-link and must be * added via a custom extension. */ export const linkInputRegex = /(?:^|\s)\[([^\]]+)\]\(([^)]+)\)$/; /** * All active autoformat rules in the editor, including built-in ones. * Used for testing and documentation purposes. */ export const AUTOFORMAT_RULES: AutoformatRule[] = [ // Block-level rules (built-in via StarterKit) { id: 'codeBlock', description: 'Triple backticks to code block', trigger: '``` + Space/Enter', regex: /^```([a-z]+)?[\s\n]$/, source: 'StarterKit (CodeBlock)', custom: false, }, { id: 'heading1', description: '# to Heading 1', trigger: '# + Space', regex: /^(#{1,1})\s$/, source: 'StarterKit (Heading)', custom: false, }, { id: 'heading2', description: '## to Heading 2', trigger: '## + Space', regex: /^(#{1,2})\s$/, source: 'StarterKit (Heading)', custom: false, }, { id: 'heading3', description: '### to Heading 3', trigger: '### + Space', regex: /^(#{1,3})\s$/, source: 'StarterKit (Heading)', custom: false, }, { id: 'blockquote', description: '> to blockquote', trigger: '> + Space', regex: /^\s*>\s$/, source: 'StarterKit (Blockquote)', custom: false, }, { id: 'bulletList', description: '- or * or + to bullet list', trigger: '- + Space', regex: /^\s*([-+*])\s$/, source: 'StarterKit (BulletList)', custom: false, }, { id: 'orderedList', description: '1. to ordered list', trigger: '1. + Space', regex: /^(\d+)\.\s$/, source: 'StarterKit (OrderedList)', custom: false, }, { id: 'taskItem', description: '[] or [ ] or [x] to task list', trigger: '[] + Space', regex: /^\s*(\[([( |x])?\])\s$/, source: 'TaskItem', custom: false, }, { id: 'horizontalRule', description: '--- or *** to horizontal rule', trigger: '---', regex: /^(?:---|—-|___\s|\*\*\*\s)$/, source: 'StarterKit (HorizontalRule)', custom: false, }, // Inline mark rules (built-in via StarterKit) { id: 'inlineCode', description: '`text` to inline code', trigger: '`text`', regex: /(^|[^`])`([^`]+)`(?!`)/, source: 'StarterKit (Code)', custom: false, }, { id: 'strikethrough', description: '~~text~~ to strikethrough', trigger: '~~text~~', regex: /(?:^|\s)(~~(?!\s+~~)((?:[^~]+))~~(?!\s+~~))$/, source: 'StarterKit (Strike)', custom: false, }, { id: 'italic', description: '*text* or _text_ to italic', trigger: '*text*', regex: /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/, source: 'StarterKit (Italic)', custom: false, }, { id: 'bold', description: '**text** or __text__ to bold', trigger: '**text**', regex: /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/, source: 'StarterKit (Bold)', custom: false, }, // Custom rules (we add these) { id: 'link', description: '[text](url) to link', trigger: '[text](url)', regex: linkInputRegex, source: 'MarkdownAutoformat (custom)', custom: true, }, ]; /** * Determine which autoformat rule (if any) matches the given input text. */ export function resolveAutoformat(text: string): AutoformatMatch | null { for (const rule of AUTOFORMAT_RULES) { const match = text.match(rule.regex); if (match) { return { id: rule.id, match }; } } return null; } /** * Parse a markdown link match into its components. */ export function parseLinkMatch(match: RegExpMatchArray): ParsedLink { return { text: match[1], href: match[2], }; }