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 86 lines 3.5 kB view raw
1/** 2 * Markdown autoformat extension for TipTap. 3 * 4 * Adds custom input rules that are NOT provided by built-in TipTap extensions. 5 * - [text](url) → clickable link 6 * - `- [ ] ` → checkbox task-list item (GFM task list). See #722. 7 * - `- [x] ` → checked task-list item. See #722. 8 * 9 * Other rules (headings, bullet/ordered lists, code blocks, bold, italic, 10 * strikethrough, inline code, blockquote, horizontal rule) are already 11 * provided by StarterKit. 12 */ 13 14import { Extension, InputRule } from '@tiptap/core'; 15import { linkInputRegex as _unusedLinkRegex } from '../autoformat-rules.js'; 16void _unusedLinkRegex; 17 18export const MarkdownAutoformat = Extension.create({ 19 name: 'markdownAutoformat', 20 21 addInputRules() { 22 const rules: InputRule[] = []; 23 24 // [text](url) → Link 25 // When user types [link text](https://example.com), replace with a 26 // linked text node. Requires the Link extension to be registered. 27 const linkType = this.editor.schema.marks.link; 28 if (linkType) { 29 rules.push( 30 new InputRule({ 31 find: /(?:^|\s)\[([^\]]+)\]\(([^)]+)\)$/, 32 handler: ({ state, range, match }) => { 33 const linkText = match[1]; 34 const href = match[2]; 35 36 // Determine if we matched a leading space 37 const fullMatch = match[0]; 38 const hasLeadingSpace = fullMatch !== `[${linkText}](${href})`; 39 const start = hasLeadingSpace ? range.from + 1 : range.from; 40 41 const mark = linkType.create({ href }); 42 const textNode = state.schema.text(linkText, [mark]); 43 44 const tr = state.tr; 45 tr.replaceWith(start, range.to, textNode); 46 }, 47 }) 48 ); 49 } 50 51 // GFM task-list autoformat: `- [ ] ` or `- [x] ` at the very start of 52 // an empty paragraph transforms the paragraph into a single-item 53 // TaskList. #722 — TaskList's built-in input rule doesn't accept the 54 // GFM markdown form (it only reacts to literal `- [ ] ` but only as a 55 // child of an existing bullet-list context in some versions). 56 const taskListType = this.editor.schema.nodes.taskList; 57 const taskItemType = this.editor.schema.nodes.taskItem; 58 const paragraphType = this.editor.schema.nodes.paragraph; 59 if (taskListType && taskItemType && paragraphType) { 60 rules.push( 61 new InputRule({ 62 // Match at the very start of a paragraph: - [ ] or * [x] etc. 63 find: /^\s*(?:[-*+])\s+\[([xX ])\]\s$/, 64 handler: ({ state, range, match, chain: _c, commands }) => { 65 void _c; void commands; 66 const checked = /[xX]/.test(match[1] ?? ' '); 67 const { tr } = state; 68 const $from = state.doc.resolve(range.from); 69 // Replace the whole paragraph start (from block start) with a 70 // taskList > taskItem > paragraph (empty). Delete the "- [ ] " 71 // literal prefix so the cursor sits inside the task item ready 72 // for the user to type. 73 const blockStart = $from.before($from.depth); 74 const blockEnd = $from.after($from.depth); 75 const emptyPara = paragraphType.create(); 76 const taskItem = taskItemType.create({ checked }, emptyPara); 77 const taskList = taskListType.create(null, taskItem); 78 tr.replaceRangeWith(blockStart, blockEnd, taskList); 79 }, 80 }) 81 ); 82 } 83 84 return rules; 85 }, 86});