Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 168 lines 4.4 kB view raw
1/** 2 * Markdown autoformat rule definitions for the docs editor. 3 * 4 * Pure data: each rule describes a regex pattern and what action it triggers. 5 * This keeps the logic testable without requiring a TipTap instance. 6 * 7 * Most markdown autoformat rules are already provided by TipTap's built-in 8 * extensions (via StarterKit and individual extensions). This module defines 9 * only the additional rules that are NOT built-in, plus exports a complete 10 * inventory of all active rules for documentation and testing. 11 */ 12import type { AutoformatRule, AutoformatMatch, ParsedLink } from './types.js'; 13 14/** 15 * Regex for markdown-style links: [text](url) 16 * Captures: [1] = full match with optional leading char, [2] = link text, [3] = URL 17 * 18 * This input rule is NOT provided by @tiptap/extension-link and must be 19 * added via a custom extension. 20 */ 21export const linkInputRegex = /(?:^|\s)\[([^\]]+)\]\(([^)]+)\)$/; 22 23/** 24 * All active autoformat rules in the editor, including built-in ones. 25 * Used for testing and documentation purposes. 26 */ 27export const AUTOFORMAT_RULES: AutoformatRule[] = [ 28 // Block-level rules (built-in via StarterKit) 29 { 30 id: 'codeBlock', 31 description: 'Triple backticks to code block', 32 trigger: '``` + Space/Enter', 33 regex: /^```([a-z]+)?[\s\n]$/, 34 source: 'StarterKit (CodeBlock)', 35 custom: false, 36 }, 37 { 38 id: 'heading1', 39 description: '# to Heading 1', 40 trigger: '# + Space', 41 regex: /^(#{1,1})\s$/, 42 source: 'StarterKit (Heading)', 43 custom: false, 44 }, 45 { 46 id: 'heading2', 47 description: '## to Heading 2', 48 trigger: '## + Space', 49 regex: /^(#{1,2})\s$/, 50 source: 'StarterKit (Heading)', 51 custom: false, 52 }, 53 { 54 id: 'heading3', 55 description: '### to Heading 3', 56 trigger: '### + Space', 57 regex: /^(#{1,3})\s$/, 58 source: 'StarterKit (Heading)', 59 custom: false, 60 }, 61 { 62 id: 'blockquote', 63 description: '> to blockquote', 64 trigger: '> + Space', 65 regex: /^\s*>\s$/, 66 source: 'StarterKit (Blockquote)', 67 custom: false, 68 }, 69 { 70 id: 'bulletList', 71 description: '- or * or + to bullet list', 72 trigger: '- + Space', 73 regex: /^\s*([-+*])\s$/, 74 source: 'StarterKit (BulletList)', 75 custom: false, 76 }, 77 { 78 id: 'orderedList', 79 description: '1. to ordered list', 80 trigger: '1. + Space', 81 regex: /^(\d+)\.\s$/, 82 source: 'StarterKit (OrderedList)', 83 custom: false, 84 }, 85 { 86 id: 'taskItem', 87 description: '[] or [ ] or [x] to task list', 88 trigger: '[] + Space', 89 regex: /^\s*(\[([( |x])?\])\s$/, 90 source: 'TaskItem', 91 custom: false, 92 }, 93 { 94 id: 'horizontalRule', 95 description: '--- or *** to horizontal rule', 96 trigger: '---', 97 regex: /^(?:---|—-|___\s|\*\*\*\s)$/, 98 source: 'StarterKit (HorizontalRule)', 99 custom: false, 100 }, 101 102 // Inline mark rules (built-in via StarterKit) 103 { 104 id: 'inlineCode', 105 description: '`text` to inline code', 106 trigger: '`text`', 107 regex: /(^|[^`])`([^`]+)`(?!`)/, 108 source: 'StarterKit (Code)', 109 custom: false, 110 }, 111 { 112 id: 'strikethrough', 113 description: '~~text~~ to strikethrough', 114 trigger: '~~text~~', 115 regex: /(?:^|\s)(~~(?!\s+~~)((?:[^~]+))~~(?!\s+~~))$/, 116 source: 'StarterKit (Strike)', 117 custom: false, 118 }, 119 { 120 id: 'italic', 121 description: '*text* or _text_ to italic', 122 trigger: '*text*', 123 regex: /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/, 124 source: 'StarterKit (Italic)', 125 custom: false, 126 }, 127 { 128 id: 'bold', 129 description: '**text** or __text__ to bold', 130 trigger: '**text**', 131 regex: /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/, 132 source: 'StarterKit (Bold)', 133 custom: false, 134 }, 135 136 // Custom rules (we add these) 137 { 138 id: 'link', 139 description: '[text](url) to link', 140 trigger: '[text](url)', 141 regex: linkInputRegex, 142 source: 'MarkdownAutoformat (custom)', 143 custom: true, 144 }, 145]; 146 147/** 148 * Determine which autoformat rule (if any) matches the given input text. 149 */ 150export function resolveAutoformat(text: string): AutoformatMatch | null { 151 for (const rule of AUTOFORMAT_RULES) { 152 const match = text.match(rule.regex); 153 if (match) { 154 return { id: rule.id, match }; 155 } 156 } 157 return null; 158} 159 160/** 161 * Parse a markdown link match into its components. 162 */ 163export function parseLinkMatch(match: RegExpMatchArray): ParsedLink { 164 return { 165 text: match[1], 166 href: match[2], 167 }; 168}