Files
tiptap/packages/extension-code-block/src/code-block.ts
Philipp Kühn 9afadeb7fe feat!: Replace defaultOptions with addOptions (#2088)
* add new addOptions option

* replace defaultOptions with addOptions for all extensions

* replace defaultOptions with addOptions for all demos

* replace defaultOptions with addOptions in docs

* refactoring

* refactoring

* drop object support for addOptions

* fix optional options

* fix tests
2021-10-26 18:31:13 +02:00

194 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Node, textblockTypeInputRule } from '@tiptap/core'
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
export interface CodeBlockOptions {
languageClassPrefix: string,
HTMLAttributes: Record<string, any>,
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
codeBlock: {
/**
* Set a code block
*/
setCodeBlock: (attributes?: { language: string }) => ReturnType,
/**
* Toggle a code block
*/
toggleCodeBlock: (attributes?: { language: string }) => ReturnType,
}
}
}
export const backtickInputRegex = /^```(?<language>[a-z]*)?[\s\n]$/
export const tildeInputRegex = /^~~~(?<language>[a-z]*)?[\s\n]$/
export const CodeBlock = Node.create<CodeBlockOptions>({
name: 'codeBlock',
addOptions() {
return {
languageClassPrefix: 'language-',
HTMLAttributes: {},
}
},
content: 'text*',
marks: '',
group: 'block',
code: true,
defining: true,
addAttributes() {
return {
language: {
default: null,
parseHTML: element => {
const { languageClassPrefix } = this.options
const classNames = [...element.firstElementChild?.classList || []]
const languages = classNames
.filter(className => className.startsWith(languageClassPrefix))
.map(className => className.replace(languageClassPrefix, ''))
const language = languages[0]
if (!language) {
return null
}
return language
},
renderHTML: attributes => {
if (!attributes.language) {
return null
}
return {
class: this.options.languageClassPrefix + attributes.language,
}
},
},
}
},
parseHTML() {
return [
{
tag: 'pre',
preserveWhitespace: 'full',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['pre', this.options.HTMLAttributes, ['code', HTMLAttributes, 0]]
},
addCommands() {
return {
setCodeBlock: attributes => ({ commands }) => {
return commands.setNode('codeBlock', attributes)
},
toggleCodeBlock: attributes => ({ commands }) => {
return commands.toggleNode('codeBlock', 'paragraph', attributes)
},
}
},
addKeyboardShortcuts() {
return {
'Mod-Alt-c': () => this.editor.commands.toggleCodeBlock(),
// remove code block when at start of document or code block is empty
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
},
}
},
addInputRules() {
return [
textblockTypeInputRule({
find: backtickInputRegex,
type: this.type,
getAttributes: ({ groups }) => groups,
}),
textblockTypeInputRule({
find: tildeInputRegex,
type: this.type,
getAttributes: ({ groups }) => groups,
}),
]
},
addProseMirrorPlugins() {
return [
// this plugin creates a code block for pasted content from VS Code
// we can also detect the copied code language
new Plugin({
key: new PluginKey('codeBlockVSCodeHandler'),
props: {
handlePaste: (view, event) => {
if (!event.clipboardData) {
return false
}
// dont create a new code block within code blocks
if (this.editor.isActive(this.type.name)) {
return false
}
const text = event.clipboardData.getData('text/plain')
const vscode = event.clipboardData.getData('vscode-editor-data')
const vscodeData = vscode
? JSON.parse(vscode)
: undefined
const language = vscodeData?.mode
if (!text || !language) {
return false
}
const { tr } = view.state
// create an empty code block
tr.replaceSelectionWith(this.type.create({ language }))
// put cursor inside the newly created code block
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2))))
// add text to code block
// strip carriage return chars from text pasted as code
// see: https://github.com/ProseMirror/prosemirror-view/commit/a50a6bcceb4ce52ac8fcc6162488d8875613aacd
tr.insertText(text.replace(/\r\n?/g, '\n'))
// store meta information
// this is useful for other plugins that depends on the paste event
// like the paste rule plugin
tr.setMeta('paste', true)
view.dispatch(tr)
return true
},
},
}),
]
},
})