Skip to content
This repository has been archived by the owner on Jan 2, 2025. It is now read-only.

Commit

Permalink
added detection and parsing of markdown on paste
Browse files Browse the repository at this point in the history
  • Loading branch information
iskaktoltay committed Jun 20, 2024
1 parent 96db461 commit 351ba75
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {BlockContainer, BlockGroup, Doc} from './extensions/Blocks'
import {BlockNoteDOMAttributes} from './extensions/Blocks/api/blockTypes'
import {CustomBlockSerializerExtension} from './extensions/Blocks/api/serialization'
import blockStyles from './extensions/Blocks/nodes/Block.module.css'
import {MarkdownExtension} from './extensions/Markdown/MarkdownExtension'
import {Placeholder} from './extensions/Placeholder/PlaceholderExtension'
import {TextAlignmentExtension} from './extensions/TextAlignment/TextAlignmentExtension'
import {TextColorExtension} from './extensions/TextColor/TextColorExtension'
Expand Down Expand Up @@ -83,6 +84,9 @@ export const getBlockNoteExtensions = <BSchema extends HMBlockSchema>(opts: {
// basics:
Text,

// copy paste:
MarkdownExtension,

// block manupulations:
BlockManipulationExtension,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export const ParagraphBlockContent = createTipTapBlock({
tag: 'p',
priority: 200,
node: 'paragraph',
getAttrs: (node) => {
// Don't match if has image (for markdown parse)
if (node.childNodes.length > 0 && node.childNodes[0].nodeName) {
const hasImage = node.childNodes[0].nodeName === 'IMG'
return hasImage ? false : {}
}
return null
},
},
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ export const BlockGroup = Node.create<{
if (typeof element === 'string') {
return false
}
return (
element.getAttribute('data-node-type') === 'blockGroup' &&
element.getAttribute('data-list-type') === 'ul' &&
null
)
return {listType: 'ul'}
},
priority: 200,
},
Expand All @@ -98,11 +94,7 @@ export const BlockGroup = Node.create<{
if (typeof element === 'string') {
return false
}
return (
element.getAttribute('data-node-type') === 'blockGroup' &&
element.getAttribute('data-list-type') === 'ol' &&
null
)
return {listType: 'ol', start: element.getAttribute('start')}
},
priority: 200,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {Extension} from '@tiptap/core'
import {DOMParser as ProseMirrorDOMParser} from '@tiptap/pm/model'
import {Plugin} from 'prosemirror-state'
import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'

const markdownRegex = new RegExp(
[
'^#{1,6} .+', // Headers
'(\\*\\*|__)(.*?)\\1|(\\*|_)(.*?)\\3', // Bold/Italic
'\\[([^\\]]+)\\]\\(([^)]+)\\)', // Links
'!\\[([^\\]]*)\\]\\(([^)]+)\\)', // Images
'`([^`]+)`', // Inline Code
'^[-+*] .+', // Unordered Lists
'^\\d+\\. .+', // Ordered Lists
'^```[a-zA-Z]*\\n[\\s\\S]*?\\n```', // Code Blocks
].join('|'),
'gm',
)

function isMarkdown(text) {
return markdownRegex.test(text)
}

export const MarkdownExtension = Extension.create({
name: 'MarkdownPasteHandler',
priority: 99999,

addProseMirrorPlugins() {
return [
new Plugin({
props: {
handlePaste: (view, event, slice) => {
const pastedText = event.clipboardData!.getData('text/plain')
const pastedHtml = event.clipboardData!.getData('text/html')

if (pastedHtml && !isMarkdown(pastedText)) {
return false
}

unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(pastedText)
.then((file) => {
const parser = new DOMParser()
const doc = parser.parseFromString(
file.value.toString(),
'text/html',
)
const fragment = ProseMirrorDOMParser.fromSchema(
view.state.schema,
).parse(doc.body)

const {tr} = view.state
const {selection} = view.state
const {$from, $to} = selection

tr.replaceWith(
$from.before($from.depth),
$to.pos,
fragment.firstChild!.content,
)

view.dispatch(tr)
return true
})
.catch((error) => {
console.error('Failed to parse as Markdown:', error)
})

return false
},
},
}),
]
},
})

0 comments on commit 351ba75

Please sign in to comment.