mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-01-18 06:03:22 +08:00
Merge branch 'main' of github.com:ueberdosis/tiptap-next into main
This commit is contained in:
commit
f3880ef840
@ -41,7 +41,12 @@ export default {
|
||||
flattenedItems() {
|
||||
const flattenedItems = []
|
||||
|
||||
this.items.forEach(({ title, link, redirect, items }) => {
|
||||
this.items.forEach(({
|
||||
title,
|
||||
link,
|
||||
redirect,
|
||||
items,
|
||||
}) => {
|
||||
flattenedItems.push({
|
||||
title,
|
||||
link,
|
||||
@ -71,7 +76,7 @@ export default {
|
||||
previousPage() {
|
||||
let previousIndex = this.currentIndex - 1
|
||||
|
||||
while (this.flattenedItems[previousIndex].redirect) {
|
||||
while (this.flattenedItems[previousIndex]?.redirect) {
|
||||
previousIndex -= 1
|
||||
}
|
||||
|
||||
|
@ -27,9 +27,9 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.html = generateHTML(this.json, [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
])
|
||||
},
|
||||
}
|
||||
|
@ -135,11 +135,11 @@ export default {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
...defaultExtensions(),
|
||||
Collaboration({
|
||||
Collaboration.configure({
|
||||
provider: this.provider,
|
||||
type: this.type,
|
||||
}),
|
||||
CollaborationCursor({
|
||||
CollaborationCursor.configure({
|
||||
provider: this.provider,
|
||||
name: this.name,
|
||||
color: this.color,
|
||||
|
@ -61,16 +61,16 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Heading({
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Heading.configure({
|
||||
level: [1, 2, 3],
|
||||
}),
|
||||
Bold(),
|
||||
Italic(),
|
||||
TextAlign(),
|
||||
HardBreak(),
|
||||
Bold,
|
||||
Italic,
|
||||
TextAlign,
|
||||
HardBreak,
|
||||
],
|
||||
content: `
|
||||
<h3>Girls Just Want to Have Fun (Cyndi Lauper)</h2>
|
||||
|
@ -32,10 +32,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Link(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Link,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
|
||||
import Strike from '@tiptap/extension-strike'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
|
||||
export default {
|
||||
@ -38,7 +37,7 @@ export default {
|
||||
`,
|
||||
extensions: [
|
||||
...defaultExtensions(),
|
||||
Highlight(),
|
||||
Highlight,
|
||||
],
|
||||
})
|
||||
},
|
||||
|
@ -22,9 +22,9 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -35,11 +35,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
CustomDocument(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
TaskList(),
|
||||
CustomTaskItem(),
|
||||
CustomDocument,
|
||||
Paragraph,
|
||||
Text,
|
||||
TaskList,
|
||||
CustomTaskItem,
|
||||
],
|
||||
content: `
|
||||
<ul data-type="taskList">
|
||||
|
@ -26,10 +26,10 @@ export default {
|
||||
this.editor = new Editor({
|
||||
content: '<p>I’m running tiptap with Vue.js. This demo is interactive, try to edit the text.</p>',
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Bold(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
],
|
||||
})
|
||||
},
|
||||
|
@ -37,10 +37,10 @@ export default {
|
||||
// <p>Example Text</p>
|
||||
// `,
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Collaboration({
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Collaboration.configure({
|
||||
provider: this.provider,
|
||||
type: this.type,
|
||||
}),
|
||||
|
@ -38,14 +38,14 @@ export default {
|
||||
// <p>Example Text</p>
|
||||
// `,
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Collaboration({
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Collaboration.configure({
|
||||
provider: this.provider,
|
||||
type: this.type,
|
||||
}),
|
||||
CollaborationCursor({
|
||||
CollaborationCursor.configure({
|
||||
provider: this.provider,
|
||||
name: 'Cyndi Lauper',
|
||||
color: '#f783ac',
|
||||
|
@ -26,11 +26,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Image(),
|
||||
Dropcursor(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Image,
|
||||
Dropcursor,
|
||||
],
|
||||
content: `
|
||||
<p>Try to drag around the image. While you drag, the editor should show a decoration under your cursor. The so called dropcursor.</p>
|
||||
|
@ -28,16 +28,16 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Focus({
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Focus.configure({
|
||||
className: 'has-focus',
|
||||
nested: true,
|
||||
}),
|
||||
Code(),
|
||||
BulletList(),
|
||||
ListItem(),
|
||||
Code,
|
||||
BulletList,
|
||||
ListItem,
|
||||
],
|
||||
autoFocus: true,
|
||||
content: `
|
||||
|
@ -46,11 +46,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
TextStyle(),
|
||||
FontFamily(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
TextStyle,
|
||||
FontFamily,
|
||||
],
|
||||
content: `
|
||||
<p><span style="font-family: Inter">Did you know that Inter is a really nice font for interfaces?</span></p>
|
||||
|
@ -26,11 +26,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Image(),
|
||||
Gapcursor(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Image,
|
||||
Gapcursor,
|
||||
],
|
||||
content: `
|
||||
<p>Try to set the cursor behind the image with your arrow keys! You should see big blinking cursor right from the image. This is the gapcursor.</p>
|
||||
|
@ -33,10 +33,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
History(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
History,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -42,11 +42,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Heading(),
|
||||
TextAlign(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Heading,
|
||||
TextAlign,
|
||||
],
|
||||
content: `
|
||||
<h2>Heading</h2>
|
||||
|
@ -25,10 +25,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Typography(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Typography,
|
||||
],
|
||||
content: `
|
||||
<p>“I have been suffering from Typomania all my life, a sickness that is incurable but not lethal.”</p>
|
||||
|
@ -55,15 +55,15 @@ export default {
|
||||
this.editor = new Editor({
|
||||
content: '<h2>Hey there!</h2><p>This editor is based on Prosemirror, fully extendable and headless. You can easily add custom nodes as Vue components.</p>',
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
CodeBlock(),
|
||||
History(),
|
||||
Bold(),
|
||||
Italic(),
|
||||
Code(),
|
||||
Heading(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
CodeBlock,
|
||||
History,
|
||||
Bold,
|
||||
Italic,
|
||||
Code,
|
||||
Heading,
|
||||
],
|
||||
})
|
||||
},
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Bold(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
],
|
||||
content: `
|
||||
<p>This isn’t bold.</p>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Code(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Code,
|
||||
],
|
||||
content: `
|
||||
<p>This isn’t code.</p>
|
||||
|
@ -61,10 +61,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Highlight(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Highlight,
|
||||
],
|
||||
content: `
|
||||
<p>This isn’t highlighted.</s></p>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Italic(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Italic,
|
||||
],
|
||||
content: `
|
||||
<p>This isn’t italic.</p>
|
||||
|
@ -13,21 +13,21 @@ context('/api/marks/link', () => {
|
||||
it('should parse a tags correctly', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><a href="#">Example Text</a></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><a href="#" target="_blank" rel="noopener noreferrer nofollow">Example Text</a></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><a target="_blank" rel="noopener noreferrer nofollow" href="#">Example Text</a></p>')
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse a tags with target attribute correctly', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><a href="#" target="_self">Example Text</a></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><a href="#" target="_self" rel="noopener noreferrer nofollow">Example Text</a></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><a target="_self" rel="noopener noreferrer nofollow" href="#">Example Text</a></p>')
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse a tags with rel attribute correctly', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><a href="#" rel="follow">Example Text</a></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><a href="#" target="_blank" rel="noopener noreferrer nofollow">Example Text</a></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><a target="_blank" rel="noopener noreferrer nofollow" href="#">Example Text</a></p>')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -32,10 +32,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Link(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Link,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Strike(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Strike,
|
||||
],
|
||||
content: `
|
||||
<p>This isn’t striked through.</s></p>
|
||||
|
@ -26,10 +26,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
TextStyle(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
TextStyle,
|
||||
],
|
||||
content: `
|
||||
<p><span>This has a <span> tag without a style attribute, so it’s thrown away.</span></p>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Underline(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Underline,
|
||||
],
|
||||
content: `
|
||||
<p>There is no underline here.</p>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Blockquote(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Blockquote,
|
||||
],
|
||||
content: `
|
||||
<blockquote>
|
||||
|
@ -31,11 +31,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
BulletList(),
|
||||
ListItem(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
BulletList,
|
||||
ListItem,
|
||||
],
|
||||
content: `
|
||||
<ul>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
CodeBlock(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
CodeBlock,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -25,9 +25,9 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
],
|
||||
content: `
|
||||
<p>The Document extension is required. Though, you can write your own implementation, e. g. to give it custom name.</p>
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
HardBreak(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
HardBreak,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -36,10 +36,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Heading({
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Heading.configure({
|
||||
levels: [1, 2, 3],
|
||||
}),
|
||||
],
|
||||
|
@ -30,10 +30,10 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
HorizontalRule(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
HorizontalRule,
|
||||
],
|
||||
content: `
|
||||
<p>This is a paragraph.</p>
|
||||
|
@ -38,11 +38,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Image(),
|
||||
Dropcursor(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Image,
|
||||
Dropcursor,
|
||||
],
|
||||
content: `
|
||||
<p>This is a basic example of implementing images. Drag to re-order.</p>
|
||||
|
@ -35,12 +35,12 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
BulletList(),
|
||||
OrderedList(),
|
||||
ListItem(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
BulletList,
|
||||
OrderedList,
|
||||
ListItem,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
|
@ -31,11 +31,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
OrderedList(),
|
||||
ListItem(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
OrderedList,
|
||||
ListItem,
|
||||
],
|
||||
content: `
|
||||
<ol>
|
||||
|
@ -25,9 +25,9 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
],
|
||||
content: `
|
||||
<p>The Paragraph extension is not required, but it’s very likely you want to use it. It’s needed to write paragraphs of text. 🤓</p>
|
||||
|
@ -27,11 +27,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
TaskList(),
|
||||
TaskItem(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
TaskList,
|
||||
TaskItem,
|
||||
],
|
||||
content: `
|
||||
<ul data-type="task_list">
|
||||
|
@ -31,11 +31,11 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
TaskList(),
|
||||
TaskItem(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
TaskList,
|
||||
TaskItem,
|
||||
],
|
||||
content: `
|
||||
<ul data-type="task_list">
|
||||
|
@ -25,9 +25,9 @@ export default {
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
],
|
||||
content: `
|
||||
<p>The Text extension is required, at least if you want to have text in your text editor and that’s very likely.</p>
|
||||
|
@ -23,20 +23,20 @@ You don’t have to use it, but we prepared a `@tiptap/vue-starter-kit` which in
|
||||
You’re free to create your own extensions for tiptap. Here is the boilerplate code that’s need to create and register your own extension:
|
||||
|
||||
```js
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
|
||||
const CustomExtension = createExtension({
|
||||
const CustomExtension = Extension.create({
|
||||
// Your code here
|
||||
})
|
||||
|
||||
const editor = new Editor({
|
||||
extensions: [
|
||||
// Register your custom extension with the editor.
|
||||
CustomExtension(),
|
||||
CustomExtension,
|
||||
// … and don’t forget all other extensions.
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
// …
|
||||
],
|
||||
```
|
||||
@ -49,7 +49,7 @@ ProseMirror has a fantastic eco system with many amazing plugins. If you want to
|
||||
```js
|
||||
import { history } from 'prosemirror-history'
|
||||
|
||||
const History = createExtension({
|
||||
const History = Extension.create({
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
history(),
|
||||
|
@ -38,15 +38,15 @@ In tiptap every node, mark and extension is living in its own file. This allows
|
||||
|
||||
```js
|
||||
// the tiptap schema API
|
||||
import { createNode } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
const Document = createNode({
|
||||
const Document = Node.create({
|
||||
name: 'document',
|
||||
topNode: true,
|
||||
content: 'block+',
|
||||
})
|
||||
|
||||
const Paragraph = createNode({
|
||||
const Paragraph = Node.create({
|
||||
name: 'paragraph',
|
||||
group: 'block',
|
||||
content: 'inline*',
|
||||
@ -55,12 +55,12 @@ const Paragraph = createNode({
|
||||
{ tag: 'p' },
|
||||
]
|
||||
},
|
||||
renderHTML({ attributes }) {
|
||||
return ['p', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['p', HTMLAttributes, 0]
|
||||
},
|
||||
})
|
||||
|
||||
const Text = createNode({
|
||||
const Text = Node.create({
|
||||
name: 'text',
|
||||
group: 'inline',
|
||||
})
|
||||
@ -79,7 +79,7 @@ Marks can be applied to specific parts of a node. That’s the case for **bold**
|
||||
The content attribute defines exactly what kind of content the node can have. ProseMirror is really strict with that. That means, content which doesn’t fit the schema is thrown away. It expects a name or group as a string. Here are a few examples:
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
// must have one ore more blocks
|
||||
content: 'block+',
|
||||
|
||||
@ -99,7 +99,7 @@ createNode({
|
||||
You can define which marks are allowed inside of a node with the `marks` setting of the schema. Add a one or more names or groups of marks, allow all or disallow all marks like this:
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
// allows only the 'bold' mark
|
||||
marks: 'bold',
|
||||
|
||||
@ -118,7 +118,7 @@ createNode({
|
||||
Add this node to a group of extensions, which can be referred to in the [content](#content) attribute of the schema.
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
// add to 'block' group
|
||||
group: 'block',
|
||||
|
||||
@ -134,7 +134,7 @@ createNode({
|
||||
Nodes can be rendered inline, too. When setting `inline: true` nodes are rendered in line with the text. That’s the case for mentions. The result is more like a mark, but with the functionality of a node. One difference is the resulting JSON document. Multiple marks are applied at once, inline nodes would result in a nested structure.
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
// renders nodes in line with the text, for example
|
||||
inline: true,
|
||||
})
|
||||
@ -144,7 +144,7 @@ createNode({
|
||||
Nodes with `atom: true` aren’t directly editable and should be treated as a single unit. It’s not so likely to use that in a editor context, but this is how it would look like:
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
atom: true,
|
||||
})
|
||||
```
|
||||
@ -153,7 +153,7 @@ createNode({
|
||||
Besides the already visible text selection, there is an invisible node selection. If you want to make your nodes selectable, you can configure it like this:
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
selectable: true,
|
||||
})
|
||||
```
|
||||
@ -162,7 +162,7 @@ createNode({
|
||||
All nodes can be configured to be draggable (by default they aren’t) with this setting:
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
draggable: true,
|
||||
})
|
||||
```
|
||||
@ -171,7 +171,7 @@ createNode({
|
||||
Users expect code to behave very differently. For all kind of nodes containing code, you can set `code: true` to take this into account.
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
code: true,
|
||||
})
|
||||
```
|
||||
@ -182,7 +182,7 @@ Nodes get dropped when their entire content is replaced (for example, when pasti
|
||||
Typically, that applies to [`Blockquote`](/api/nodes/blockquote), [`CodeBlock`](/api/nodes/code-block), [`Heading`](/api/nodes/heading), and [`ListItem`](/api/nodes/list-item).
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
defining: true,
|
||||
})
|
||||
```
|
||||
@ -191,7 +191,7 @@ createNode({
|
||||
For nodes that should fence the cursor for regular editing operations like backspacing, for example a TableCell, set `isolating: true`.
|
||||
|
||||
```js
|
||||
createNode({
|
||||
Node.create({
|
||||
isolating: true,
|
||||
})
|
||||
```
|
||||
@ -201,7 +201,7 @@ createNode({
|
||||
If you don’t want the mark to be active when the cursor is at its end, set inclusive to `false`. For example, that’s how it’s configured for [`Link`](/api/marks/link) marks:
|
||||
|
||||
```js
|
||||
createMark({
|
||||
Mark.create({
|
||||
inclusive: false,
|
||||
})
|
||||
```
|
||||
@ -210,7 +210,7 @@ createMark({
|
||||
By default all nodes can be applied at the same time. With the excludes attribute you can define which marks must not coexist with the mark. For example, the inline code mark excludes any other mark (bold, italic, and all others).
|
||||
|
||||
```js
|
||||
createMark({
|
||||
Mark.create({
|
||||
// must not coexist with the bold mark
|
||||
excludes: 'bold'
|
||||
// exclude any other mark
|
||||
@ -222,7 +222,7 @@ createMark({
|
||||
Add this mark to a group of extensions, which can be referred to in the content attribute of the schema.
|
||||
|
||||
```js
|
||||
createMark({
|
||||
Mark.create({
|
||||
// add this mark to the 'basic' group
|
||||
group: 'basic',
|
||||
// add this mark to the 'basic' and the 'foobar' group
|
||||
@ -234,7 +234,7 @@ createMark({
|
||||
Users expect code to behave very differently. For all kind of marks containing code, you can set `code: true` to take this into account.
|
||||
|
||||
```js
|
||||
createMark({
|
||||
Mark.create({
|
||||
code: true,
|
||||
})
|
||||
```
|
||||
@ -243,7 +243,7 @@ createMark({
|
||||
By default marks can span multiple nodes when rendered as HTML. Set `spanning: false` to indicate that a mark must not span multiple nodes.
|
||||
|
||||
```js
|
||||
createMark({
|
||||
Mark.create({
|
||||
spanning: false,
|
||||
})
|
||||
```
|
||||
@ -262,9 +262,9 @@ import Text from '@tiptap/extension-text'
|
||||
|
||||
const editor = new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
// add more extensions here
|
||||
])
|
||||
})
|
||||
@ -282,9 +282,9 @@ import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
|
||||
const schema = getSchema([
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
// add more extensions here
|
||||
])
|
||||
```
|
||||
|
@ -171,9 +171,9 @@ Attributes can be applied to multiple extensions at once. That’s useful for te
|
||||
Take a closer look at [the full source code](https://github.com/ueberdosis/tiptap-next/tree/main/packages/extension-text-align) of the [`TextAlign`](/api/extensions/text-align) extension to see a more complex example. But here is how it works in a nutshell:
|
||||
|
||||
```js
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
|
||||
const TextAlign = createExtension({
|
||||
const TextAlign = Extension.create({
|
||||
addGlobalAttributes() {
|
||||
return [
|
||||
{
|
||||
@ -204,8 +204,8 @@ const TextAlign = createExtension({
|
||||
With the `renderHTML` function you can control how an extension is rendered to HTML. We pass an attributes object to it, with all local attributes, global attributes, and configured CSS classes. Here is an example from the `Bold` extension:
|
||||
|
||||
```js
|
||||
renderHTML({ attributes }) {
|
||||
return ['strong', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['strong', HTMLAttributes, 0]
|
||||
},
|
||||
```
|
||||
|
||||
@ -214,16 +214,16 @@ The first value in the array should be the name of HTML tag. If the second eleme
|
||||
The number zero (representing a hole) is used to indicate where the content should be inserted. Let’s look at the rendering of the `CodeBlock` extension with two nested tags:
|
||||
|
||||
```js
|
||||
renderHTML({ attributes }) {
|
||||
return ['pre', ['code', attributes, 0]]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['pre', ['code', HTMLAttributes, 0]]
|
||||
},
|
||||
```
|
||||
|
||||
If you want to add some specific attributes there, import the `mergeAttributes` helper from `@tiptap/core`:
|
||||
|
||||
```js
|
||||
renderHTML({ attributes }) {
|
||||
return ['a', mergeAttributes(attributes, { rel: this.options.rel }), 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['a', mergeAttributes(HTMLAttributes, { rel: this.options.rel }), 0]
|
||||
},
|
||||
```
|
||||
|
||||
@ -386,33 +386,33 @@ const CustomLink = Link.extend({
|
||||
There is a whole lot to learn about node views, so head over to the [dedicated section in our guide about node views](/guide/advanced-node-views) for more information. If you’re looking for a real-world example, look at the source code of the [`TaskItem`](/api/nodes/task-item) node. This is using a node view to render the checkboxes.
|
||||
|
||||
## Start from scratch
|
||||
You can also build your own extensions from scratch with the `createNode()`, `createMark()`, and `createExtension()` functions. Pass an option with your code and configuration.
|
||||
You can also build your own extensions from scratch with the `Node`, `Mark`, and `Extension` classes. Pass an option with your code and configuration.
|
||||
|
||||
And if everything is working fine, don’t forget to [share it with the community](https://github.com/ueberdosis/tiptap-next/issues/new/choose).
|
||||
|
||||
### Create a node
|
||||
```js
|
||||
import { createNode } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
const CustomNode = createNode({
|
||||
const CustomNode = Node.create({
|
||||
// Your code goes here.
|
||||
})
|
||||
```
|
||||
|
||||
### Create a mark
|
||||
```js
|
||||
import { createMark } from '@tiptap/core'
|
||||
import { Mark } from '@tiptap/core'
|
||||
|
||||
const CustomMark = createMark({
|
||||
const CustomMark = Mark.create({
|
||||
// Your code goes here.
|
||||
})
|
||||
```
|
||||
|
||||
### Create an extension
|
||||
```js
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
|
||||
const CustomExtension = createExtension({
|
||||
const CustomExtension = Extension.create({
|
||||
// Your code goes here.
|
||||
})
|
||||
```
|
||||
|
@ -28,23 +28,23 @@ p {
|
||||
## Option 2: Add custom classes
|
||||
Most extensions have a `class` option, which you can use to add a custom CSS class to the HTML tag.
|
||||
|
||||
Most extensions allow you to add attributes to the rendered HTML through the `attributes` configuration. You can use that to add a custom class (or any other attribute):
|
||||
Most extensions allow you to add attributes to the rendered HTML through the `HTMLAttributes` configuration. You can use that to add a custom class (or any other attribute):
|
||||
|
||||
```js
|
||||
new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph({
|
||||
attributes: {
|
||||
Document,
|
||||
Paragraph.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'my-custom-paragraph',
|
||||
},
|
||||
}),
|
||||
Heading({
|
||||
attributes: {
|
||||
Heading.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'my-custom-heading',
|
||||
},
|
||||
}),
|
||||
Text(),
|
||||
Text,
|
||||
]
|
||||
})
|
||||
```
|
||||
@ -65,10 +65,10 @@ You can even customize the markup for every extension. This will make a custom b
|
||||
import Bold from '@tiptap/extension-bold'
|
||||
|
||||
const CustomBold = Bold.extend({
|
||||
renderHTML({ attributes }) {
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
// Original:
|
||||
// return ['strong', attributes, 0]
|
||||
return ['b', attributes, 0]
|
||||
// return ['strong', HTMLAttributes, 0]
|
||||
return ['b', HTMLAttributes, 0]
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -15,13 +15,13 @@ If you’re using TypeScript in your project and want to extend tiptap, there ar
|
||||
To extend or create default options for an extension, you’ll need to define a custom type, here is an example:
|
||||
|
||||
```js
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
|
||||
export interface CustomExtensionOptions {
|
||||
awesomeness: number,
|
||||
}
|
||||
|
||||
const CustomExtension = createExtension({
|
||||
const CustomExtension = Extension.create({
|
||||
defaultOptions: <CustomExtensionOptions>{
|
||||
awesomeness: 100,
|
||||
},
|
||||
@ -32,9 +32,9 @@ const CustomExtension = createExtension({
|
||||
The core package also exports a `Command` type, which needs to be added to all commands that you specify in your code. Here is an example:
|
||||
|
||||
```js
|
||||
import { Command, createExtension } from '@tiptap/core'
|
||||
import { Command, Extension } from '@tiptap/core'
|
||||
|
||||
const CustomExtension = createExtension({
|
||||
const CustomExtension = Extension.create({
|
||||
addCommands() {
|
||||
return {
|
||||
/**
|
||||
|
@ -29,9 +29,9 @@ import Text from '@tiptap/extension-text'
|
||||
|
||||
new Editor({
|
||||
extensions: [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
// all your other extensions
|
||||
]
|
||||
})
|
||||
@ -44,9 +44,9 @@ new Editor({
|
||||
In case you’ve built some custom extensions for your project, you’re required to rewrite them to fit the new API. No worries, you can keep a lot of your work though. The `schema`, `commands`, `keys`, `inputRules` and `pasteRules` all work like they did before. It’s just different how you register them.
|
||||
|
||||
```js
|
||||
import { createNode } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
const CustomExtension = createNode({
|
||||
const CustomExtension = Node.create({
|
||||
name: 'custom_extension'
|
||||
defaultOptions: {
|
||||
…
|
||||
@ -57,7 +57,7 @@ const CustomExtension = createNode({
|
||||
parseHTML() {
|
||||
…
|
||||
},
|
||||
renderHTML({ node, attributes }) {
|
||||
renderHTML({ node, HTMLAttributes }) {
|
||||
…
|
||||
},
|
||||
addCommands() {
|
||||
|
@ -14,7 +14,10 @@ import createStyleTag from './utils/createStyleTag'
|
||||
import CommandManager from './CommandManager'
|
||||
import ExtensionManager from './ExtensionManager'
|
||||
import EventEmitter from './EventEmitter'
|
||||
import { Extensions, UnionToIntersection, PickValue } from './types'
|
||||
import { Extension } from './Extension'
|
||||
import { Node } from './Node'
|
||||
import { Mark } from './Mark'
|
||||
import { Extensions, UnionToIntersection } from './types'
|
||||
import * as extensions from './extensions'
|
||||
import style from './style'
|
||||
|
||||
@ -37,7 +40,19 @@ export interface CommandsSpec {
|
||||
|
||||
export interface AllExtensions {}
|
||||
|
||||
export type AllCommands = UnionToIntersection<ReturnType<PickValue<ReturnType<AllExtensions[keyof AllExtensions]>, 'addCommands'>>>
|
||||
export type UnfilteredCommands = {
|
||||
[Item in keyof AllExtensions]: AllExtensions[Item] extends Extension<any, infer ExtensionCommands>
|
||||
? ExtensionCommands
|
||||
: AllExtensions[Item] extends Node<any, infer NodeCommands>
|
||||
? NodeCommands
|
||||
: AllExtensions[Item] extends Mark<any, infer MarkCommands>
|
||||
? MarkCommands
|
||||
: never
|
||||
}
|
||||
|
||||
type ValuesOf<T> = T[keyof T];
|
||||
type KeysWithTypeOf<T, Type> = ({[P in keyof T]: T[P] extends Type ? P : never })[keyof T]
|
||||
type AllCommands = UnionToIntersection<ValuesOf<Pick<UnfilteredCommands, KeysWithTypeOf<UnfilteredCommands, {}>>>>
|
||||
|
||||
export type SingleCommands = {
|
||||
[Item in keyof AllCommands]: AllCommands[Item] extends (...args: any[]) => any
|
||||
@ -239,7 +254,7 @@ export class Editor extends EventEmitter {
|
||||
* Creates an extension manager.
|
||||
*/
|
||||
private createExtensionManager() {
|
||||
const coreExtensions = Object.entries(extensions).map(([, extension]) => extension())
|
||||
const coreExtensions = Object.entries(extensions).map(([, extension]) => extension)
|
||||
const allExtensions = [...this.options.extensions, ...coreExtensions]
|
||||
|
||||
this.extensionManager = new ExtensionManager(allExtensions, this.proxy)
|
||||
|
@ -2,7 +2,7 @@ import { Plugin } from 'prosemirror-state'
|
||||
import { Editor } from './Editor'
|
||||
import { GlobalAttributes } from './types'
|
||||
|
||||
export interface ExtensionSpec<Options = {}, Commands = {}> {
|
||||
export interface ExtensionConfig<Options = any, Commands = {}> {
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
@ -63,51 +63,55 @@ export interface ExtensionSpec<Options = {}, Commands = {}> {
|
||||
}) => Plugin[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension interface for internal usage
|
||||
*/
|
||||
export type Extension = Required<Omit<ExtensionSpec, 'defaultOptions'> & {
|
||||
type: string,
|
||||
options: {
|
||||
[key: string]: any
|
||||
},
|
||||
}>
|
||||
export class Extension<Options = any, Commands = any> {
|
||||
config: Required<ExtensionConfig> = {
|
||||
name: 'extension',
|
||||
defaultOptions: {},
|
||||
addGlobalAttributes: () => [],
|
||||
addCommands: () => ({}),
|
||||
addKeyboardShortcuts: () => ({}),
|
||||
addInputRules: () => [],
|
||||
addPasteRules: () => [],
|
||||
addProseMirrorPlugins: () => [],
|
||||
}
|
||||
|
||||
/**
|
||||
* Default extension
|
||||
*/
|
||||
export const defaultExtension: Extension = {
|
||||
name: 'extension',
|
||||
type: 'extension',
|
||||
options: {},
|
||||
addGlobalAttributes: () => [],
|
||||
addCommands: () => ({}),
|
||||
addKeyboardShortcuts: () => ({}),
|
||||
addInputRules: () => [],
|
||||
addPasteRules: () => [],
|
||||
addProseMirrorPlugins: () => [],
|
||||
}
|
||||
options!: Options
|
||||
|
||||
export function createExtension<Options extends {}, Commands extends {}>(config: ExtensionSpec<Options, Commands>) {
|
||||
const extend = <ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<ExtensionSpec<ExtendedOptions, ExtendedCommands>>) => {
|
||||
return createExtension({
|
||||
constructor(config: ExtensionConfig<Options, Commands>) {
|
||||
this.config = {
|
||||
...this.config,
|
||||
...config,
|
||||
...extendedConfig,
|
||||
} as ExtensionSpec<ExtendedOptions, ExtendedCommands>)
|
||||
}
|
||||
|
||||
const setOptions = (options?: Partial<Options>) => {
|
||||
const { defaultOptions, ...rest } = config
|
||||
|
||||
return {
|
||||
...defaultExtension,
|
||||
...rest,
|
||||
options: {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
} as Options,
|
||||
}
|
||||
|
||||
this.options = this.config.defaultOptions
|
||||
}
|
||||
|
||||
return Object.assign(setOptions, { config, extend })
|
||||
static create<O, C>(config: ExtensionConfig<O, C>) {
|
||||
return new Extension<O, C>(config)
|
||||
}
|
||||
|
||||
configure(options: Partial<Options>) {
|
||||
return Extension
|
||||
.create<Options, Commands>(this.config as ExtensionConfig<Options, Commands>)
|
||||
.#configure({
|
||||
...this.config.defaultOptions,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
#configure = (options: Partial<Options>) => {
|
||||
this.options = {
|
||||
...this.config.defaultOptions,
|
||||
...options,
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<ExtensionConfig<ExtendedOptions, ExtendedCommands>>) {
|
||||
return new Extension<ExtendedOptions, ExtendedCommands>({
|
||||
...this.config,
|
||||
...extendedConfig,
|
||||
} as ExtensionConfig<ExtendedOptions, ExtendedCommands>)
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ export default class ExtensionManager {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
type: getSchemaTypeByName(extension.config.name, this.schema),
|
||||
}
|
||||
|
||||
const commands = extension.addCommands.bind(context)()
|
||||
const commands = extension.config.addCommands.bind(context)()
|
||||
|
||||
editor.registerCommands(commands)
|
||||
})
|
||||
@ -43,10 +43,10 @@ export default class ExtensionManager {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
type: getSchemaTypeByName(extension.config.name, this.schema),
|
||||
}
|
||||
|
||||
return extension.addProseMirrorPlugins.bind(context)()
|
||||
return extension.config.addProseMirrorPlugins.bind(context)()
|
||||
})
|
||||
.flat()
|
||||
|
||||
@ -64,10 +64,10 @@ export default class ExtensionManager {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
type: getSchemaTypeByName(extension.config.name, this.schema),
|
||||
}
|
||||
|
||||
return extension.addInputRules.bind(context)()
|
||||
return extension.config.addInputRules.bind(context)()
|
||||
})
|
||||
.flat()
|
||||
}
|
||||
@ -78,10 +78,10 @@ export default class ExtensionManager {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
type: getSchemaTypeByName(extension.config.name, this.schema),
|
||||
}
|
||||
|
||||
return extension.addPasteRules.bind(context)()
|
||||
return extension.config.addPasteRules.bind(context)()
|
||||
})
|
||||
.flat()
|
||||
}
|
||||
@ -91,10 +91,10 @@ export default class ExtensionManager {
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor: this.editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
type: getSchemaTypeByName(extension.config.name, this.schema),
|
||||
}
|
||||
|
||||
return keymap(extension.addKeyboardShortcuts.bind(context)())
|
||||
return keymap(extension.config.addKeyboardShortcuts.bind(context)())
|
||||
})
|
||||
}
|
||||
|
||||
@ -104,17 +104,17 @@ export default class ExtensionManager {
|
||||
const allAttributes = getAttributesFromExtensions(this.extensions)
|
||||
|
||||
return Object.fromEntries(nodeExtensions
|
||||
.filter(extension => !!extension.addNodeView)
|
||||
.filter(extension => !!extension.config.addNodeView)
|
||||
.map(extension => {
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name)
|
||||
const context = {
|
||||
options: extension.options,
|
||||
editor,
|
||||
type: getSchemaTypeByName(extension.name, this.schema),
|
||||
type: getSchemaTypeByName(extension.config.name, this.schema),
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const renderer = extension.addNodeView?.bind(context)?.() as NodeViewRenderer
|
||||
const renderer = extension.config.addNodeView?.bind(context)?.() as NodeViewRenderer
|
||||
|
||||
const nodeview = (
|
||||
node: ProsemirrorNode,
|
||||
@ -122,18 +122,18 @@ export default class ExtensionManager {
|
||||
getPos: (() => number) | boolean,
|
||||
decorations: Decoration[],
|
||||
) => {
|
||||
const attributes = getRenderedAttributes(node, extensionAttributes)
|
||||
const HTMLAttributes = getRenderedAttributes(node, extensionAttributes)
|
||||
|
||||
return renderer({
|
||||
editor,
|
||||
node,
|
||||
getPos,
|
||||
decorations,
|
||||
attributes,
|
||||
HTMLAttributes,
|
||||
})
|
||||
}
|
||||
|
||||
return [extension.name, nodeview]
|
||||
return [extension.config.name, nodeview]
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
import {
|
||||
DOMOutputSpec, MarkSpec, Mark, MarkType,
|
||||
DOMOutputSpec,
|
||||
MarkSpec,
|
||||
Mark as ProseMirrorMark,
|
||||
MarkType,
|
||||
} from 'prosemirror-model'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { ExtensionSpec, defaultExtension } from './Extension'
|
||||
import { ExtensionConfig } from './Extension'
|
||||
import { Attributes, Overwrite } from './types'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export interface MarkExtensionSpec<Options = {}, Commands = {}> extends Overwrite<ExtensionSpec<Options, Commands>, {
|
||||
export interface MarkConfig<Options = any, Commands = {}> extends Overwrite<ExtensionConfig<Options, Commands>, {
|
||||
/**
|
||||
* Inclusive
|
||||
*/
|
||||
@ -44,8 +47,8 @@ export interface MarkExtensionSpec<Options = {}, Commands = {}> extends Overwrit
|
||||
options: Options,
|
||||
},
|
||||
props: {
|
||||
mark: Mark,
|
||||
attributes: { [key: string]: any },
|
||||
mark: ProseMirrorMark,
|
||||
HTMLAttributes: { [key: string]: any },
|
||||
}
|
||||
) => DOMOutputSpec) | null,
|
||||
|
||||
@ -106,46 +109,62 @@ export interface MarkExtensionSpec<Options = {}, Commands = {}> extends Overwrit
|
||||
}) => Plugin[],
|
||||
}> {}
|
||||
|
||||
export type MarkExtension = Required<Omit<MarkExtensionSpec, 'defaultOptions'> & {
|
||||
type: string,
|
||||
options: {
|
||||
[key: string]: any
|
||||
},
|
||||
}>
|
||||
export class Mark<Options = any, Commands = {}> {
|
||||
config: Required<MarkConfig> = {
|
||||
name: 'mark',
|
||||
defaultOptions: {},
|
||||
addGlobalAttributes: () => [],
|
||||
addCommands: () => ({}),
|
||||
addKeyboardShortcuts: () => ({}),
|
||||
addInputRules: () => [],
|
||||
addPasteRules: () => [],
|
||||
addProseMirrorPlugins: () => [],
|
||||
inclusive: null,
|
||||
excludes: null,
|
||||
group: null,
|
||||
spanning: null,
|
||||
parseHTML: () => null,
|
||||
renderHTML: null,
|
||||
addAttributes: () => ({}),
|
||||
}
|
||||
|
||||
const defaultMark: MarkExtension = {
|
||||
...defaultExtension,
|
||||
type: 'mark',
|
||||
name: 'mark',
|
||||
inclusive: null,
|
||||
excludes: null,
|
||||
group: null,
|
||||
spanning: null,
|
||||
parseHTML: () => null,
|
||||
renderHTML: null,
|
||||
addAttributes: () => ({}),
|
||||
}
|
||||
options!: Options
|
||||
|
||||
export function createMark<Options extends {}, Commands extends {}>(config: MarkExtensionSpec<Options, Commands>) {
|
||||
const extend = <ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<MarkExtensionSpec<ExtendedOptions, ExtendedCommands>>) => {
|
||||
return createMark({
|
||||
constructor(config: MarkConfig<Options, Commands>) {
|
||||
this.config = {
|
||||
...this.config,
|
||||
...config,
|
||||
...extendedConfig,
|
||||
} as MarkExtensionSpec<ExtendedOptions, ExtendedCommands>)
|
||||
}
|
||||
|
||||
const setOptions = (options?: Partial<Options>) => {
|
||||
const { defaultOptions, ...rest } = config
|
||||
|
||||
return {
|
||||
...defaultMark,
|
||||
...rest,
|
||||
options: {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
} as Options,
|
||||
}
|
||||
|
||||
this.options = this.config.defaultOptions
|
||||
}
|
||||
|
||||
return Object.assign(setOptions, { config, extend })
|
||||
static create<O, C>(config: MarkConfig<O, C>) {
|
||||
return new Mark<O, C>(config)
|
||||
}
|
||||
|
||||
configure(options: Partial<Options>) {
|
||||
return Mark
|
||||
.create<Options, Commands>(this.config as MarkConfig<Options, Commands>)
|
||||
.#configure({
|
||||
...this.config.defaultOptions,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
#configure = (options: Partial<Options>) => {
|
||||
this.options = {
|
||||
...this.config.defaultOptions,
|
||||
...options,
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedCommands>>) {
|
||||
return new Mark<ExtendedOptions, ExtendedCommands>({
|
||||
...this.config,
|
||||
...extendedConfig,
|
||||
} as MarkConfig<ExtendedOptions, ExtendedCommands>)
|
||||
}
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
import {
|
||||
DOMOutputSpec, NodeSpec, Node, NodeType,
|
||||
DOMOutputSpec,
|
||||
NodeSpec,
|
||||
Node as ProseMirrorNode,
|
||||
NodeType,
|
||||
} from 'prosemirror-model'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { ExtensionSpec, defaultExtension } from './Extension'
|
||||
import { ExtensionConfig } from './Extension'
|
||||
import { Attributes, NodeViewRenderer, Overwrite } from './types'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export interface NodeExtensionSpec<Options = {}, Commands = {}> extends Overwrite<ExtensionSpec<Options, Commands>, {
|
||||
export interface NodeConfig<Options = any, Commands = {}> extends Overwrite<ExtensionConfig<Options, Commands>, {
|
||||
/**
|
||||
* TopNode
|
||||
*/
|
||||
@ -79,8 +82,8 @@ export interface NodeExtensionSpec<Options = {}, Commands = {}> extends Overwrit
|
||||
options: Options,
|
||||
},
|
||||
props: {
|
||||
node: Node,
|
||||
attributes: { [key: string]: any },
|
||||
node: ProseMirrorNode,
|
||||
HTMLAttributes: { [key: string]: any },
|
||||
}
|
||||
) => DOMOutputSpec) | null,
|
||||
|
||||
@ -150,54 +153,70 @@ export interface NodeExtensionSpec<Options = {}, Commands = {}> extends Overwrit
|
||||
}) => NodeViewRenderer) | null,
|
||||
}> {}
|
||||
|
||||
export type NodeExtension = Required<Omit<NodeExtensionSpec, 'defaultOptions'> & {
|
||||
type: string,
|
||||
options: {
|
||||
[key: string]: any
|
||||
},
|
||||
}>
|
||||
export class Node<Options = any, Commands = {}> {
|
||||
config: Required<NodeConfig> = {
|
||||
name: 'node',
|
||||
defaultOptions: {},
|
||||
addGlobalAttributes: () => [],
|
||||
addCommands: () => ({}),
|
||||
addKeyboardShortcuts: () => ({}),
|
||||
addInputRules: () => [],
|
||||
addPasteRules: () => [],
|
||||
addProseMirrorPlugins: () => [],
|
||||
topNode: false,
|
||||
content: null,
|
||||
marks: null,
|
||||
group: null,
|
||||
inline: null,
|
||||
atom: null,
|
||||
selectable: null,
|
||||
draggable: null,
|
||||
code: null,
|
||||
defining: null,
|
||||
isolating: null,
|
||||
parseHTML: () => null,
|
||||
renderHTML: null,
|
||||
addAttributes: () => ({}),
|
||||
addNodeView: null,
|
||||
}
|
||||
|
||||
const defaultNode: NodeExtension = {
|
||||
...defaultExtension,
|
||||
type: 'node',
|
||||
name: 'node',
|
||||
topNode: false,
|
||||
content: null,
|
||||
marks: null,
|
||||
group: null,
|
||||
inline: null,
|
||||
atom: null,
|
||||
selectable: null,
|
||||
draggable: null,
|
||||
code: null,
|
||||
defining: null,
|
||||
isolating: null,
|
||||
parseHTML: () => null,
|
||||
renderHTML: null,
|
||||
addAttributes: () => ({}),
|
||||
addNodeView: null,
|
||||
}
|
||||
options!: Options
|
||||
|
||||
export function createNode<Options extends {}, Commands extends {}>(config: NodeExtensionSpec<Options, Commands>) {
|
||||
const extend = <ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<NodeExtensionSpec<ExtendedOptions, ExtendedCommands>>) => {
|
||||
return createNode({
|
||||
constructor(config: NodeConfig<Options, Commands>) {
|
||||
this.config = {
|
||||
...this.config,
|
||||
...config,
|
||||
...extendedConfig,
|
||||
} as NodeExtensionSpec<ExtendedOptions, ExtendedCommands>)
|
||||
}
|
||||
|
||||
const setOptions = (options?: Partial<Options>) => {
|
||||
const { defaultOptions, ...rest } = config
|
||||
|
||||
return {
|
||||
...defaultNode,
|
||||
...rest,
|
||||
options: {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
} as Options,
|
||||
}
|
||||
|
||||
this.options = this.config.defaultOptions
|
||||
}
|
||||
|
||||
return Object.assign(setOptions, { config, extend })
|
||||
static create<O, C>(config: NodeConfig<O, C>) {
|
||||
return new Node<O, C>(config)
|
||||
}
|
||||
|
||||
configure(options: Partial<Options>) {
|
||||
return Node
|
||||
.create<Options, Commands>(this.config as NodeConfig<Options, Commands>)
|
||||
.#configure({
|
||||
...this.config.defaultOptions,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
#configure = (options: Partial<Options>) => {
|
||||
this.options = {
|
||||
...this.config.defaultOptions,
|
||||
...options,
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
extend<ExtendedOptions = Options, ExtendedCommands = Commands>(extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedCommands>>) {
|
||||
return new Node<ExtendedOptions, ExtendedCommands>({
|
||||
...this.config,
|
||||
...extendedConfig,
|
||||
} as NodeConfig<ExtendedOptions, ExtendedCommands>)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { createExtension } from '../Extension'
|
||||
import { Extension } from '../Extension'
|
||||
import blur from '../commands/blur'
|
||||
import clearContent from '../commands/clearContent'
|
||||
import clearNodes from '../commands/clearNodes'
|
||||
@ -28,7 +28,7 @@ import updateMarkAttributes from '../commands/updateMarkAttributes'
|
||||
import updateNodeAttributes from '../commands/updateNodeAttributes'
|
||||
import wrapInList from '../commands/wrapInList'
|
||||
|
||||
export const Commands = createExtension({
|
||||
export const Commands = Extension.create({
|
||||
addCommands() {
|
||||
return {
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { createExtension } from '../Extension'
|
||||
import { Extension } from '../Extension'
|
||||
|
||||
export const Editable = createExtension({
|
||||
export const Editable = Extension.create({
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
@ -14,9 +14,8 @@ export const Editable = createExtension({
|
||||
},
|
||||
})
|
||||
|
||||
// TODO: Editable circularly references itself!?
|
||||
// declare module '@tiptap/core' {
|
||||
// interface AllExtensions {
|
||||
// Editable: typeof Editable,
|
||||
// }
|
||||
// }
|
||||
declare module '@tiptap/core' {
|
||||
interface AllExtensions {
|
||||
Editable: typeof Editable,
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { createExtension } from '../Extension'
|
||||
import { Extension } from '../Extension'
|
||||
|
||||
export const FocusEvents = createExtension({
|
||||
export const FocusEvents = Extension.create({
|
||||
addProseMirrorPlugins() {
|
||||
const { editor } = this
|
||||
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
selectNodeBackward,
|
||||
} from 'prosemirror-commands'
|
||||
import { undoInputRule } from 'prosemirror-inputrules'
|
||||
import { createExtension } from '../Extension'
|
||||
import { Extension } from '../Extension'
|
||||
|
||||
export const Keymap = createExtension({
|
||||
export const Keymap = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
const handleBackspace = () => this.editor.commands.try(({ state, dispatch }) => [
|
||||
() => undoInputRule(state, dispatch),
|
||||
|
@ -6,8 +6,8 @@ export {
|
||||
} from './Editor'
|
||||
|
||||
export * from './Extension'
|
||||
export * from './NodeExtension'
|
||||
export * from './MarkExtension'
|
||||
export * from './Node'
|
||||
export * from './Mark'
|
||||
export * from './types'
|
||||
|
||||
export { default as nodeInputRule } from './inputRules/nodeInputRule'
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Node } from 'prosemirror-model'
|
||||
import { Node as ProseMirrorNode } from 'prosemirror-model'
|
||||
import { Decoration, NodeView } from 'prosemirror-view'
|
||||
import { Extension } from './Extension'
|
||||
import { NodeExtension } from './NodeExtension'
|
||||
import { MarkExtension } from './MarkExtension'
|
||||
import { Node } from './Node'
|
||||
import { Mark } from './Mark'
|
||||
import { Editor } from './Editor'
|
||||
|
||||
export type Extensions = (Extension | NodeExtension | MarkExtension)[]
|
||||
export type Extensions = (Extension | Node | Mark)[]
|
||||
|
||||
export type Attribute = {
|
||||
default: any,
|
||||
@ -46,10 +46,10 @@ export type AnyObject = {
|
||||
|
||||
export type NodeViewRendererProps = {
|
||||
editor: Editor,
|
||||
node: Node,
|
||||
node: ProseMirrorNode,
|
||||
getPos: (() => number) | boolean,
|
||||
decorations: Decoration[],
|
||||
attributes: AnyObject,
|
||||
HTMLAttributes: { [key: string]: any },
|
||||
}
|
||||
|
||||
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView
|
||||
|
@ -27,7 +27,7 @@ export default function getAttributesFromExtensions(extensions: Extensions) {
|
||||
options: extension.options,
|
||||
}
|
||||
|
||||
const globalAttributes = extension.addGlobalAttributes.bind(context)() as GlobalAttributes
|
||||
const globalAttributes = extension.config.addGlobalAttributes.bind(context)() as GlobalAttributes
|
||||
|
||||
globalAttributes.forEach(globalAttribute => {
|
||||
globalAttribute.types.forEach(type => {
|
||||
@ -52,13 +52,13 @@ export default function getAttributesFromExtensions(extensions: Extensions) {
|
||||
options: extension.options,
|
||||
}
|
||||
|
||||
const attributes = extension.addAttributes.bind(context)() as Attributes
|
||||
const attributes = extension.config.addAttributes.bind(context)() as Attributes
|
||||
|
||||
Object
|
||||
.entries(attributes)
|
||||
.forEach(([name, attribute]) => {
|
||||
extensionAttributes.push({
|
||||
type: extension.name,
|
||||
type: extension.config.name,
|
||||
name,
|
||||
attribute: {
|
||||
...defaultAttribute,
|
||||
|
@ -6,6 +6,7 @@ import getRenderedAttributes from './getRenderedAttributes'
|
||||
import isEmptyObject from './isEmptyObject'
|
||||
import injectExtensionAttributesToParseRule from './injectExtensionAttributesToParseRule'
|
||||
import callOrReturn from './callOrReturn'
|
||||
import mergeAttributes from './mergeAttributes'
|
||||
|
||||
function cleanUpSchemaItem<T>(data: T) {
|
||||
return Object.fromEntries(Object.entries(data).filter(([key, value]) => {
|
||||
@ -20,70 +21,76 @@ function cleanUpSchemaItem<T>(data: T) {
|
||||
export default function getSchema(extensions: Extensions): Schema {
|
||||
const allAttributes = getAttributesFromExtensions(extensions)
|
||||
const { nodeExtensions, markExtensions } = splitExtensions(extensions)
|
||||
const topNode = nodeExtensions.find(extension => extension.topNode)?.name
|
||||
const topNode = nodeExtensions.find(extension => extension.config.topNode)?.config.name
|
||||
|
||||
const nodes = Object.fromEntries(nodeExtensions.map(extension => {
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name)
|
||||
const context = { options: extension.options }
|
||||
const schema: NodeSpec = cleanUpSchemaItem({
|
||||
content: callOrReturn(extension.content, context),
|
||||
marks: callOrReturn(extension.marks, context),
|
||||
group: callOrReturn(extension.group, context),
|
||||
inline: callOrReturn(extension.inline, context),
|
||||
atom: callOrReturn(extension.atom, context),
|
||||
selectable: callOrReturn(extension.selectable, context),
|
||||
draggable: callOrReturn(extension.draggable, context),
|
||||
code: callOrReturn(extension.code, context),
|
||||
defining: callOrReturn(extension.defining, context),
|
||||
isolating: callOrReturn(extension.isolating, context),
|
||||
content: callOrReturn(extension.config.content, context),
|
||||
marks: callOrReturn(extension.config.marks, context),
|
||||
group: callOrReturn(extension.config.group, context),
|
||||
inline: callOrReturn(extension.config.inline, context),
|
||||
atom: callOrReturn(extension.config.atom, context),
|
||||
selectable: callOrReturn(extension.config.selectable, context),
|
||||
draggable: callOrReturn(extension.config.draggable, context),
|
||||
code: callOrReturn(extension.config.code, context),
|
||||
defining: callOrReturn(extension.config.defining, context),
|
||||
isolating: callOrReturn(extension.config.isolating, context),
|
||||
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
|
||||
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
|
||||
})),
|
||||
})
|
||||
|
||||
if (extension.parseHTML) {
|
||||
schema.parseDOM = extension.parseHTML
|
||||
if (extension.config.parseHTML) {
|
||||
schema.parseDOM = extension.config.parseHTML
|
||||
.bind(context)()
|
||||
?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
|
||||
}
|
||||
|
||||
if (extension.renderHTML) {
|
||||
schema.toDOM = node => (extension.renderHTML as Function)?.bind(context)({
|
||||
if (extension.config.renderHTML) {
|
||||
schema.toDOM = node => (extension.config.renderHTML as Function)?.bind(context)({
|
||||
node,
|
||||
attributes: getRenderedAttributes(node, extensionAttributes),
|
||||
HTMLAttributes: mergeAttributes(
|
||||
extension.options.HTMLAttributes,
|
||||
getRenderedAttributes(node, extensionAttributes),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return [extension.name, schema]
|
||||
return [extension.config.name, schema]
|
||||
}))
|
||||
|
||||
const marks = Object.fromEntries(markExtensions.map(extension => {
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
||||
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name)
|
||||
const context = { options: extension.options }
|
||||
const schema: MarkSpec = cleanUpSchemaItem({
|
||||
inclusive: callOrReturn(extension.inclusive, context),
|
||||
excludes: callOrReturn(extension.excludes, context),
|
||||
group: callOrReturn(extension.group, context),
|
||||
spanning: callOrReturn(extension.spanning, context),
|
||||
inclusive: callOrReturn(extension.config.inclusive, context),
|
||||
excludes: callOrReturn(extension.config.excludes, context),
|
||||
group: callOrReturn(extension.config.group, context),
|
||||
spanning: callOrReturn(extension.config.spanning, context),
|
||||
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
|
||||
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
|
||||
})),
|
||||
})
|
||||
|
||||
if (extension.parseHTML) {
|
||||
schema.parseDOM = extension.parseHTML
|
||||
if (extension.config.parseHTML) {
|
||||
schema.parseDOM = extension.config.parseHTML
|
||||
.bind(context)()
|
||||
?.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
|
||||
}
|
||||
|
||||
if (extension.renderHTML) {
|
||||
schema.toDOM = mark => (extension.renderHTML as Function)?.bind(context)({
|
||||
if (extension.config.renderHTML) {
|
||||
schema.toDOM = mark => (extension.config.renderHTML as Function)?.bind(context)({
|
||||
mark,
|
||||
attributes: getRenderedAttributes(mark, extensionAttributes),
|
||||
HTMLAttributes: mergeAttributes(
|
||||
extension.options.HTMLAttributes,
|
||||
getRenderedAttributes(mark, extensionAttributes),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return [extension.name, schema]
|
||||
return [extension.config.name, schema]
|
||||
}))
|
||||
|
||||
return new Schema({
|
||||
|
@ -4,13 +4,13 @@ import callOrReturn from './callOrReturn'
|
||||
|
||||
export default function isList(name: string, extensions: Extensions) {
|
||||
const { nodeExtensions } = splitExtensions(extensions)
|
||||
const extension = nodeExtensions.find(item => item.name === name)
|
||||
const extension = nodeExtensions.find(item => item.config.name === name)
|
||||
|
||||
if (!extension) {
|
||||
return false
|
||||
}
|
||||
|
||||
const groups = callOrReturn(extension.group, { options: extension.options })
|
||||
const groups = callOrReturn(extension.config.group, { options: extension.options })
|
||||
|
||||
if (typeof groups !== 'string') {
|
||||
return false
|
||||
|
@ -1,26 +1,28 @@
|
||||
import { AnyObject } from '../types'
|
||||
|
||||
export default function mergeAttributes(...object: AnyObject[]) {
|
||||
return object.reduce((items, item) => {
|
||||
const mergedAttributes = { ...items }
|
||||
export default function mergeAttributes(...objects: AnyObject[]) {
|
||||
return objects
|
||||
.filter(item => !!item)
|
||||
.reduce((items, item) => {
|
||||
const mergedAttributes = { ...items }
|
||||
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
const exists = mergedAttributes[key]
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
const exists = mergedAttributes[key]
|
||||
|
||||
if (!exists) {
|
||||
mergedAttributes[key] = value
|
||||
return
|
||||
}
|
||||
if (!exists) {
|
||||
mergedAttributes[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'class') {
|
||||
mergedAttributes[key] = [mergedAttributes[key], value].join(' ')
|
||||
} else if (key === 'style') {
|
||||
mergedAttributes[key] = [mergedAttributes[key], value].join('; ')
|
||||
} else {
|
||||
mergedAttributes[key] = value
|
||||
}
|
||||
})
|
||||
if (key === 'class') {
|
||||
mergedAttributes[key] = [mergedAttributes[key], value].join(' ')
|
||||
} else if (key === 'style') {
|
||||
mergedAttributes[key] = [mergedAttributes[key], value].join('; ')
|
||||
} else {
|
||||
mergedAttributes[key] = value
|
||||
}
|
||||
})
|
||||
|
||||
return mergedAttributes
|
||||
}, {})
|
||||
return mergedAttributes
|
||||
}, {})
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Extensions } from '../types'
|
||||
import { Extension } from '../Extension'
|
||||
import { NodeExtension } from '../NodeExtension'
|
||||
import { MarkExtension } from '../MarkExtension'
|
||||
import { Node } from '../Node'
|
||||
import { Mark } from '../Mark'
|
||||
|
||||
export default function splitExtensions(extensions: Extensions) {
|
||||
const baseExtensions = extensions.filter(extension => extension.type === 'extension') as Extension[]
|
||||
const nodeExtensions = extensions.filter(extension => extension.type === 'node') as NodeExtension[]
|
||||
const markExtensions = extensions.filter(extension => extension.type === 'mark') as MarkExtension[]
|
||||
const baseExtensions = extensions.filter(extension => extension instanceof Extension) as Extension[]
|
||||
const nodeExtensions = extensions.filter(extension => extension instanceof Node) as Node[]
|
||||
const markExtensions = extensions.filter(extension => extension instanceof Mark) as Mark[]
|
||||
|
||||
return {
|
||||
baseExtensions,
|
||||
|
@ -1,11 +1,21 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
import { wrappingInputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export interface BlockquoteOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /^\s*>\s$/gm
|
||||
|
||||
const Blockquote = createNode({
|
||||
const Blockquote = Node.create({
|
||||
name: 'blockquote',
|
||||
|
||||
defaultOptions: <BlockquoteOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
content: 'block*',
|
||||
|
||||
group: 'block',
|
||||
@ -18,8 +28,8 @@ const Blockquote = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['blockquote', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['blockquote', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,15 +1,25 @@
|
||||
import {
|
||||
Command, createMark, markInputRule, markPasteRule,
|
||||
Command, Mark, markInputRule, markPasteRule,
|
||||
} from '@tiptap/core'
|
||||
|
||||
export interface BoldOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const starInputRegex = /(?:^|\s)((?:\*\*)((?:[^*]+))(?:\*\*))$/gm
|
||||
export const starPasteRegex = /(?:^|\s)((?:\*\*)((?:[^*]+))(?:\*\*))/gm
|
||||
export const underscoreInputRegex = /(?:^|\s)((?:__)((?:[^__]+))(?:__))$/gm
|
||||
export const underscorePasteRegex = /(?:^|\s)((?:__)((?:[^__]+))(?:__))/gm
|
||||
|
||||
const Bold = createMark({
|
||||
const Bold = Mark.create({
|
||||
name: 'bold',
|
||||
|
||||
defaultOptions: <BoldOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
@ -26,8 +36,8 @@ const Bold = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['strong', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['strong', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,11 +1,21 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
import { wrappingInputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export interface BulletListOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /^\s*([-+*])\s$/
|
||||
|
||||
const BulletList = createNode({
|
||||
const BulletList = Node.create({
|
||||
name: 'bulletList',
|
||||
|
||||
defaultOptions: <BulletListOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block list',
|
||||
|
||||
content: 'listItem+',
|
||||
@ -16,8 +26,8 @@ const BulletList = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['ul', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['ul', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,18 +1,22 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
import { textblockTypeInputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export interface CodeBlockOptions {
|
||||
languageClassPrefix: string,
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const backtickInputRegex = /^```(?<language>[a-z]*)? $/
|
||||
export const tildeInputRegex = /^~~~(?<language>[a-z]*)? $/
|
||||
|
||||
const CodeBlock = createNode({
|
||||
const CodeBlock = Node.create({
|
||||
name: 'codeBlock',
|
||||
|
||||
defaultOptions: <CodeBlockOptions>{
|
||||
languageClassPrefix: 'language-',
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
content: 'text*',
|
||||
@ -64,8 +68,8 @@ const CodeBlock = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['pre', ['code', attributes, 0]]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['pre', ['code', HTMLAttributes, 0]]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,13 +1,26 @@
|
||||
import {
|
||||
Command, createMark, markInputRule, markPasteRule,
|
||||
Command,
|
||||
Mark,
|
||||
markInputRule,
|
||||
markPasteRule,
|
||||
} from '@tiptap/core'
|
||||
|
||||
export interface CodeOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))$/gm
|
||||
export const pasteRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))/gm
|
||||
|
||||
const Code = createMark({
|
||||
const Code = Mark.create({
|
||||
name: 'code',
|
||||
|
||||
defaultOptions: <CodeOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
excludes: '_',
|
||||
|
||||
parseHTML() {
|
||||
@ -16,8 +29,8 @@ const Code = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['code', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['code', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createExtension, Command } from '@tiptap/core'
|
||||
import { Extension, Command } from '@tiptap/core'
|
||||
import { yCursorPlugin } from 'y-prosemirror'
|
||||
|
||||
export interface CollaborationCursorOptions {
|
||||
@ -8,7 +8,7 @@ export interface CollaborationCursorOptions {
|
||||
render (user: { name: string, color: string }): HTMLElement,
|
||||
}
|
||||
|
||||
const CollaborationCursor = createExtension({
|
||||
const CollaborationCursor = Extension.create({
|
||||
defaultOptions: <CollaborationCursorOptions>{
|
||||
provider: null,
|
||||
name: 'Someone',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import {
|
||||
redo, undo, ySyncPlugin, yUndoPlugin,
|
||||
} from 'y-prosemirror'
|
||||
@ -8,7 +8,7 @@ export interface CollaborationOptions {
|
||||
type: any,
|
||||
}
|
||||
|
||||
const Collaboration = createExtension({
|
||||
const Collaboration = Extension.create({
|
||||
defaultOptions: <CollaborationOptions>{
|
||||
provider: null,
|
||||
type: null,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createNode } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
const Document = createNode({
|
||||
const Document = Node.create({
|
||||
name: 'document',
|
||||
topNode: true,
|
||||
content: 'block+',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { dropCursor } from 'prosemirror-dropcursor'
|
||||
|
||||
const Dropcursor = createExtension({
|
||||
const Dropcursor = Extension.create({
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
dropCursor(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { DecorationSet, Decoration } from 'prosemirror-view'
|
||||
|
||||
@ -7,7 +7,7 @@ export interface FocusOptions {
|
||||
nested: boolean,
|
||||
}
|
||||
|
||||
const FocusClasses = createExtension({
|
||||
const FocusClasses = Extension.create({
|
||||
defaultOptions: <FocusOptions>{
|
||||
className: 'has-focus',
|
||||
nested: false,
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Command, createExtension } from '@tiptap/core'
|
||||
import { Command, Extension } from '@tiptap/core'
|
||||
import '@tiptap/extension-text-style'
|
||||
|
||||
type FontFamilyOptions = {
|
||||
types: string[],
|
||||
}
|
||||
|
||||
const FontFamily = createExtension({
|
||||
const FontFamily = Extension.create({
|
||||
defaultOptions: <FontFamilyOptions>{
|
||||
types: ['textStyle'],
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { gapCursor } from 'prosemirror-gapcursor'
|
||||
|
||||
const Gapcursor = createExtension({
|
||||
const Gapcursor = Extension.create({
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
gapCursor(),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
import { exitCode } from 'prosemirror-commands'
|
||||
|
||||
const HardBreak = createNode({
|
||||
const HardBreak = Node.create({
|
||||
name: 'hardBreak',
|
||||
|
||||
inline: true,
|
||||
@ -16,8 +16,8 @@ const HardBreak = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['br', attributes]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['br', HTMLAttributes]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,17 +1,21 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
import { textblockTypeInputRule } from 'prosemirror-inputrules'
|
||||
|
||||
type Level = 1 | 2 | 3 | 4 | 5 | 6
|
||||
|
||||
export interface HeadingOptions {
|
||||
levels: Level[],
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const Heading = createNode({
|
||||
const Heading = Node.create({
|
||||
name: 'heading',
|
||||
|
||||
defaultOptions: <HeadingOptions>{
|
||||
levels: [1, 2, 3, 4, 5, 6],
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
content: 'inline*',
|
||||
@ -37,13 +41,13 @@ const Heading = createNode({
|
||||
}))
|
||||
},
|
||||
|
||||
renderHTML({ node, attributes }) {
|
||||
renderHTML({ node, HTMLAttributes }) {
|
||||
const hasLevel = this.options.levels.includes(node.attrs.level)
|
||||
const level = hasLevel
|
||||
? node.attrs.level
|
||||
: this.options.levels[0]
|
||||
|
||||
return [`h${level}`, attributes, 0]
|
||||
return [`h${level}`, HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,16 +1,26 @@
|
||||
import {
|
||||
Command,
|
||||
createMark,
|
||||
Mark,
|
||||
markInputRule,
|
||||
markPasteRule,
|
||||
} from '@tiptap/core'
|
||||
|
||||
export interface HighlightOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /(?:^|\s)((?:==)((?:[^~]+))(?:==))$/gm
|
||||
export const pasteRegex = /(?:^|\s)((?:==)((?:[^~]+))(?:==))/gm
|
||||
|
||||
const Highlight = createMark({
|
||||
const Highlight = Mark.create({
|
||||
name: 'highlight',
|
||||
|
||||
defaultOptions: <HighlightOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
color: {
|
||||
@ -42,8 +52,8 @@ const Highlight = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['mark', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['mark', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Command, createExtension } from '@tiptap/core'
|
||||
import { Command, Extension } from '@tiptap/core'
|
||||
import { history, undo, redo } from 'prosemirror-history'
|
||||
|
||||
export interface HistoryOptions {
|
||||
@ -6,7 +6,7 @@ export interface HistoryOptions {
|
||||
newGroupDelay: number,
|
||||
}
|
||||
|
||||
const History = createExtension({
|
||||
const History = Extension.create({
|
||||
defaultOptions: <HistoryOptions>{
|
||||
depth: 100,
|
||||
newGroupDelay: 500,
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { Command, createNode, nodeInputRule } from '@tiptap/core'
|
||||
import { Command, Node, nodeInputRule } from '@tiptap/core'
|
||||
|
||||
const HorizontalRule = createNode({
|
||||
export interface HorizontalRuleOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const HorizontalRule = Node.create({
|
||||
name: 'horizontalRule',
|
||||
|
||||
defaultOptions: <HorizontalRuleOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
||||
parseHTML() {
|
||||
@ -11,8 +21,8 @@ const HorizontalRule = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['hr', attributes]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['hr', HTMLAttributes]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { Command, createNode, nodeInputRule } from '@tiptap/core'
|
||||
import { Command, Node, nodeInputRule } from '@tiptap/core'
|
||||
|
||||
export interface ImageOptions {
|
||||
inline: boolean,
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/
|
||||
|
||||
const Image = createNode({
|
||||
const Image = Node.create({
|
||||
name: 'image',
|
||||
|
||||
defaultOptions: <ImageOptions>{
|
||||
inline: false,
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
inline() {
|
||||
@ -45,8 +49,8 @@ const Image = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['img', attributes]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['img', HTMLAttributes]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,15 +1,28 @@
|
||||
import {
|
||||
Command, createMark, markInputRule, markPasteRule,
|
||||
Command,
|
||||
Mark,
|
||||
markInputRule,
|
||||
markPasteRule,
|
||||
} from '@tiptap/core'
|
||||
|
||||
export interface ItalicOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const starInputRegex = /(?:^|\s)((?:\*)((?:[^*]+))(?:\*))$/gm
|
||||
export const starPasteRegex = /(?:^|\s)((?:\*)((?:[^*]+))(?:\*))/gm
|
||||
export const underscoreInputRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))$/gm
|
||||
export const underscorePasteRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))/gm
|
||||
|
||||
const Italic = createMark({
|
||||
const Italic = Mark.create({
|
||||
name: 'italic',
|
||||
|
||||
defaultOptions: <ItalicOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
@ -25,8 +38,8 @@ const Italic = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['em', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['em', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,25 +1,26 @@
|
||||
import {
|
||||
Command, createMark, markPasteRule, mergeAttributes,
|
||||
} from '@tiptap/core'
|
||||
import { Command, Mark, markPasteRule } from '@tiptap/core'
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
|
||||
export interface LinkOptions {
|
||||
openOnClick: boolean,
|
||||
target: string,
|
||||
rel: string,
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const pasteRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/()]*)/gi
|
||||
|
||||
const Link = createMark({
|
||||
const Link = Mark.create({
|
||||
name: 'link',
|
||||
|
||||
inclusive: false,
|
||||
|
||||
defaultOptions: <LinkOptions>{
|
||||
openOnClick: true,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
HTMLAttributes: {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
},
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
@ -28,7 +29,7 @@ const Link = createMark({
|
||||
default: null,
|
||||
},
|
||||
target: {
|
||||
default: this.options.target,
|
||||
default: this.options.HTMLAttributes.target,
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -39,8 +40,8 @@ const Link = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['a', mergeAttributes(attributes, { rel: this.options.rel }), 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['a', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { createNode } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
const ListItem = createNode({
|
||||
export interface ListItemOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const ListItem = Node.create({
|
||||
name: 'listItem',
|
||||
|
||||
defaultOptions: <ListItemOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
content: '(paragraph|list?)+',
|
||||
|
||||
defining: true,
|
||||
@ -15,8 +25,8 @@ const ListItem = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['li', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['li', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
|
@ -1,11 +1,21 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
import { wrappingInputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export interface OrderedListOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /^(\d+)\.\s$/
|
||||
|
||||
const OrderedList = createNode({
|
||||
const OrderedList = Node.create({
|
||||
name: 'orderedList',
|
||||
|
||||
defaultOptions: <OrderedListOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block list',
|
||||
|
||||
content: 'listItem+',
|
||||
@ -31,12 +41,12 @@ const OrderedList = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
const { start, ...attributesWithoutStart } = attributes
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
const { start, ...attributesWithoutStart } = HTMLAttributes
|
||||
|
||||
return start === 1
|
||||
? ['ol', attributesWithoutStart, 0]
|
||||
: ['ol', attributes, 0]
|
||||
: ['ol', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<p data-component="yeah" ref="content"></p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
@ -1,9 +1,18 @@
|
||||
import { Command, createNode } from '@tiptap/core'
|
||||
// import ParagraphComponent from './paragraph.vue'
|
||||
import { Command, Node } from '@tiptap/core'
|
||||
|
||||
const Paragraph = createNode({
|
||||
export interface ParagraphOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const Paragraph = Node.create({
|
||||
name: 'paragraph',
|
||||
|
||||
defaultOptions: <ParagraphOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
||||
content: 'inline*',
|
||||
@ -14,8 +23,8 @@ const Paragraph = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['p', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['p', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,13 +1,26 @@
|
||||
import {
|
||||
Command, createMark, markInputRule, markPasteRule,
|
||||
Command,
|
||||
Mark,
|
||||
markInputRule,
|
||||
markPasteRule,
|
||||
} from '@tiptap/core'
|
||||
|
||||
export interface StrikeOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export const inputRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))$/gm
|
||||
export const pasteRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))/gm
|
||||
|
||||
const Strike = createMark({
|
||||
const Strike = Mark.create({
|
||||
name: 'strike',
|
||||
|
||||
defaultOptions: <StrikeOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
@ -25,8 +38,8 @@ const Strike = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['s', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['s', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,25 +1,29 @@
|
||||
import { createNode, mergeAttributes } from '@tiptap/core'
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { wrappingInputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export const inputRegex = /^\s*(\[([ |x])\])\s$/
|
||||
|
||||
export interface TaskItemOptions {
|
||||
nested: boolean,
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const TaskItem = createNode({
|
||||
export const inputRegex = /^\s*(\[([ |x])\])\s$/
|
||||
|
||||
const TaskItem = Node.create({
|
||||
name: 'taskItem',
|
||||
|
||||
defaultOptions: <TaskItemOptions>{
|
||||
nested: false,
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
content() {
|
||||
return this.options.nested ? '(paragraph|taskList)+' : 'paragraph+'
|
||||
},
|
||||
|
||||
defining: true,
|
||||
|
||||
defaultOptions: <TaskItemOptions>{
|
||||
nested: false,
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
checked: {
|
||||
@ -43,8 +47,8 @@ const TaskItem = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['li', mergeAttributes(attributes, { 'data-type': 'taskItem' }), 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['li', mergeAttributes(HTMLAttributes, { 'data-type': 'taskItem' }), 0]
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
@ -64,7 +68,7 @@ const TaskItem = createNode({
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ attributes, getPos, editor }) => {
|
||||
return ({ HTMLAttributes, getPos, editor }) => {
|
||||
const { view } = editor
|
||||
const listItem = document.createElement('li')
|
||||
const checkbox = document.createElement('input')
|
||||
@ -82,13 +86,13 @@ const TaskItem = createNode({
|
||||
}
|
||||
})
|
||||
|
||||
if (attributes['data-checked'] === true) {
|
||||
if (HTMLAttributes['data-checked'] === true) {
|
||||
checkbox.setAttribute('checked', 'checked')
|
||||
}
|
||||
|
||||
listItem.append(checkbox, content)
|
||||
|
||||
Object.entries(attributes).forEach(([key, value]) => {
|
||||
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
||||
listItem.setAttribute(key, value)
|
||||
})
|
||||
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { Command, createNode, mergeAttributes } from '@tiptap/core'
|
||||
import { Command, Node, mergeAttributes } from '@tiptap/core'
|
||||
|
||||
const TaskList = createNode({
|
||||
export interface TaskListOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const TaskList = Node.create({
|
||||
name: 'taskList',
|
||||
|
||||
defaultOptions: <TaskListOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block list',
|
||||
|
||||
content: 'taskItem+',
|
||||
@ -16,8 +26,8 @@ const TaskList = createNode({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['ul', mergeAttributes(attributes, { 'data-type': 'taskList' }), 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['ul', mergeAttributes(HTMLAttributes, { 'data-type': 'taskList' }), 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Command, createExtension } from '@tiptap/core'
|
||||
import { Command, Extension } from '@tiptap/core'
|
||||
|
||||
type TextAlignOptions = {
|
||||
types: string[],
|
||||
@ -6,7 +6,7 @@ type TextAlignOptions = {
|
||||
defaultAlignment: string,
|
||||
}
|
||||
|
||||
const TextAlign = createExtension({
|
||||
const TextAlign = Extension.create({
|
||||
defaultOptions: <TextAlignOptions>{
|
||||
types: ['heading', 'paragraph'],
|
||||
alignments: ['left', 'center', 'right', 'justify'],
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Command, createMark, getMarkAttrs } from '@tiptap/core'
|
||||
import { Command, Mark, getMarkAttrs } from '@tiptap/core'
|
||||
|
||||
const TextStyle = createMark({
|
||||
const TextStyle = Mark.create({
|
||||
name: 'textStyle',
|
||||
|
||||
parseHTML() {
|
||||
@ -20,8 +20,8 @@ const TextStyle = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['span', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['span', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createNode } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
const Text = createNode({
|
||||
const Text = Node.create({
|
||||
name: 'text',
|
||||
group: 'inline',
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createExtension } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import {
|
||||
emDash,
|
||||
ellipsis,
|
||||
@ -20,7 +20,7 @@ export const laquo = new InputRule(/<<$/, '«')
|
||||
export const raquo = new InputRule(/>>$/, '»')
|
||||
export const multiplication = new InputRule(/\d+\s?([*x])\s?\d+$/, '×')
|
||||
|
||||
const Typography = createExtension({
|
||||
const Typography = Extension.create({
|
||||
addInputRules() {
|
||||
return [
|
||||
emDash,
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { Command, createMark } from '@tiptap/core'
|
||||
import { Command, Mark } from '@tiptap/core'
|
||||
|
||||
const Underline = createMark({
|
||||
export interface UnderlineOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const Underline = Mark.create({
|
||||
name: 'underline',
|
||||
|
||||
defaultOptions: <UnderlineOptions>{
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
@ -14,8 +24,8 @@ const Underline = createMark({
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ attributes }) {
|
||||
return ['u', attributes, 0]
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['u', HTMLAttributes, 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -23,23 +23,23 @@ export function defaultExtensions(options: {
|
||||
heading: HeadingOptions,
|
||||
}) {
|
||||
return [
|
||||
Dropcursor(),
|
||||
Gapcursor(),
|
||||
Document(),
|
||||
History(options?.history),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Bold(),
|
||||
Italic(),
|
||||
Code(),
|
||||
CodeBlock(options?.codeBlock),
|
||||
Heading(options?.heading),
|
||||
HardBreak(),
|
||||
Strike(),
|
||||
Blockquote(),
|
||||
HorizontalRule(),
|
||||
BulletList(),
|
||||
OrderedList(),
|
||||
ListItem(),
|
||||
Dropcursor,
|
||||
Gapcursor,
|
||||
Document,
|
||||
History.configure(options?.history),
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
Italic,
|
||||
Code,
|
||||
CodeBlock.configure(options?.codeBlock),
|
||||
Heading.configure(options?.heading),
|
||||
HardBreak,
|
||||
Strike,
|
||||
Blockquote,
|
||||
HorizontalRule,
|
||||
BulletList,
|
||||
OrderedList,
|
||||
ListItem,
|
||||
]
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ describe('generateHTML', () => {
|
||||
}
|
||||
|
||||
const html = generateHTML(json, [
|
||||
Document(),
|
||||
Paragraph(),
|
||||
Text(),
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
])
|
||||
|
||||
expect(html).to.eq('<p>Example Text</p>')
|
||||
|
@ -57,4 +57,13 @@ describe('mergeAttributes', () => {
|
||||
style: 'color: red; background: green',
|
||||
})
|
||||
})
|
||||
|
||||
it('should ignore falsy values', () => {
|
||||
// @ts-expect-error
|
||||
const value = mergeAttributes(undefined, { class: 'foo' })
|
||||
|
||||
expect(value).to.deep.eq({
|
||||
class: 'foo',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user