add basic inputrules and pasterules

This commit is contained in:
Philipp Kühn
2020-04-02 08:53:59 +02:00
parent b85df2b4c1
commit 28d5ae094b
10 changed files with 209 additions and 4 deletions

View File

@@ -4,4 +4,7 @@ export default Editor
export { Editor }
export { default as Extension } from './src/Extension'
export { default as Node } from './src/Node'
export { default as Mark } from './src/Mark'
export { default as Mark } from './src/Mark'
export { default as markInputRule } from './src/inputRules/markInputRule'
export { default as markPasteRule } from './src/pasteRules/markPasteRule'

View File

@@ -122,6 +122,8 @@ export class Editor extends EventEmitter {
return [
...this.extensionManager.plugins,
...this.extensionManager.keymaps,
...this.extensionManager.pasteRules,
inputRules({ rules: this.extensionManager.inputRules }),
keymap({ Backspace: undoInputRule }),
keymap(baseKeymap),
dropCursor(),

View File

@@ -48,6 +48,18 @@ export default class ExtensionManager {
.toArray()
}
get inputRules(): any {
return collect(this.extensions)
.flatMap(extension => extension.inputRules())
.toArray()
}
get pasteRules(): any {
return collect(this.extensions)
.flatMap(extension => extension.pasteRules())
.toArray()
}
get keymaps() {
return collect(this.extensions)
.map(extension => extension.keys())

View File

@@ -0,0 +1,60 @@
import { InputRule } from 'prosemirror-inputrules'
import { EditorState } from 'prosemirror-state'
import { MarkType } from 'prosemirror-model'
function getMarksBetween(start: number, end: number, state: EditorState) {
let marks: any[] = []
state.doc.nodesBetween(start, end, (node, pos) => {
marks = [...marks, ...node.marks.map(mark => ({
start: pos,
end: pos + node.nodeSize,
mark,
}))]
})
return marks
}
export default function (regexp: RegExp, markType: MarkType, getAttrs?: Function) {
return new InputRule(regexp, (state, match, start, end) => {
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
const { tr } = state
const m = match.length - 1
let markEnd = end
let markStart = start
if (match[m]) {
const matchStart = start + match[0].indexOf(match[m - 1])
const matchEnd = matchStart + match[m - 1].length - 1
const textStart = matchStart + match[m - 1].lastIndexOf(match[m])
const textEnd = textStart + match[m].length
const excludedMarks = getMarksBetween(start, end, state)
.filter(item => {
const { excluded } = item.mark.type
return excluded.find((type: MarkType) => type.name === markType.name)
})
.filter(item => item.end > matchStart)
if (excludedMarks.length) {
return null
}
if (textEnd < matchEnd) {
tr.delete(textEnd, matchEnd)
}
if (textStart > matchStart) {
tr.delete(matchStart, textStart)
}
markStart = matchStart
markEnd = markStart + match[m].length
}
tr.addMark(markStart, markEnd, markType.create(attrs))
tr.removeStoredMark(markType)
return tr
})
}

View File

@@ -0,0 +1,60 @@
import { Plugin } from 'prosemirror-state'
import { Slice, Fragment, MarkType } from 'prosemirror-model'
export default function (regexp: RegExp, type: MarkType, getAttrs?: Function) {
const handler = (fragment: Fragment, parent?: any) => {
const nodes: any[] = []
fragment.forEach(child => {
if (child.isText && child.text) {
const { text } = child
let pos = 0
let match
// eslint-disable-next-line
while ((match = regexp.exec(text)) !== null) {
if (parent.type.allowsMarkType(type) && match[1]) {
const start = match.index
const end = start + match[0].length
const textStart = start + match[0].indexOf(match[1])
const textEnd = textStart + match[1].length
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
// adding text before markdown to nodes
if (start > 0) {
nodes.push(child.cut(pos, start))
}
// adding the markdown part to nodes
nodes.push(child
.cut(textStart, textEnd)
// @ts-ignore
.mark(type.create(attrs).addToSet(child.marks)))
pos = end
}
}
// adding rest of text to nodes
if (pos < text.length) {
nodes.push(child.cut(pos))
}
} else {
nodes.push(child.copy(handler(child.content, child)))
}
})
return Fragment.fromArray(nodes)
}
return new Plugin({
props: {
transformPasted: slice => {
console.log({slice})
return new Slice(handler(slice.content), slice.openStart, slice.openEnd)
},
},
})
}

View File

@@ -1,4 +1,4 @@
import { Mark } from '@tiptap/core'
import { Mark, markInputRule, markPasteRule } from '@tiptap/core'
import { toggleMark } from 'prosemirror-commands'
import { MarkSpec } from 'prosemirror-model'
@@ -44,4 +44,16 @@ export default class Bold extends Mark {
}
}
inputRules() {
return [
markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, this.schemaType),
]
}
pasteRules() {
return [
markPasteRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)/g, this.schemaType),
]
}
}

View File

@@ -0,0 +1,23 @@
import { Node } from '@tiptap/core'
import { NodeSpec } from 'prosemirror-model'
export default class CodeBlock extends Node {
name = 'code_block'
schema(): NodeSpec {
return {
content: 'text*',
marks: 'italic',
group: 'block',
code: true,
defining: true,
draggable: false,
parseDOM: [
{ tag: 'pre', preserveWhitespace: 'full' },
],
toDOM: () => ['pre', ['code', 0]],
}
}
}

View File

@@ -0,0 +1,17 @@
{
"name": "@tiptap/extension-codeblock",
"version": "1.0.0",
"source": "index.ts",
"main": "dist/tiptap-extension-codeblock.js",
"umd:main": "dist/tiptap-extension-codeblock.umd.js",
"module": "dist/tiptap-extension-codeblock.mjs",
"unpkg": "dist/tiptap-extension-codeblock.js",
"jsdelivr": "dist/tiptap-extension-codeblock.js",
"files": [
"src",
"dist"
],
"peerDependencies": {
"@tiptap/core": "2.x"
}
}

View File

@@ -1,4 +1,4 @@
import { Mark } from '@tiptap/core'
import { Mark, markInputRule, markPasteRule } from '@tiptap/core'
import { toggleMark } from 'prosemirror-commands'
import { MarkSpec } from 'prosemirror-model'
@@ -36,4 +36,18 @@ export default class Italic extends Mark {
}
}
inputRules() {
return [
markInputRule(/(?:^|[^_])(_([^_]+)_)$/, this.schemaType),
// markInputRule(/(?:^|[^*])(\*([^*]+)\*)$/, this.schemaType),
]
}
pasteRules() {
return [
markPasteRule(/_([^_]+)_/g, this.schemaType),
// markPasteRule(/\*([^*]+)\*/g, this.schemaType),
]
}
}