import { nodeInputRule, textblockTypeInputRule } from '@tiptap/core'
import { CodeBlock as CodeBlockTipTap } from '@tiptap/extension-code-block'
export const lineBeginingBacktickInputRegex = /^```[a-z]*[\s\n]$/
export const backtickInputRegex = /(?<=\s|\n)```[a-z]*[\s\n]$/
export const lineBeginingTildeInputRegex = /^~~~[a-z]*[\s\n]$/
export const tildeInputRegex = /(?<=\s|\n)~~~[a-z]*[\s\n]$/
export const CodeBlock = CodeBlockTipTap.extend({
addInputRules () {
return [
nodeInputRule({
find: backtickInputRegex,
type: this.type,
getAttributes: (match) => ({
language: match[1]
})
}),
textblockTypeInputRule({
find: lineBeginingBacktickInputRegex,
type: this.type,
getAttributes: (match) => ({
language: match[1]
})
}),
nodeInputRule({
find: tildeInputRegex,
type: this.type,
getAttributes: (match) => ({
language: match[1]
})
}),
textblockTypeInputRule({
find: lineBeginingTildeInputRegex,
type: this.type,
getAttributes: (match) => ({
language: match[1]
})
})
]
},
addKeyboardShortcuts () {
return {
'Mod-Alt-c': () => this.editor.commands.toggleCodeBlock(),
Backspace: () => {
const { empty, $anchor } = this.editor.state.selection
const isAtStart = $anchor.pos === 1
if (!empty || $anchor.parent.type.name !== this.name) {
return false
}
if (isAtStart || !$anchor.parent.textContent.length) {
return this.editor.commands.clearNodes()
}
return false
},
Enter: () => {
const { state } = this.editor
const { from, to } = state.selection
if (from > 1 && from === to) {
let inCodeBlock = false
state.doc.nodesBetween(from - 1, to - 1, (node) => {
if (node.type.name === 'codeBlock') inCodeBlock = true
})
// const cursorAtEnd = to === state.doc.content.size - 1
if (inCodeBlock) {
return this.editor.commands.exitCode()
}
}
return false
},
ArrowDown: () => {
const { state } = this.editor
const { from, to } = state.selection
if (from > 1 && from === to) {
let inCodeBlock = false
state.doc.nodesBetween(from - 1, to - 1, (node) => {
if (node.type.name === 'codeBlock') inCodeBlock = true
})
const cursorAtEnd = to === state.doc.content.size - 1
if (inCodeBlock && cursorAtEnd) {
return this.editor.commands.exitCode()
}
}
return false
},
'Shift-Enter': () => {
const { state } = this.editor
const { from, to } = state.selection
if (from > 1 && from === to) {
let inCodeBlock = false
state.doc.nodesBetween(from - 1, to - 1, (node) => {
if (node.type.name === 'codeBlock') inCodeBlock = true
})
if (inCodeBlock) {
return this.editor.commands.newlineInCode()
}
}
return false
},
'Mod-Enter': () => {
const { state } = this.editor
const { from, to } = state.selection
if (from > 1 && from === to) {
let inCodeBlock = false
state.doc.nodesBetween(from - 1, to - 1, (node) => {
if (node.type.name === 'codeBlock') inCodeBlock = true
})
if (inCodeBlock) {
return this.editor.commands.newlineInCode()
}
}
return false
},
'Alt-Enter': () => {
const { state } = this.editor
const { from, to } = state.selection
if (from > 1 && from === to) {
let inCodeBlock = false
state.doc.nodesBetween(from - 1, to - 1, (node) => {
if (node.type.name === 'codeBlock') inCodeBlock = true
})
if (inCodeBlock) {
return this.editor.commands.newlineInCode()
}
}
return false
}
}
}
})