feat(pm): new prosemirror package for dependency resolving

* chore:(core): migrate to tsup

* chore: migrate blockquote and bold to tsup

* chore: migrated bubble-menu and bullet-list to tsup

* chore: migrated more packages to tsup

* chore: migrate code and character extensions to tsup

* chore: update package.json to simplify build for all packages

* chore: move all packages to tsup as a build process

* chore: change ci build task

* feat(pm): add prosemirror meta package

* rfix: resolve issues with build paths & export mappings

* docs: update documentation to include notes for @tiptap/pm

* chore(pm): update tsconfig

* chore(packages): update packages

* fix(pm): add package export infos & fix dependencies

* chore(general): start moving to pm package as deps

* chore: move to tiptap pm package internally

* fix(demos): fix demos working with new pm package

* fix(tables): fix tables package

* fix(tables): fix tables package

* chore(demos): pinned typescript version

* chore: remove unnecessary tsconfig

* chore: fix netlify build

* fix(demos): fix package resolving for pm packages

* fix(tests): fix package resolving for pm packages

* fix(tests): fix package resolving for pm packages

* chore(tests): fix tests not running correctly after pm package

* chore(pm): add files to files array

* chore: update build workflow

* chore(tests): increase timeout time back to 12s

* chore(docs): update docs

* chore(docs): update installation guides & pm information to docs

* chore(docs): add link to prosemirror docs

* fix(vue-3): add missing build step

* chore(docs): comment out cdn link

* chore(docs): remove semicolons from docs

* chore(docs): remove unnecessary installation note

* chore(docs): remove unnecessary installation note
This commit is contained in:
Dominik 2023-02-02 17:37:33 +01:00 committed by GitHub
parent 0ecb5a8df8
commit f387ad3dd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
271 changed files with 3903 additions and 3124 deletions

View File

@ -23,50 +23,49 @@ jobs:
node-version: [16] node-version: [16]
steps: steps:
- uses: actions/checkout@v3.0.2
- uses: actions/checkout@v3.0.2 - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.5.1
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }} - name: Load cached dependencies
uses: actions/setup-node@v3.5.1 uses: actions/cache@v3.0.11
with: id: cache
node-version: ${{ matrix.node-version }} with:
path: |
**/node_modules
/home/runner/.cache/Cypress
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Load cached dependencies - name: Install dependencies
uses: actions/cache@v3.0.11 id: install-dependencies
id: cache if: steps.cache.outputs.cache-hit != 'true'
with: run: npm install
path: |
**/node_modules
/home/runner/.cache/Cypress
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies # - name: Fix code style linting errors
id: install-dependencies # id: lint-fix
if: steps.cache.outputs.cache-hit != 'true' # run: npm run lint:fix
run: npm install # continue-on-error: true
#
# - name: Commit fixed linting errors
# id: commit
# uses: stefanzweifel/git-auto-commit-action@v4
# with:
# commit_message: "ci: fix code style linting errors"
# - name: Fix code style linting errors - name: Lint code
# id: lint-fix id: lint
# run: npm run lint:fix run: npm run lint
# continue-on-error: true
#
# - name: Commit fixed linting errors
# id: commit
# uses: stefanzweifel/git-auto-commit-action@v4
# with:
# commit_message: "ci: fix code style linting errors"
- name: Lint code - name: Send Slack notifications
id: lint uses: act10ns/slack@v1
run: npm run lint if: failure()
with:
- name: Send Slack notifications status: ${{ job.status }}
uses: act10ns/slack@v1 steps: ${{ toJson(steps) }}
if: failure() channel: '#tiptap-notifications'
with:
status: ${{ job.status }}
steps: ${{ toJson(steps) }}
channel: '#tiptap-notifications'
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -79,48 +78,55 @@ jobs:
node-version: [16] node-version: [16]
steps: steps:
- uses: actions/checkout@v3.0.2
- uses: actions/checkout@v3.0.2 - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.5.1
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }} - name: Install dependencies
uses: actions/setup-node@v3.5.1 id: install-dependencies
with: run: npm install
node-version: ${{ matrix.node-version }}
- name: Run tests with Cypress - name: Try to build the packages
id: cypress id: build-packages
uses: cypress-io/github-action@v4.2.0 run: npm run build:pm
with:
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
start: npm run start
wait-on: 'http://localhost:3000'
project: ./tests
browser: chrome
quiet: true
- name: Export screenshots (on failure only) - name: Run tests with Cypress
uses: actions/upload-artifact@v3.1.0 id: cypress
if: failure() uses: cypress-io/github-action@v4.2.0
with: with:
name: cypress-screenshots cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
path: tests/cypress/screenshots start: npm run start
retention-days: 7 wait-on: 'http://localhost:3000'
project: ./tests
browser: chrome
quiet: true
- name: Export screen recordings (on failure only) - name: Export screenshots (on failure only)
uses: actions/upload-artifact@v3.1.0 uses: actions/upload-artifact@v3.1.0
if: failure() if: failure()
with: with:
name: cypress-videos name: cypress-screenshots
path: tests/cypress/videos path: tests/cypress/screenshots
retention-days: 7 retention-days: 7
- name: Send Slack notifications - name: Export screen recordings (on failure only)
uses: act10ns/slack@v1 uses: actions/upload-artifact@v3.1.0
if: failure() if: failure()
with: with:
status: ${{ job.status }} name: cypress-videos
steps: ${{ toJson(steps) }} path: tests/cypress/videos
channel: '#tiptap-notifications' retention-days: 7
- name: Send Slack notifications
uses: act10ns/slack@v1
if: failure()
with:
status: ${{ job.status }}
steps: ${{ toJson(steps) }}
channel: '#tiptap-notifications'
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -135,36 +141,35 @@ jobs:
node-version: [16] node-version: [16]
steps: steps:
- uses: actions/checkout@v3.0.2
- uses: actions/checkout@v3.0.2 - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.5.1
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }} - name: Load cached dependencies
uses: actions/setup-node@v3.5.1 uses: actions/cache@v3.0.11
with: id: cache
node-version: ${{ matrix.node-version }} with:
path: |
**/node_modules
/home/runner/.cache/Cypress
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Load cached dependencies - name: Install dependencies
uses: actions/cache@v3.0.11 id: install-dependencies
id: cache if: steps.cache.outputs.cache-hit != 'true'
with: run: npm install
path: |
**/node_modules
/home/runner/.cache/Cypress
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies - name: Try to build the packages
id: install-dependencies id: build-packages
if: steps.cache.outputs.cache-hit != 'true' run: npm run build:ci
run: npm install
- name: Try to build the packages - name: Send Slack notifications
id: build-packages uses: act10ns/slack@v1
run: npm run build:ci if: failure()
with:
- name: Send Slack notifications status: ${{ job.status }}
uses: act10ns/slack@v1 steps: ${{ toJson(steps) }}
if: failure() channel: '#tiptap-notifications'
with:
status: ${{ job.status }}
steps: ${{ toJson(steps) }}
channel: '#tiptap-notifications'

View File

@ -10,7 +10,7 @@ prosemirror-keymap
prosemirror-model prosemirror-model
prosemirror-schema-list prosemirror-schema-list
prosemirror-state prosemirror-state
@tiptap/prosemirror-tables prosemirror-tables
prosemirror-transform prosemirror-transform
prosemirror-view prosemirror-view
react react

View File

@ -33,7 +33,7 @@
"sass": "^1.49.7", "sass": "^1.49.7",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"tailwindcss": "^2.2.19", "tailwindcss": "^2.2.19",
"typescript": "^4.5.5", "typescript": "4.7.4",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"vite": "^2.9.13", "vite": "^2.9.13",
"vite-plugin-checker": "^0.3.4", "vite-plugin-checker": "^0.3.4",

View File

@ -34,7 +34,7 @@
"sass": "^1.49.7", "sass": "^1.49.7",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"tailwindcss": "^2.2.19", "tailwindcss": "^2.2.19",
"typescript": "^4.5.5", "typescript": "4.7.4",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"vite": "^2.9.13", "vite": "^2.9.13",
"vite-plugin-checker": "^0.3.4", "vite-plugin-checker": "^0.3.4",

View File

@ -1,5 +1,5 @@
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { Plugin } from 'prosemirror-state' import { Plugin } from '@tiptap/pm/state'
import findColors from './findColors' import findColors from './findColors'

View File

@ -1,5 +1,5 @@
import { Node } from 'prosemirror-model' import { Node } from '@tiptap/pm/model'
import { Decoration, DecorationSet } from 'prosemirror-view' import { Decoration, DecorationSet } from '@tiptap/pm/view'
export default function (doc: Node): DecorationSet { export default function (doc: Node): DecorationSet {
const hexColor = /(#[0-9a-f]{3,6})\b/gi const hexColor = /(#[0-9a-f]{3,6})\b/gi

View File

@ -1,5 +1,5 @@
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { Plugin } from 'prosemirror-state' import { Plugin } from '@tiptap/pm/state'
import findColors from './findColors' import findColors from './findColors'
@ -14,9 +14,7 @@ export const ColorHighlighter = Extension.create({
return findColors(doc) return findColors(doc)
}, },
apply(transaction, oldState) { apply(transaction, oldState) {
return transaction.docChanged return transaction.docChanged ? findColors(transaction.doc) : oldState
? findColors(transaction.doc)
: oldState
}, },
}, },
props: { props: {

View File

@ -1,8 +1,8 @@
import { Node } from 'prosemirror-model' import { Node } from '@tiptap/pm/model'
import { Decoration, DecorationSet } from 'prosemirror-view' import { Decoration, DecorationSet } from '@tiptap/pm/view'
export default function (doc: Node): DecorationSet { export default function (doc: Node): DecorationSet {
const hexColor = /(#[0-9a-f]{3,6})\b/ig const hexColor = /(#[0-9a-f]{3,6})\b/gi
const decorations: Decoration[] = [] const decorations: Decoration[] = []
doc.descendants((node, position) => { doc.descendants((node, position) => {
@ -10,20 +10,18 @@ export default function (doc: Node): DecorationSet {
return return
} }
Array Array.from(node.text.matchAll(hexColor)).forEach(match => {
.from(node.text.matchAll(hexColor)) const color = match[0]
.forEach(match => { const index = match.index || 0
const color = match[0] const from = position + index
const index = match.index || 0 const to = from + color.length
const from = position + index const decoration = Decoration.inline(from, to, {
const to = from + color.length class: 'color',
const decoration = Decoration.inline(from, to, { style: `--color: ${color}`,
class: 'color',
style: `--color: ${color}`,
})
decorations.push(decoration)
}) })
decorations.push(decoration)
})
}) })
return DecorationSet.create(doc, decorations) return DecorationSet.create(doc, decorations)

View File

@ -1,4 +1,4 @@
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
import * as Y from 'yjs' import * as Y from 'yjs'
import { AnnotationState } from './AnnotationState' import { AnnotationState } from './AnnotationState'
@ -8,10 +8,10 @@ export const AnnotationPluginKey = new PluginKey('annotation')
export interface AnnotationPluginOptions { export interface AnnotationPluginOptions {
HTMLAttributes: { HTMLAttributes: {
[key: string]: any [key: string]: any
}, }
onUpdate: (items: [any?]) => {}, onUpdate: (items: [any?]) => {}
map: Y.Map<any>, map: Y.Map<any>
instance: string, instance: string
} }
export const AnnotationPlugin = (options: AnnotationPluginOptions) => new Plugin({ export const AnnotationPlugin = (options: AnnotationPluginOptions) => new Plugin({
@ -39,9 +39,7 @@ export const AnnotationPlugin = (options: AnnotationPluginOptions) => new Plugin
return decorations return decorations
} }
const annotations = this const annotations = this.getState(state).annotationsAt(selection.from)
.getState(state)
.annotationsAt(selection.from)
options.onUpdate(annotations) options.onUpdate(annotations)

View File

@ -1,18 +1,26 @@
import { EditorState, Transaction } from 'prosemirror-state' import { EditorState, Transaction } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from 'prosemirror-view' import { Decoration, DecorationSet } from '@tiptap/pm/view'
import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, ySyncPluginKey } from 'y-prosemirror' import {
absolutePositionToRelativePosition,
relativePositionToAbsolutePosition,
ySyncPluginKey,
} from 'y-prosemirror'
import * as Y from 'yjs' import * as Y from 'yjs'
import { AnnotationItem } from './AnnotationItem' import { AnnotationItem } from './AnnotationItem'
import { AnnotationPluginKey } from './AnnotationPlugin' import { AnnotationPluginKey } from './AnnotationPlugin'
import { AddAnnotationAction, DeleteAnnotationAction, UpdateAnnotationAction } from './collaboration-annotation' import {
AddAnnotationAction,
DeleteAnnotationAction,
UpdateAnnotationAction,
} from './collaboration-annotation'
export interface AnnotationStateOptions { export interface AnnotationStateOptions {
HTMLAttributes: { HTMLAttributes: {
[key: string]: any [key: string]: any
}, }
map: Y.Map<any>, map: Y.Map<any>
instance: string, instance: string
} }
export class AnnotationState { export class AnnotationState {
@ -93,14 +101,27 @@ export class AnnotationState {
} }
// eslint-disable-next-line // eslint-disable-next-line
console.log(`[${this.options.instance}] Decoration.inline()`, from, to, HTMLAttributes, { id, data: annotation.data }) console.log(`[${this.options.instance}] Decoration.inline()`, from, to, HTMLAttributes, {
id,
data: annotation.data,
})
if (from === to) { if (from === to) {
console.warn(`[${this.options.instance}] corrupt decoration `, annotation.from, from, annotation.to, to) console.warn(
`[${this.options.instance}] corrupt decoration `,
annotation.from,
from,
annotation.to,
to,
)
} }
decorations.push( decorations.push(
Decoration.inline(from, to, HTMLAttributes, { id, data: annotation.data, inclusiveEnd: true }), Decoration.inline(from, to, HTMLAttributes, {
id,
data: annotation.data,
inclusiveEnd: true,
}),
) )
}) })
@ -109,7 +130,10 @@ export class AnnotationState {
apply(transaction: Transaction, state: EditorState) { apply(transaction: Transaction, state: EditorState) {
// Add/Remove annotations // Add/Remove annotations
const action = transaction.getMeta(AnnotationPluginKey) as AddAnnotationAction | UpdateAnnotationAction | DeleteAnnotationAction const action = transaction.getMeta(AnnotationPluginKey) as
| AddAnnotationAction
| UpdateAnnotationAction
| DeleteAnnotationAction
if (action && action.type) { if (action && action.type) {
// eslint-disable-next-line // eslint-disable-next-line

View File

@ -1,5 +1,5 @@
import { mergeAttributes, Node } from '@tiptap/core' import { mergeAttributes, Node } from '@tiptap/core'
import { Plugin } from 'prosemirror-state' import { Plugin } from '@tiptap/pm/state'
export const Figure = Node.create({ export const Figure = Node.create({
name: 'figure', name: 'figure',

View File

@ -1,6 +1,6 @@
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { NodeSelection, Plugin } from 'prosemirror-state' import { NodeSelection, Plugin } from '@tiptap/pm/state'
import { __serializeForClipboard as serializeForClipboard } from 'prosemirror-view' import { __serializeForClipboard as serializeForClipboard } from '@tiptap/pm/view'
function removeNode(node) { function removeNode(node) {
node.parentNode.removeChild(node) node.parentNode.removeChild(node)
@ -25,7 +25,8 @@ export default Extension.create({
node = node.node node = node.node
while (node && node.parentNode) { while (node && node.parentNode) {
if (node.parentNode?.classList?.contains('ProseMirror')) { // todo if (node.parentNode?.classList?.contains('ProseMirror')) {
// todo
break break
} }
@ -131,7 +132,8 @@ export default Extension.create({
if (node) { if (node) {
node = node.node node = node.node
while (node && node.parentNode) { while (node && node.parentNode) {
if (node.parentNode?.classList?.contains('ProseMirror')) { // todo if (node.parentNode?.classList?.contains('ProseMirror')) {
// todo
break break
} }
node = node.parentNode node = node.parentNode
@ -145,7 +147,7 @@ export default Extension.create({
const rect = absoluteRect(node) const rect = absoluteRect(node)
const win = node.ownerDocument.defaultView const win = node.ownerDocument.defaultView
rect.top += win.pageYOffset + ((lineHeight - 24) / 2) + top rect.top += win.pageYOffset + (lineHeight - 24) / 2 + top
rect.left += win.pageXOffset rect.left += win.pageXOffset
rect.width = `${WIDTH}px` rect.width = `${WIDTH}px`

View File

@ -1,7 +1,7 @@
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { Node as ProsemirrorNode } from 'prosemirror-model' import { Node as ProsemirrorNode } from '@tiptap/pm/model'
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state' import { Plugin, PluginKey, TextSelection } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from 'prosemirror-view' import { Decoration, DecorationSet } from '@tiptap/pm/view'
import LinterPlugin, { Result as Issue } from './LinterPlugin' import LinterPlugin, { Result as Issue } from './LinterPlugin'
@ -22,9 +22,11 @@ function renderIcon(issue: Issue) {
function runAllLinterPlugins(doc: ProsemirrorNode, plugins: Array<typeof LinterPlugin>) { function runAllLinterPlugins(doc: ProsemirrorNode, plugins: Array<typeof LinterPlugin>) {
const decorations: [any?] = [] const decorations: [any?] = []
const results = plugins.map(RegisteredLinterPlugin => { const results = plugins
return new RegisteredLinterPlugin(doc).scan().getResults() .map(RegisteredLinterPlugin => {
}).flat() return new RegisteredLinterPlugin(doc).scan().getResults()
})
.flat()
results.forEach(issue => { results.forEach(issue => {
decorations.push( decorations.push(
@ -39,7 +41,7 @@ function runAllLinterPlugins(doc: ProsemirrorNode, plugins: Array<typeof LinterP
} }
export interface LinterOptions { export interface LinterOptions {
plugins: Array<typeof LinterPlugin>, plugins: Array<typeof LinterPlugin>
} }
export const Linter = Extension.create<LinterOptions>({ export const Linter = Extension.create<LinterOptions>({
@ -62,9 +64,7 @@ export const Linter = Extension.create<LinterOptions>({
return runAllLinterPlugins(doc, plugins) return runAllLinterPlugins(doc, plugins)
}, },
apply(transaction, oldState) { apply(transaction, oldState) {
return transaction.docChanged return transaction.docChanged ? runAllLinterPlugins(transaction.doc, plugins) : oldState
? runAllLinterPlugins(transaction.doc, plugins)
: oldState
}, },
}, },
props: { props: {
@ -72,7 +72,7 @@ export const Linter = Extension.create<LinterOptions>({
return this.getState(state) return this.getState(state)
}, },
handleClick(view, _, event) { handleClick(view, _, event) {
const target = (event.target as IconDivElement) const target = event.target as IconDivElement
if (/lint-icon/.test(target.className) && target.issue) { if (/lint-icon/.test(target.className) && target.issue) {
const { from, to } = target.issue const { from, to } = target.issue
@ -89,7 +89,7 @@ export const Linter = Extension.create<LinterOptions>({
return false return false
}, },
handleDoubleClick(view, _, event) { handleDoubleClick(view, _, event) {
const target = (event.target as IconDivElement) const target = event.target as IconDivElement
if (/lint-icon/.test((event.target as HTMLElement).className) && target.issue) { if (/lint-icon/.test((event.target as HTMLElement).className) && target.issue) {
const prob = target.issue const prob = target.issue

View File

@ -1,9 +1,9 @@
import { Node as ProsemirrorNode } from 'prosemirror-model' import { Node as ProsemirrorNode } from '@tiptap/pm/model'
export interface Result { export interface Result {
message: string, message: string
from: number, from: number
to: number, to: number
fix?: Function fix?: Function
} }

View File

@ -1,4 +1,4 @@
import { EditorView } from 'prosemirror-view' import { EditorView } from '@tiptap/pm/view'
import LinterPlugin, { Result as Issue } from '../LinterPlugin' import LinterPlugin, { Result as Issue } from '../LinterPlugin'

View File

@ -1,4 +1,4 @@
import { EditorView } from 'prosemirror-view' import { EditorView } from '@tiptap/pm/view'
import LinterPlugin, { Result as Issue } from '../LinterPlugin' import LinterPlugin, { Result as Issue } from '../LinterPlugin'
@ -7,13 +7,7 @@ export class Punctuation extends LinterPlugin {
fix(replacement: any) { fix(replacement: any) {
return function ({ state, dispatch }: EditorView, issue: Issue) { return function ({ state, dispatch }: EditorView, issue: Issue) {
dispatch( dispatch(state.tr.replaceWith(issue.from, issue.to, state.schema.text(replacement)))
state.tr.replaceWith(
issue.from,
issue.to,
state.schema.text(replacement),
),
)
} }
} }

View File

@ -1,5 +1,5 @@
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
// @ts-ignore // @ts-ignore
function nodeEqualsType({ types, node }) { function nodeEqualsType({ types, node }) {
@ -13,8 +13,8 @@ function nodeEqualsType({ types, node }) {
*/ */
export interface TrailingNodeOptions { export interface TrailingNodeOptions {
node: string, node: string
notAfter: string[], notAfter: string[]
} }
export const TrailingNode = Extension.create<TrailingNodeOptions>({ export const TrailingNode = Extension.create<TrailingNodeOptions>({
@ -23,9 +23,7 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
addOptions() { addOptions() {
return { return {
node: 'paragraph', node: 'paragraph',
notAfter: [ notAfter: ['paragraph'],
'paragraph',
],
} }
}, },

View File

@ -11,8 +11,30 @@ import {
} from 'path' } from 'path'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
// import checker from 'vite-plugin-checker' // import checker from 'vite-plugin-checker'
const getPackageDependencies = () => {
const paths: Array<{ find: string, replacement: any }> = []
fg.sync('../packages/*', { onlyDirectories: true })
.map(name => name.replace('../packages/', ''))
.forEach(name => {
if (name === 'pm') {
fg.sync(`../packages/${name}/*`, { onlyDirectories: true })
.forEach(subName => {
const subPkgName = subName.replace(`../packages/${name}/`, '')
paths.push({ find: `@tiptap/${name}/${subPkgName}`, replacement: resolve(`../packages/${name}/${subPkgName}/index.ts`) })
})
} else {
paths.push({ find: `@tiptap/${name}`, replacement: resolve(`../packages/${name}/src/index.ts`) })
}
})
return paths
}
const includeDependencies = fs.readFileSync('./includeDependencies.txt') const includeDependencies = fs.readFileSync('./includeDependencies.txt')
.toString() .toString()
.replace(/\r\n/g, '\n') .replace(/\r\n/g, '\n')
@ -271,12 +293,6 @@ export default defineConfig({
], ],
resolve: { resolve: {
alias: [ alias: getPackageDependencies(),
...fg.sync('../packages/*', { onlyDirectories: true })
.map(name => name.replace('../packages/', ''))
.map(name => {
return { find: `@tiptap/${name}`, replacement: resolve(`../packages/${name}/src/index.ts`) }
}),
],
}, },
}) })

View File

@ -120,7 +120,7 @@ addCommands() {
If youre just wrapping a plain ProseMirror command, youll need to pass `dispatch` anyway. Then theres also no need to check it: If youre just wrapping a plain ProseMirror command, youll need to pass `dispatch` anyway. Then theres also no need to check it:
```js ```js
import { exitCode } from 'prosemirror-commands' import { exitCode } from '@tiptap/pm/commands'
export default () => ({ state, dispatch }) => { export default () => ({ state, dispatch }) => {
return exitCode(state, dispatch) return exitCode(state, dispatch)

View File

@ -112,7 +112,7 @@ Alternatively you can pass a ProseMirror `PluginKey`.
```js ```js
import { Editor } from '@tiptap/core' import { Editor } from '@tiptap/core'
import BubbleMenu from '@tiptap/extension-bubble-menu' import BubbleMenu from '@tiptap/extension-bubble-menu'
import { PluginKey } from 'prosemirror-state' import { PluginKey } from '@tiptap/pm/state'
new Editor({ new Editor({
extensions: [ extensions: [

View File

@ -20,10 +20,6 @@ We kindly ask you to [sponsor our work](/sponsor) when using this extension in p
npm install @tiptap/extension-collaboration-cursor npm install @tiptap/extension-collaboration-cursor
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-collaboration-cursor) which packages are needed and how to install them.
:::
This extension requires the [`Collaboration`](/api/extensions/collaboration) extension. This extension requires the [`Collaboration`](/api/extensions/collaboration) extension.
## Settings ## Settings

View File

@ -20,10 +20,6 @@ We kindly ask you to [sponsor our work](/sponsor) when using this extension in p
npm install @tiptap/extension-collaboration yjs y-websocket npm install @tiptap/extension-collaboration yjs y-websocket
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-collaboration) which packages are needed and how to install them.
:::
## Settings ## Settings
### document ### document

View File

@ -4,6 +4,7 @@ icon: drag-drop-line
--- ---
# Dropcursor # Dropcursor
[![Version](https://img.shields.io/npm/v/@tiptap/extension-dropcursor.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-dropcursor) [![Version](https://img.shields.io/npm/v/@tiptap/extension-dropcursor.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-dropcursor)
[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-dropcursor.svg)](https://npmcharts.com/compare/@tiptap/extension-dropcursor?minimal=true) [![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-dropcursor.svg)](https://npmcharts.com/compare/@tiptap/extension-dropcursor?minimal=true)
@ -12,28 +13,27 @@ This extension loads the [ProseMirror Dropcursor plugin](https://github.com/Pros
Note that Tiptap is headless, but the dropcursor needs CSS for its appearance. There are settings for the color and width, and youre free to add a custom CSS class. Note that Tiptap is headless, but the dropcursor needs CSS for its appearance. There are settings for the color and width, and youre free to add a custom CSS class.
## Installation ## Installation
```bash ```bash
npm install @tiptap/extension-dropcursor npm install @tiptap/extension-dropcursor
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-dropcursor) which packages are needed and how to install them.
:::
## Settings ## Settings
### color ### color
Color of the dropcursor. Color of the dropcursor.
Default: `'currentColor'` Default: `'currentColor'`
```js ```js
Dropcursor.configure({ Dropcursor.configure({
color: '#ff0000' color: '#ff0000',
}) })
``` ```
### width ### width
Width of the dropcursor. Width of the dropcursor.
Default: `1` Default: `1`
@ -45,6 +45,7 @@ Dropcursor.configure({
``` ```
### class ### class
One or multiple CSS classes that should be applied to the dropcursor. One or multiple CSS classes that should be applied to the dropcursor.
```js ```js
@ -54,7 +55,9 @@ Dropcursor.configure({
``` ```
## Source code ## Source code
[packages/extension-dropcursor/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-dropcursor/) [packages/extension-dropcursor/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-dropcursor/)
## Usage ## Usage
https://embed.tiptap.dev/preview/Extensions/Dropcursor https://embed.tiptap.dev/preview/Extensions/Dropcursor

View File

@ -100,7 +100,7 @@ Alternatively you can pass a ProseMirror `PluginKey`.
```js ```js
import { Editor } from '@tiptap/core' import { Editor } from '@tiptap/core'
import FloatingMenu from '@tiptap/extension-floating-menu' import FloatingMenu from '@tiptap/extension-floating-menu'
import { PluginKey } from 'prosemirror-state' import { PluginKey } from '@tiptap/pm/state'
new Editor({ new Editor({
extensions: [ extensions: [

View File

@ -4,6 +4,7 @@ icon: space
--- ---
# Gapcursor # Gapcursor
[![Version](https://img.shields.io/npm/v/@tiptap/extension-gapcursor.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-gapcursor) [![Version](https://img.shields.io/npm/v/@tiptap/extension-gapcursor.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-gapcursor)
[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-gapcursor.svg)](https://npmcharts.com/compare/@tiptap/extension-gapcursor?minimal=true) [![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-gapcursor.svg)](https://npmcharts.com/compare/@tiptap/extension-gapcursor?minimal=true)
@ -12,16 +13,15 @@ This extension loads the [ProseMirror Gapcursor plugin](https://github.com/Prose
Note that Tiptap is headless, but the gapcursor needs CSS for its appearance. The [default CSS](https://github.com/ueberdosis/tiptap/tree/main/packages/core/src/style.ts) is loaded through the Editor class. Note that Tiptap is headless, but the gapcursor needs CSS for its appearance. The [default CSS](https://github.com/ueberdosis/tiptap/tree/main/packages/core/src/style.ts) is loaded through the Editor class.
## Installation ## Installation
```bash ```bash
npm install @tiptap/extension-gapcursor npm install @tiptap/extension-gapcursor
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-gapcursor) which packages are needed and how to install them.
:::
## Source code ## Source code
[packages/extension-gapcursor/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-gapcursor/) [packages/extension-gapcursor/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-gapcursor/)
## Usage ## Usage
https://embed.tiptap.dev/preview/Extensions/Gapcursor https://embed.tiptap.dev/preview/Extensions/Gapcursor

View File

@ -4,23 +4,22 @@ icon: history-line
--- ---
# History # History
[![Version](https://img.shields.io/npm/v/@tiptap/extension-history.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-history) [![Version](https://img.shields.io/npm/v/@tiptap/extension-history.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-history)
[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-history.svg)](https://npmcharts.com/compare/@tiptap/extension-history?minimal=true) [![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-history.svg)](https://npmcharts.com/compare/@tiptap/extension-history?minimal=true)
This extension provides history support. All changes to the document will be tracked and can be removed with `undo`. Undone changes can be applied with `redo` again. This extension provides history support. All changes to the document will be tracked and can be removed with `undo`. Undone changes can be applied with `redo` again.
## Installation ## Installation
```bash ```bash
npm install @tiptap/extension-history npm install @tiptap/extension-history
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-history) which packages are needed and how to install them.
:::
## Settings ## Settings
### depth ### depth
The amount of history events that are collected before the oldest events are discarded. Defaults to 100. The amount of history events that are collected before the oldest events are discarded. Defaults to 100.
Default: `100` Default: `100`
@ -32,6 +31,7 @@ History.configure({
``` ```
### newGroupDelay ### newGroupDelay
The delay between changes after which a new group should be started (in milliseconds). When changes arent adjacent, a new group is always started. The delay between changes after which a new group should be started (in milliseconds). When changes arent adjacent, a new group is always started.
Default: `500` Default: `500`
@ -45,12 +45,15 @@ History.configure({
## Commands ## Commands
### undo() ### undo()
Undo the last change. Undo the last change.
```js ```js
editor.commands.undo() editor.commands.undo()
``` ```
### redo() ### redo()
Redo the last change. Redo the last change.
```js ```js
@ -58,13 +61,16 @@ editor.commands.redo()
``` ```
## Keyboard shortcuts ## Keyboard shortcuts
| Command | Windows/Linux | macOS | | Command | Windows/Linux | macOS |
| ------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | ------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| undo() | `Control`&nbsp;`Z`<br>`Control`&nbsp;`я` | `Cmd`&nbsp;`Z`<br>`Cmd`&nbsp;`я` | | undo() | `Control`&nbsp;`Z`<br>`Control`&nbsp;`я` | `Cmd`&nbsp;`Z`<br>`Cmd`&nbsp;`я` |
| redo() | `Shift`&nbsp;`Control`&nbsp;`Z`<br>`Control`&nbsp;`Y`<br>`Shift`&nbsp;`Control`&nbsp;`я` | `Shift`&nbsp;`Cmd`&nbsp;`Z`<br>`Cmd`&nbsp;`Y`<br>`Shift`&nbsp;`Cmd`&nbsp;`я` | | redo() | `Shift`&nbsp;`Control`&nbsp;`Z`<br>`Control`&nbsp;`Y`<br>`Shift`&nbsp;`Control`&nbsp;`я` | `Shift`&nbsp;`Cmd`&nbsp;`Z`<br>`Cmd`&nbsp;`Y`<br>`Shift`&nbsp;`Cmd`&nbsp;`я` |
## Source code ## Source code
[packages/extension-history/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-history/) [packages/extension-history/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-history/)
## Usage ## Usage
https://embed.tiptap.dev/preview/Extensions/History https://embed.tiptap.dev/preview/Extensions/History

View File

@ -14,10 +14,6 @@ The `StarterKit` is a collection of the most popular Tiptap extensions. If you
npm install @tiptap/starter-kit npm install @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit) which packages are needed and how to install them.
:::
## Included extensions ## Included extensions
### Nodes ### Nodes

View File

@ -1,5 +1,5 @@
# Introduction # Introduction
tiptap is a friendly wrapper around [ProseMirror](https://ProseMirror.net). Although Tiptap tries to hide most of the complexity of ProseMirror, its built on top of its APIs and we recommend you to read through the [ProseMirror Guide](https://ProseMirror.net/docs/guide/) for advanced usage. Tiptap is a friendly wrapper around [ProseMirror](https://ProseMirror.net). Although Tiptap tries to hide most of the complexity of ProseMirror, its built on top of its APIs and we recommend you to read through the [ProseMirror Guide](https://ProseMirror.net/docs/guide/) for advanced usage.
### Structure ### Structure
ProseMirror works with a strict [Schema](/api/schema), which defines the allowed structure of a document. A document is a tree of headings, paragraphs and others elements, so called nodes. Marks can be attached to a node, e. g. to emphasize part of it. [Commands](/api/commands) change that document programmatically. ProseMirror works with a strict [Schema](/api/schema), which defines the allowed structure of a document. A document is a tree of headings, paragraphs and others elements, so called nodes. Marks can be attached to a node, e. g. to emphasize part of it. [Commands](/api/commands) change that document programmatically.

View File

@ -28,10 +28,6 @@ First, install the dependencies:
npm install @tiptap/extension-collaboration yjs y-webrtc npm install @tiptap/extension-collaboration yjs y-webrtc
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-collaboration) which packages are needed and how to install them.
:::
Now, create a new Y document, and register it with Tiptap: Now, create a new Y document, and register it with Tiptap:
```js ```js
@ -78,10 +74,6 @@ For the client, the example is nearly the same, only the provider is different.
npm install @tiptap/extension-collaboration @hocuspocus/provider npm install @tiptap/extension-collaboration @hocuspocus/provider
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [see here](https://tiptap.dev/installation/peer-dependencies#tiptapextension-collaboration) which packages are needed and how to install them.
:::
And then register the WebSocket provider with Tiptap: And then register the WebSocket provider with Tiptap:
```js ```js
@ -293,7 +285,7 @@ server.listen()
## Pitfalls ## Pitfalls
### Schema updates ### Schema updates
tiptap is very strict with the [schema](/api/schema), that means, if you add something thats not allowed according to the configured schema itll be thrown away. That can lead to a strange behaviour when multiple clients with different schemas share changes to a document. Tiptap is very strict with the [schema](/api/schema), that means, if you add something thats not allowed according to the configured schema itll be thrown away. That can lead to a strange behaviour when multiple clients with different schemas share changes to a document.
Lets say you added an editor to your app and the first people use it already. They have all a loaded instance of Tiptap with all default extensions, and therefor a schema that only allows those. But you want to add task lists in the next update, so you add the extension and deploy again. Lets say you added an editor to your app and the first people use it already. They have all a loaded instance of Tiptap with all default extensions, and therefor a schema that only allows those. But you want to add task lists in the next update, so you add the extension and deploy again.

View File

@ -114,7 +114,7 @@ const awesomeness = editor.storage.customExtension.awesomeness
``` ```
### Schema ### Schema
tiptap works with a strict schema, which configures how the content can be structured, nested, how it behaves and many more things. You [can change all aspects of the schema](/api/schema) for existing extensions. Lets walk through a few common use cases. Tiptap works with a strict schema, which configures how the content can be structured, nested, how it behaves and many more things. You [can change all aspects of the schema](/api/schema) for existing extensions. Lets walk through a few common use cases.
The default `Blockquote` extension can wrap other nodes, like headings. If you want to allow nothing but paragraphs in your blockquotes, set the `content` attribute accordingly: The default `Blockquote` extension can wrap other nodes, like headings. If you want to allow nothing but paragraphs in your blockquotes, set the `content` attribute accordingly:
@ -531,7 +531,7 @@ After all, Tiptap is built on ProseMirror and ProseMirror has a pretty powerful
You can wrap existing ProseMirror plugins in Tiptap extensions like shown in the example below. You can wrap existing ProseMirror plugins in Tiptap extensions like shown in the example below.
```js ```js
import { history } from 'prosemirror-history' import { history } from '@tiptap/pm/history'
const History = Extension.create({ const History = Extension.create({
addProseMirrorPlugins() { addProseMirrorPlugins() {
@ -550,7 +550,7 @@ Or you can add them to a Tiptap extension like shown in the below example.
```js ```js
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
export const EventHandler = Extension.create({ export const EventHandler = Extension.create({
name: 'eventHandler', name: 'eventHandler',

39
docs/guide/prosemirror.md Normal file
View File

@ -0,0 +1,39 @@
---
tableOfContents: true
---
# Accessing ProseMirror internals
The ProseMirror internals are packaged in the `@tiptap/pm` package that you need to install with `npm install @tiptap/pm`. If you already have this done you can skip the following step.
```bash
npm i @tiptap/pm
```
After that you can access all internal ProseMirror packages like this:
```js
// this example loads the EditorState class from the ProseMirror state package
import { EditorState } from '@tiptap/pm/state'
```
The following packages are available:
- `@tiptap/pm/changeset`
- `@tiptap/pm/collab`
- `@tiptap/pm/commands`
- `@tiptap/pm/dropcursor`
- `@tiptap/pm/gapcursor`
- `@tiptap/pm/history`
- `@tiptap/pm/inputrules`
- `@tiptap/pm/keymap`
- `@tiptap/pm/markdown`
- `@tiptap/pm/menu`
- `@tiptap/pm/model`
- `@tiptap/pm/schema-basic`
- `@tiptap/pm/schema-list`
- `@tiptap/pm/state`
- `@tiptap/pm/tables`
- `@tiptap/pm/trailing-node`
- `@tiptap/pm/transform`
- `@tiptap/pm/view`

View File

@ -5,24 +5,33 @@ tableOfContents: true
# Installation # Installation
## Introduction ## Introduction
Tiptap is framework-agnostic and even works with Vanilla JavaScript (if thats your thing). The following integration guides help you integrating Tiptap in your JavaScript project. Tiptap is framework-agnostic and even works with Vanilla JavaScript (if thats your thing). The following integration guides help you integrating Tiptap in your JavaScript project.
## Integration guides ## Base Setup
<!-- * [CDN](/installation/cdn) -->
* [Vanilla JavaScript](/installation/vanilla-javascript)
* [React](/installation/react)
* [Next.js](/installation/nextjs)
* [Vue 3](/installation/vue3)
* [Vue 2](/installation/vue2)
* [Nuxt.js](/installation/nuxt)
* [Svelte](/installation/svelte)
* [Alpine.js](/installation/alpine)
* [PHP](/installation/php)
:::warning Are you using Yarn, pNPM, npm 6 or less? To get started you will need to install `@tiptap/core`, `@tiptap/pm` and `@tiptap/starter-kit` in your project like this:
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please [read this](https://tiptap.dev/installation/peer-dependencies) to understand what is needed in that case.
::: ```bash
npm install @tiptap/core @tiptap/pm @tiptap/starter-kit
```
After that, you can start using Tiptap in your project. To integrate it into your framework, please follow the guides below.
## Integration guides
- [Vanilla JavaScript](/installation/vanilla-javascript)
- [React](/installation/react)
- [Next.js](/installation/nextjs)
- [Vue 3](/installation/vue3)
- [Vue 2](/installation/vue2)
- [Nuxt.js](/installation/nuxt)
- [Svelte](/installation/svelte)
- [Alpine.js](/installation/alpine)
- [PHP](/installation/php)
<!-- [CDN](/installation/cdn)-->
### Community efforts ### Community efforts
* [Angular](https://github.com/sibiraj-s/ngx-tiptap)
* [SolidJS](https://github.com/LXSMNSYC/solid-tiptap) - [Angular](https://github.com/sibiraj-s/ngx-tiptap)
- [SolidJS](https://github.com/LXSMNSYC/solid-tiptap)

View File

@ -28,16 +28,12 @@ npm run dev
## 2. Install the dependencies ## 2. Install the dependencies
Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For the following example youll need `alpinejs`, the `@tiptap/core` package and the `@tiptap/starter-kit` which has the most common extensions to get started quickly. Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For the following example youll need `alpinejs`, the `@tiptap/core` package, the `@tiptap/pm` package and the `@tiptap/starter-kit` which has the most common extensions to get started quickly.
```bash ```bash
npm install alpinejs @tiptap/core @tiptap/starter-kit npm install alpinejs @tiptap/core @tiptap/pm @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1, you can now start your project with `npm run dev`, and open [http://localhost:5173](http://localhost:5173) in your favorite browser. This might be different, if youre working with an existing project. If you followed step 1, you can now start your project with `npm run dev`, and open [http://localhost:5173](http://localhost:5173) in your favorite browser. This might be different, if youre working with an existing project.
## 3. Initialize the editor ## 3. Initialize the editor
@ -74,7 +70,7 @@ document.addEventListener('alpine:init', () => {
onSelectionUpdate({ editor }) { onSelectionUpdate({ editor }) {
_this.updatedAt = Date.now() _this.updatedAt = Date.now()
} }
}); })
}, },
isLoaded() { isLoaded() {
return editor return editor
@ -91,9 +87,9 @@ document.addEventListener('alpine:init', () => {
toggleItalic() { toggleItalic() {
editor.chain().toggleItalic().focus().run() editor.chain().toggleItalic().focus().run()
}, },
}; }
}); })
}); })
window.Alpine = Alpine window.Alpine = Alpine
Alpine.start() Alpine.start()

View File

@ -25,16 +25,12 @@ cd my-tiptap-project
``` ```
## 2. Install the dependencies ## 2. Install the dependencies
Now that we have a standard boilerplate set up we can get started on getting Tiptap up and running! For this we will need to install two packages: `@tiptap/react` and `@tiptap/starter-kit` which includes all the extensions you need to get started quickly. Now that we have a standard boilerplate set up we can get started on getting Tiptap up and running! For this we will need to install three packages: `@tiptap/react`, `@tiptap/pm` and `@tiptap/starter-kit` which includes all the extensions you need to get started quickly.
```bash ```bash
npm install @tiptap/react @tiptap/starter-kit npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1 and 2, you can now start your project with `npm run dev`, and open [http://localhost:3000/](http://localhost:3000/) in your favorite browser. This might be different, if youre working with an existing project. If you followed step 1 and 2, you can now start your project with `npm run dev`, and open [http://localhost:3000/](http://localhost:3000/) in your favorite browser. This might be different, if youre working with an existing project.
## 3. Create a new component ## 3. Create a new component
@ -57,7 +53,7 @@ const Tiptap = () => {
) )
} }
export default Tiptap; export default Tiptap
``` ```
## 4. Add it to your app ## 4. Add it to your app

View File

@ -26,16 +26,12 @@ cd my-tiptap-project
``` ```
## 2. Install the dependencies ## 2. Install the dependencies
Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For the following example youll need the `@tiptap/vue-2` package, with a few components, and `@tiptap/starter-kit` which has the most common extensions to get started quickly. Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For the following example youll need the `@tiptap/vue-2` package with a few components, the `@tiptap/pm` package, and `@tiptap/starter-kit` which has the most common extensions to get started quickly.
```bash ```bash
npm install @tiptap/vue-2 @tiptap/starter-kit npm install @tiptap/vue-2 @tiptap/pm @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1 and 2, you can now start your project with `npm run serve`, and open [http://localhost:8080/](http://localhost:8080/) in your favorite browser. This might be different, if youre working with an existing project. If you followed step 1 and 2, you can now start your project with `npm run serve`, and open [http://localhost:8080/](http://localhost:8080/) in your favorite browser. This might be different, if youre working with an existing project.
## 3. Create a new component ## 3. Create a new component

View File

@ -1,80 +0,0 @@
---
tableOfContents: true
---
# Peer dependencies
## Introduction
With the release of version 2.0.0-beta.205 we introduced peer dependencies. Most packages require the installation of peer dependencies.
## Why peer dependencies
In the past it has happened that users installed ProseMirror or Yjs packages to develope their own extensions, which had a different version than the ones included in Tiptap. This has caused version clashes.
## How to install
### NPM 7 or higher
If you are using NPM 7 or higher, you can ignore the following notes. NPM installs peer dependencies automatically and no further action is required.
### Yarn, pNPM, npm 6 or less
#### @tiptap/core
| Package manager | Command |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add prosemirror-commands prosemirror-keymap prosemirror-model prosemirror-schema-list prosemirror-state prosemirror-transform prosemirror-view` |
| pNPM | `pnpm install prosemirror-commands prosemirror-keymap prosemirror-model prosemirror-schema-list prosemirror-state prosemirror-transform prosemirror-view` |
| npm 6 or less | `npm install prosemirror-commands prosemirror-keymap prosemirror-model prosemirror-schema-list prosemirror-state prosemirror-transform prosemirror-view` |
#### @tiptap/starter-kit
| Package manager | Command |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add prosemirror-history prosemirror-dropcursor prosemirror-gapcursor` |
| pNPM | `pnpm install prosemirror-history prosemirror-dropcursor prosemirror-gapcursor` |
| npm 6 or less | `npm install prosemirror-history prosemirror-dropcursor prosemirror-gapcursor` |
#### @tiptap/extension-history
| Package manager | Command |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add prosemirror-history` |
| pNPM | `pnpm install prosemirror-history` |
| npm 6 or less | `npm install prosemirror-history` |
#### @tiptap/extension-gapcursor
| Package manager | Command |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add prosemirror-gapcursor` |
| pNPM | `pnpm install prosemirror-gapcursor` |
| npm 6 or less | `npm install prosemirror-gapcursor` |
#### @tiptap/extension-dropcursor
| Package manager | Command |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add prosemirror-dropcursor` |
| pNPM | `pnpm install prosemirror-dropcursor` |
| npm 6 or less | `npm install prosemirror-dropcursor` |
#### @tiptap/extension-collaboration
| Package manager | Command |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add y-prosemirror` |
| pNPM | `pnpm install y-prosemirror` |
| npm 6 or less | `npm install y-prosemirror` |
#### @tiptap/extension-collaboration-cursor
| Package manager | Command |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Yarn | `yarn add y-prosemirror` |
| pNPM | `pnpm install y-prosemirror` |
| npm 6 or less | `npm install y-prosemirror` |

View File

@ -37,13 +37,9 @@ cd my-tiptap-project
Time to install the `@tiptap/react` package and our [`StarterKit`](/api/extensions/starter-kit), which has the most popular extensions to get started quickly. Time to install the `@tiptap/react` package and our [`StarterKit`](/api/extensions/starter-kit), which has the most popular extensions to get started quickly.
```bash ```bash
npm install @tiptap/react @tiptap/starter-kit npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1 and 2, you can now start your project with `npm run start`, and open [http://localhost:3000](http://localhost:3000) in your browser. If you followed step 1 and 2, you can now start your project with `npm run start`, and open [http://localhost:3000](http://localhost:3000) in your browser.
#### 3. Create a new component #### 3. Create a new component

View File

@ -28,16 +28,12 @@ npm run dev
``` ```
## 2. Install the dependencies ## 2. Install the dependencies
Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For the following example youll need the `@tiptap/core` package, with a few components, and `@tiptap/starter-kit` which has the most common extensions to get started quickly. Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For the following example youll need the `@tiptap/core` package, with a few components, `@tiptap/pm` and `@tiptap/starter-kit` which has the most common extensions to get started quickly.
```bash ```bash
npm install @tiptap/core @tiptap/starter-kit npm install @tiptap/core @tiptap/pm @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1 and 2, you can now start your project with `npm run dev`, and open [http://localhost:3000/](http://localhost:3000/) in your favorite browser. This might be different, if youre working with an existing project. If you followed step 1 and 2, you can now start your project with `npm run dev`, and open [http://localhost:3000/](http://localhost:3000/) in your favorite browser. This might be different, if youre working with an existing project.
## 3. Create a new component ## 3. Create a new component

View File

@ -9,18 +9,14 @@ tableOfContents: true
You are using plain JavaScript or a framework that is not listed here? No worries, we provide everything you need. You are using plain JavaScript or a framework that is not listed here? No worries, we provide everything you need.
## 1. Install the dependencies ## 1. Install the dependencies
For the following example you will need `@tiptap/core` (the actual editor) and `@tiptap/starter-kit`. For the following example you will need `@tiptap/core` (the actual editor), `@tiptap/pm` (the ProseMirror library) and `@tiptap/starter-kit`.
The StarterKit doesnt include all, but the most common extensions. The StarterKit doesnt include all, but the most common extensions.
```bash ```bash
npm install @tiptap/core @tiptap/starter-kit npm install @tiptap/core @tiptap/pm @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
## 2. Add some markup ## 2. Add some markup
Add the following HTML where you want the editor to be mounted: Add the following HTML where you want the editor to be mounted:

View File

@ -33,10 +33,6 @@ Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For
npm install @tiptap/vue-2 @tiptap/starter-kit npm install @tiptap/vue-2 @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1 and 2, you can now start your project with `npm run dev`, and open [http://localhost:8080](http://localhost:8080) in your favorite browser. This might be different, if youre working with an existing project. If you followed step 1 and 2, you can now start your project with `npm run dev`, and open [http://localhost:8080](http://localhost:8080) in your favorite browser. This might be different, if youre working with an existing project.
## 3. Create a new component ## 3. Create a new component

View File

@ -33,10 +33,6 @@ Okay, enough of the boring boilerplate work. Lets finally install Tiptap! For
npm install @tiptap/vue-3 @tiptap/starter-kit npm install @tiptap/vue-3 @tiptap/starter-kit
``` ```
:::warning Are you using Yarn, pNPM, npm 6 or less?
Unfortunately your package manager does not install peer dependencies automatically and you have to install them by your own. Please check the following links to find out what dependencies are needed and how to install them: [@tiptap/core](https://tiptap.dev/installation/peer-dependencies#tiptapcore), [@tiptap/starter-kit](https://tiptap.dev/installation/peer-dependencies#tiptapstarter-kit)
:::
If you followed step 1 and 2, you can now start your project with `npm run serve`, and open [http://localhost:8080](http://localhost:8080) in your favorite browser. This might be different, if youre working with an existing project. If you followed step 1 and 2, you can now start your project with `npm run serve`, and open [http://localhost:8080](http://localhost:8080) in your favorite browser. This might be different, if youre working with an existing project.
## 3. Create a new component ## 3. Create a new component

View File

@ -10,7 +10,7 @@ tableOfContents: true
[![License](https://img.shields.io/npm/l/@tiptap/core.svg)](https://www.npmjs.com/package/@tiptap/core) [![License](https://img.shields.io/npm/l/@tiptap/core.svg)](https://www.npmjs.com/package/@tiptap/core)
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis) [![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*. Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
Create exactly the rich text editor you want out of customizable building blocks. Tiptap comes with sensible defaults, a lot of extensions and a friendly API to customize every aspect. Its backed by a welcoming community, open source, and free. Create exactly the rich text editor you want out of customizable building blocks. Tiptap comes with sensible defaults, a lot of extensions and a friendly API to customize every aspect. Its backed by a welcoming community, open source, and free.

View File

@ -120,6 +120,8 @@
items: items:
- title: Configuration - title: Configuration
link: /guide/configuration link: /guide/configuration
- title: ProseMirror
link: /guide/prosemirror
- title: Menus - title: Menus
link: /guide/menus link: /guide/menus
- title: Styling - title: Styling

863
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,9 +13,10 @@
"lint": "eslint --quiet --no-error-on-unmatched-pattern ./", "lint": "eslint --quiet --no-error-on-unmatched-pattern ./",
"lint:fix": "eslint --fix --quiet --no-error-on-unmatched-pattern ./", "lint:fix": "eslint --fix --quiet --no-error-on-unmatched-pattern ./",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
"test:open": "cypress open --project tests", "test:open": "npm run build:pm && cypress open --project tests",
"test": "cypress run --project tests", "test": "npm run build:pm && cypress run --project tests",
"build": "npm run clean:packages && lerna run build", "build": "npm run clean:packages && npm run clean:packs && lerna run build",
"build:pm": "npm --prefix ./packages/pm run build",
"build:demos": "npm --prefix ./demos run build:demos", "build:demos": "npm --prefix ./demos run build:demos",
"build:ci": "npm run build", "build:ci": "npm run build",
"release:major": "lerna version major --force-publish", "release:major": "lerna version major --force-publish",
@ -26,9 +27,10 @@
"release:patch:pre": "lerna version prepatch --force-publish", "release:patch:pre": "lerna version prepatch --force-publish",
"release:pre": "lerna version prerelease --force-publish", "release:pre": "lerna version prerelease --force-publish",
"publish": "npm run build:packages && lerna exec --since --no-private -- npm publish --access public", "publish": "npm run build:packages && lerna exec --since --no-private -- npm publish --access public",
"pack": "lerna exec -- npm pack", "pack": "npm run clean:packs && lerna exec -- npm pack",
"clean:packages": "rm -rf ./packages/*/dist", "clean:packages": "rm -rf ./packages/*/dist && rm -rf ./packages/pm/*/dist",
"reset": "npm run clean:packages && rm -rf ./**/.cache && rm -rf ./**/node_modules && rm -rf ./package-lock.json && npm install", "clean:packs": "rm -rf ./packages/*/*.tgz",
"reset": "npm run clean:packages && npm run clean:packs && rm -rf ./**/.cache && rm -rf ./**/node_modules && rm -rf ./package-lock.json && npm install",
"prepare": "husky install" "prepare": "husky install"
}, },
"devDependencies": { "devDependencies": {
@ -56,7 +58,7 @@
"lerna": "^5.5.1", "lerna": "^5.5.1",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"ts-loader": "^9.2.6", "ts-loader": "9.3.1",
"tsup": "^6.5.0", "tsup": "^6.5.0",
"typescript": "4.7.4", "typescript": "4.7.4",
"webpack": "^5.68.0" "webpack": "^5.68.0"

View File

@ -31,22 +31,10 @@
"dist" "dist"
], ],
"peerDependencies": { "peerDependencies": {
"prosemirror-commands": "^1.3.1", "@tiptap/pm": "^2.0.0-beta.209"
"prosemirror-keymap": "^1.2.0",
"prosemirror-model": "^1.18.1",
"prosemirror-schema-list": "^1.2.2",
"prosemirror-state": "^1.4.1",
"prosemirror-transform": "^1.7.0",
"prosemirror-view": "^1.28.2"
}, },
"devDependencies": { "devDependencies": {
"prosemirror-commands": "^1.3.1", "@tiptap/pm": "^2.0.0-beta.209"
"prosemirror-keymap": "^1.2.0",
"prosemirror-model": "^1.18.1",
"prosemirror-schema-list": "^1.2.2",
"prosemirror-state": "^1.4.1",
"prosemirror-transform": "^1.7.0",
"prosemirror-view": "^1.28.2"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,27 +1,19 @@
import { EditorState, Transaction } from 'prosemirror-state' import { EditorState, Transaction } from '@tiptap/pm/state'
import { Editor } from './Editor' import { Editor } from './Editor'
import { createChainableState } from './helpers/createChainableState' import { createChainableState } from './helpers/createChainableState'
import { import {
AnyCommands, AnyCommands, CanCommands, ChainedCommands, CommandProps, SingleCommands,
CanCommands,
ChainedCommands,
CommandProps,
SingleCommands,
} from './types' } from './types'
export class CommandManager { export class CommandManager {
editor: Editor editor: Editor
rawCommands: AnyCommands rawCommands: AnyCommands
customState?: EditorState customState?: EditorState
constructor(props: { constructor(props: { editor: Editor; state?: EditorState }) {
editor: Editor,
state?: EditorState,
}) {
this.editor = props.editor this.editor = props.editor
this.rawCommands = this.editor.extensionManager.commands this.rawCommands = this.editor.extensionManager.commands
this.customState = props.state this.customState = props.state
@ -41,9 +33,8 @@ export class CommandManager {
const { tr } = state const { tr } = state
const props = this.buildProps(tr) const props = this.buildProps(tr)
return Object.fromEntries(Object return Object.fromEntries(
.entries(rawCommands) Object.entries(rawCommands).map(([name, command]) => {
.map(([name, command]) => {
const method = (...args: any[]) => { const method = (...args: any[]) => {
const callback = command(...args)(props) const callback = command(...args)(props)
@ -55,7 +46,8 @@ export class CommandManager {
} }
return [name, method] return [name, method]
})) as unknown as SingleCommands }),
) as unknown as SingleCommands
} }
get chain(): () => ChainedCommands { get chain(): () => ChainedCommands {
@ -87,18 +79,20 @@ export class CommandManager {
} }
const chain = { const chain = {
...Object.fromEntries(Object.entries(rawCommands).map(([name, command]) => { ...Object.fromEntries(
const chainedCommand = (...args: never[]) => { Object.entries(rawCommands).map(([name, command]) => {
const props = this.buildProps(tr, shouldDispatch) const chainedCommand = (...args: never[]) => {
const callback = command(...args)(props) const props = this.buildProps(tr, shouldDispatch)
const callback = command(...args)(props)
callbacks.push(callback) callbacks.push(callback)
return chain return chain
} }
return [name, chainedCommand] return [name, chainedCommand]
})), }),
),
run, run,
} as unknown as ChainedCommands } as unknown as ChainedCommands
@ -110,11 +104,11 @@ export class CommandManager {
const dispatch = false const dispatch = false
const tr = startTr || state.tr const tr = startTr || state.tr
const props = this.buildProps(tr, dispatch) const props = this.buildProps(tr, dispatch)
const formattedCommands = Object.fromEntries(Object const formattedCommands = Object.fromEntries(
.entries(rawCommands) Object.entries(rawCommands).map(([name, command]) => {
.map(([name, command]) => {
return [name, (...args: never[]) => command(...args)({ ...props, dispatch: undefined })] return [name, (...args: never[]) => command(...args)({ ...props, dispatch: undefined })]
})) as unknown as SingleCommands }),
) as unknown as SingleCommands
return { return {
...formattedCommands, ...formattedCommands,
@ -138,21 +132,18 @@ export class CommandManager {
state, state,
transaction: tr, transaction: tr,
}), }),
dispatch: shouldDispatch dispatch: shouldDispatch ? () => undefined : undefined,
? () => undefined
: undefined,
chain: () => this.createChain(tr), chain: () => this.createChain(tr),
can: () => this.createCan(tr), can: () => this.createCan(tr),
get commands() { get commands() {
return Object.fromEntries(Object return Object.fromEntries(
.entries(rawCommands) Object.entries(rawCommands).map(([name, command]) => {
.map(([name, command]) => {
return [name, (...args: never[]) => command(...args)(props)] return [name, (...args: never[]) => command(...args)(props)]
})) as unknown as SingleCommands }),
) as unknown as SingleCommands
}, },
} }
return props return props
} }
} }

View File

@ -1,11 +1,8 @@
import { MarkType, NodeType, Schema } from 'prosemirror-model' import { MarkType, NodeType, Schema } from '@tiptap/pm/model'
import { import {
EditorState, EditorState, Plugin, PluginKey, Transaction,
Plugin, } from '@tiptap/pm/state'
PluginKey, import { EditorView } from '@tiptap/pm/view'
Transaction,
} from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { CommandManager } from './CommandManager' import { CommandManager } from './CommandManager'
import { EventEmitter } from './EventEmitter' import { EventEmitter } from './EventEmitter'
@ -39,7 +36,6 @@ export interface HTMLElement {
} }
export class Editor extends EventEmitter<EditorEvents> { export class Editor extends EventEmitter<EditorEvents> {
private commandManager!: CommandManager private commandManager!: CommandManager
public extensionManager!: ExtensionManager public extensionManager!: ExtensionManager
@ -182,9 +178,7 @@ export class Editor extends EventEmitter<EditorEvents> {
// since plugins are applied after creating the view // since plugins are applied after creating the view
// `editable` is always `true` for one tick. // `editable` is always `true` for one tick.
// thats why we also have to check for `options.editable` // thats why we also have to check for `options.editable`
return this.options.editable return this.options.editable && this.view && this.view.editable
&& this.view
&& this.view.editable
} }
/** /**
@ -200,7 +194,10 @@ export class Editor extends EventEmitter<EditorEvents> {
* @param plugin A ProseMirror plugin * @param plugin A ProseMirror plugin
* @param handlePlugins Control how to merge the plugin into the existing plugins. * @param handlePlugins Control how to merge the plugin into the existing plugins.
*/ */
public registerPlugin(plugin: Plugin, handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[]): void { public registerPlugin(
plugin: Plugin,
handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[],
): void {
const plugins = isFunction(handlePlugins) const plugins = isFunction(handlePlugins)
? handlePlugins(plugin, [...this.state.plugins]) ? handlePlugins(plugin, [...this.state.plugins])
: [...this.state.plugins, plugin] : [...this.state.plugins, plugin]
@ -220,10 +217,8 @@ export class Editor extends EventEmitter<EditorEvents> {
return return
} }
const name = typeof nameOrPluginKey === 'string' // @ts-ignore
? `${nameOrPluginKey}$` const name = typeof nameOrPluginKey === 'string' ? `${nameOrPluginKey}$` : nameOrPluginKey.key
// @ts-ignore
: nameOrPluginKey.key
const state = this.state.reconfigure({ const state = this.state.reconfigure({
// @ts-ignore // @ts-ignore
@ -237,9 +232,7 @@ export class Editor extends EventEmitter<EditorEvents> {
* Creates an extension manager. * Creates an extension manager.
*/ */
private createExtensionManager(): void { private createExtensionManager(): void {
const coreExtensions = this.options.enableCoreExtensions const coreExtensions = this.options.enableCoreExtensions ? Object.values(extensions) : []
? Object.values(extensions)
: []
const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => { const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => {
return ['extension', 'node', 'mark'].includes(extension?.type) return ['extension', 'node', 'mark'].includes(extension?.type)
}) })
@ -397,16 +390,12 @@ export class Editor extends EventEmitter<EditorEvents> {
* @param name Name of the node or mark * @param name Name of the node or mark
* @param attributes Attributes of the node or mark * @param attributes Attributes of the node or mark
*/ */
public isActive(name: string, attributes?: {}): boolean; public isActive(name: string, attributes?: {}): boolean
public isActive(attributes: {}): boolean; public isActive(attributes: {}): boolean
public isActive(nameOrAttributes: string, attributesOrUndefined?: {}): boolean { public isActive(nameOrAttributes: string, attributesOrUndefined?: {}): boolean {
const name = typeof nameOrAttributes === 'string' const name = typeof nameOrAttributes === 'string' ? nameOrAttributes : null
? nameOrAttributes
: null
const attributes = typeof nameOrAttributes === 'string' const attributes = typeof nameOrAttributes === 'string' ? attributesOrUndefined : nameOrAttributes
? attributesOrUndefined
: nameOrAttributes
return isActive(this.state, name, attributes) return isActive(this.state, name, attributes)
} }
@ -429,13 +418,10 @@ export class Editor extends EventEmitter<EditorEvents> {
* Get the document as text. * Get the document as text.
*/ */
public getText(options?: { public getText(options?: {
blockSeparator?: string, blockSeparator?: string
textSerializers?: Record<string, TextSerializer>, textSerializers?: Record<string, TextSerializer>
}): string { }): string {
const { const { blockSeparator = '\n\n', textSerializers = {} } = options || {}
blockSeparator = '\n\n',
textSerializers = {},
} = options || {}
return getText(this.state.doc, { return getText(this.state.doc, {
blockSeparator, blockSeparator,
@ -459,7 +445,9 @@ export class Editor extends EventEmitter<EditorEvents> {
* @deprecated * @deprecated
*/ */
public getCharacterCount(): number { public getCharacterCount(): number {
console.warn('[tiptap warn]: "editor.getCharacterCount()" is deprecated. Please use "editor.storage.characterCount.characters()" instead.') console.warn(
'[tiptap warn]: "editor.getCharacterCount()" is deprecated. Please use "editor.storage.characterCount.characters()" instead.',
)
return this.state.doc.content.size - 2 return this.state.doc.content.size - 2
} }
@ -484,5 +472,4 @@ export class Editor extends EventEmitter<EditorEvents> {
// @ts-ignore // @ts-ignore
return !this.view?.docView return !this.view?.docView
} }
} }

View File

@ -1,4 +1,4 @@
import { Plugin, Transaction } from 'prosemirror-state' import { Plugin, Transaction } from '@tiptap/pm/state'
import { ExtensionConfig } from '.' import { ExtensionConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
@ -20,245 +20,265 @@ import { mergeDeep } from './utilities/mergeDeep'
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface ExtensionConfig<Options = any, Storage = any> { interface ExtensionConfig<Options = any, Storage = any> {
[key: string]: any; [key: string]: any
/** /**
* Name * Name
*/ */
name: string, name: string
/** /**
* Priority * Priority
*/ */
priority?: number, priority?: number
/** /**
* Default options * Default options
*/ */
defaultOptions?: Options, defaultOptions?: Options
/** /**
* Default Options * Default Options
*/ */
addOptions?: (this: { addOptions?: (this: {
name: string, name: string
parent: Exclude<ParentConfig<ExtensionConfig<Options, Storage>>['addOptions'], undefined>, parent: Exclude<ParentConfig<ExtensionConfig<Options, Storage>>['addOptions'], undefined>
}) => Options, }) => Options
/** /**
* Default Storage * Default Storage
*/ */
addStorage?: (this: { addStorage?: (this: {
name: string, name: string
options: Options, options: Options
parent: Exclude<ParentConfig<ExtensionConfig<Options, Storage>>['addStorage'], undefined>, parent: Exclude<ParentConfig<ExtensionConfig<Options, Storage>>['addStorage'], undefined>
}) => Storage, }) => Storage
/** /**
* Global attributes * Global attributes
*/ */
addGlobalAttributes?: (this: { addGlobalAttributes?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addGlobalAttributes'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addGlobalAttributes']
}) => GlobalAttributes | {}, }) => GlobalAttributes | {}
/** /**
* Raw * Raw
*/ */
addCommands?: (this: { addCommands?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addCommands'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addCommands']
}) => Partial<RawCommands>, }) => Partial<RawCommands>
/** /**
* Keyboard shortcuts * Keyboard shortcuts
*/ */
addKeyboardShortcuts?: (this: { addKeyboardShortcuts?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addKeyboardShortcuts'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addKeyboardShortcuts']
}) => { }) => {
[key: string]: KeyboardShortcutCommand, [key: string]: KeyboardShortcutCommand
}, }
/** /**
* Input rules * Input rules
*/ */
addInputRules?: (this: { addInputRules?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addInputRules'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addInputRules']
}) => InputRule[], }) => InputRule[]
/** /**
* Paste rules * Paste rules
*/ */
addPasteRules?: (this: { addPasteRules?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addPasteRules'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addPasteRules']
}) => PasteRule[], }) => PasteRule[]
/** /**
* ProseMirror plugins * ProseMirror plugins
*/ */
addProseMirrorPlugins?: (this: { addProseMirrorPlugins?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addProseMirrorPlugins'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addProseMirrorPlugins']
}) => Plugin[], }) => Plugin[]
/** /**
* Extensions * Extensions
*/ */
addExtensions?: (this: { addExtensions?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addExtensions'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addExtensions']
}) => Extensions, }) => Extensions
/** /**
* Extend Node Schema * Extend Node Schema
*/ */
extendNodeSchema?: (( extendNodeSchema?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<ExtensionConfig<Options, Storage>>['extendNodeSchema'], storage: Storage
}, parent: ParentConfig<ExtensionConfig<Options, Storage>>['extendNodeSchema']
extension: Node, },
) => Record<string, any>) | null, extension: Node,
) => Record<string, any>)
| null
/** /**
* Extend Mark Schema * Extend Mark Schema
*/ */
extendMarkSchema?: (( extendMarkSchema?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<ExtensionConfig<Options, Storage>>['extendMarkSchema'], storage: Storage
}, parent: ParentConfig<ExtensionConfig<Options, Storage>>['extendMarkSchema']
extension: Mark, },
) => Record<string, any>) | null, extension: Mark,
) => Record<string, any>)
| null
/** /**
* The editor is not ready yet. * The editor is not ready yet.
*/ */
onBeforeCreate?: ((this: { onBeforeCreate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onBeforeCreate'], editor: Editor
}) => void) | null, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onBeforeCreate']
}) => void)
| null
/** /**
* The editor is ready. * The editor is ready.
*/ */
onCreate?: ((this: { onCreate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onCreate'], editor: Editor
}) => void) | null, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onCreate']
}) => void)
| null
/** /**
* The content has changed. * The content has changed.
*/ */
onUpdate?: ((this: { onUpdate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onUpdate'], editor: Editor
}) => void) | null, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onUpdate']
}) => void)
| null
/** /**
* The selection has changed. * The selection has changed.
*/ */
onSelectionUpdate?: ((this: { onSelectionUpdate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onSelectionUpdate'], editor: Editor
}) => void) | null, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onSelectionUpdate']
}) => void)
| null
/** /**
* The editor state has changed. * The editor state has changed.
*/ */
onTransaction?: (( onTransaction?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onTransaction'], editor: Editor
}, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onTransaction']
props: { },
transaction: Transaction, props: {
}, transaction: Transaction
) => void) | null, },
) => void)
| null
/** /**
* The editor is focused. * The editor is focused.
*/ */
onFocus?: (( onFocus?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onFocus'], editor: Editor
}, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onFocus']
props: { },
event: FocusEvent, props: {
}, event: FocusEvent
) => void) | null, },
) => void)
| null
/** /**
* The editor isnt focused anymore. * The editor isnt focused anymore.
*/ */
onBlur?: (( onBlur?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onBlur'], editor: Editor
}, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onBlur']
props: { },
event: FocusEvent, props: {
}, event: FocusEvent
) => void) | null, },
) => void)
| null
/** /**
* The editor is destroyed. * The editor is destroyed.
*/ */
onDestroy?: ((this: { onDestroy?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
parent: ParentConfig<ExtensionConfig<Options, Storage>>['onDestroy'], editor: Editor
}) => void) | null, parent: ParentConfig<ExtensionConfig<Options, Storage>>['onDestroy']
}) => void)
| null
} }
} }
@ -289,30 +309,28 @@ export class Extension<Options = any, Storage = any> {
this.name = this.config.name this.name = this.config.name
if (config.defaultOptions) { if (config.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`) console.warn(
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`,
)
} }
// TODO: remove `addOptions` fallback // TODO: remove `addOptions` fallback
this.options = this.config.defaultOptions this.options = this.config.defaultOptions
if (this.config.addOptions) { if (this.config.addOptions) {
this.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>( this.options = callOrReturn(
this, getExtensionField<AnyConfig['addOptions']>(this, 'addOptions', {
'addOptions',
{
name: this.name, name: this.name,
}, }),
)) )
} }
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( this.storage = callOrReturn(
this, getExtensionField<AnyConfig['addStorage']>(this, 'addStorage', {
'addStorage',
{
name: this.name, name: this.name,
options: this.options, options: this.options,
}, }),
)) || {} ) || {}
} }
static create<O = any, S = any>(config: Partial<ExtensionConfig<O, S>> = {}) { static create<O = any, S = any>(config: Partial<ExtensionConfig<O, S>> = {}) {
@ -326,49 +344,45 @@ export class Extension<Options = any, Storage = any> {
extension.options = mergeDeep(this.options as Record<string, any>, options) as Options extension.options = mergeDeep(this.options as Record<string, any>, options) as Options
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( extension.storage = callOrReturn(
extension, getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
'addStorage',
{
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
}, }),
)) )
return extension return extension
} }
extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<ExtensionConfig<ExtendedOptions, ExtendedStorage>> = {}) { extend<ExtendedOptions = Options, ExtendedStorage = Storage>(
extendedConfig: Partial<ExtensionConfig<ExtendedOptions, ExtendedStorage>> = {},
) {
const extension = new Extension<ExtendedOptions, ExtendedStorage>(extendedConfig) const extension = new Extension<ExtendedOptions, ExtendedStorage>(extendedConfig)
extension.parent = this extension.parent = this
this.child = extension this.child = extension
extension.name = extendedConfig.name extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
? extendedConfig.name
: extension.parent.name
if (extendedConfig.defaultOptions) { if (extendedConfig.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`) console.warn(
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
)
} }
extension.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>( extension.options = callOrReturn(
extension, getExtensionField<AnyConfig['addOptions']>(extension, 'addOptions', {
'addOptions',
{
name: extension.name, name: extension.name,
}, }),
)) )
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( extension.storage = callOrReturn(
extension, getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
'addStorage',
{
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
}, }),
)) )
return extension return extension
} }

View File

@ -1,7 +1,7 @@
import { keymap } from 'prosemirror-keymap' import { keymap } from '@tiptap/pm/keymap'
import { Node as ProsemirrorNode, Schema } from 'prosemirror-model' import { Node as ProsemirrorNode, Schema } from '@tiptap/pm/model'
import { Plugin } from 'prosemirror-state' import { Plugin } from '@tiptap/pm/state'
import { Decoration, EditorView } from 'prosemirror-view' import { Decoration, EditorView } from '@tiptap/pm/view'
import { Mark, NodeConfig } from '.' import { Mark, NodeConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
@ -20,7 +20,6 @@ import { callOrReturn } from './utilities/callOrReturn'
import { findDuplicates } from './utilities/findDuplicates' import { findDuplicates } from './utilities/findDuplicates'
export class ExtensionManager { export class ExtensionManager {
editor: Editor editor: Editor
schema: Schema schema: Schema
@ -64,21 +63,13 @@ export class ExtensionManager {
this.editor.on('beforeCreate', onBeforeCreate) this.editor.on('beforeCreate', onBeforeCreate)
} }
const onCreate = getExtensionField<AnyConfig['onCreate']>( const onCreate = getExtensionField<AnyConfig['onCreate']>(extension, 'onCreate', context)
extension,
'onCreate',
context,
)
if (onCreate) { if (onCreate) {
this.editor.on('create', onCreate) this.editor.on('create', onCreate)
} }
const onUpdate = getExtensionField<AnyConfig['onUpdate']>( const onUpdate = getExtensionField<AnyConfig['onUpdate']>(extension, 'onUpdate', context)
extension,
'onUpdate',
context,
)
if (onUpdate) { if (onUpdate) {
this.editor.on('update', onUpdate) this.editor.on('update', onUpdate)
@ -104,31 +95,19 @@ export class ExtensionManager {
this.editor.on('transaction', onTransaction) this.editor.on('transaction', onTransaction)
} }
const onFocus = getExtensionField<AnyConfig['onFocus']>( const onFocus = getExtensionField<AnyConfig['onFocus']>(extension, 'onFocus', context)
extension,
'onFocus',
context,
)
if (onFocus) { if (onFocus) {
this.editor.on('focus', onFocus) this.editor.on('focus', onFocus)
} }
const onBlur = getExtensionField<AnyConfig['onBlur']>( const onBlur = getExtensionField<AnyConfig['onBlur']>(extension, 'onBlur', context)
extension,
'onBlur',
context,
)
if (onBlur) { if (onBlur) {
this.editor.on('blur', onBlur) this.editor.on('blur', onBlur)
} }
const onDestroy = getExtensionField<AnyConfig['onDestroy']>( const onDestroy = getExtensionField<AnyConfig['onDestroy']>(extension, 'onDestroy', context)
extension,
'onDestroy',
context,
)
if (onDestroy) { if (onDestroy) {
this.editor.on('destroy', onDestroy) this.editor.on('destroy', onDestroy)
@ -141,38 +120,41 @@ export class ExtensionManager {
const duplicatedNames = findDuplicates(resolvedExtensions.map(extension => extension.name)) const duplicatedNames = findDuplicates(resolvedExtensions.map(extension => extension.name))
if (duplicatedNames.length) { if (duplicatedNames.length) {
console.warn(`[tiptap warn]: Duplicate extension names found: [${duplicatedNames.map(item => `'${item}'`).join(', ')}]. This can lead to issues.`) console.warn(
`[tiptap warn]: Duplicate extension names found: [${duplicatedNames
.map(item => `'${item}'`)
.join(', ')}]. This can lead to issues.`,
)
} }
return resolvedExtensions return resolvedExtensions
} }
static flatten(extensions: Extensions): Extensions { static flatten(extensions: Extensions): Extensions {
return extensions return (
.map(extension => { extensions
const context = { .map(extension => {
name: extension.name, const context = {
options: extension.options, name: extension.name,
storage: extension.storage, options: extension.options,
} storage: extension.storage,
}
const addExtensions = getExtensionField<AnyConfig['addExtensions']>( const addExtensions = getExtensionField<AnyConfig['addExtensions']>(
extension,
'addExtensions',
context,
)
if (addExtensions) {
return [
extension, extension,
...this.flatten(addExtensions()), 'addExtensions',
] context,
} )
return extension if (addExtensions) {
}) return [extension, ...this.flatten(addExtensions())]
// `Infinity` will break TypeScript so we set a number that is probably high enough }
.flat(10)
return extension
})
// `Infinity` will break TypeScript so we set a number that is probably high enough
.flat(10)
)
} }
static sort(extensions: Extensions): Extensions { static sort(extensions: Extensions): Extensions {
@ -256,16 +238,14 @@ export class ExtensionManager {
// bind exit handling // bind exit handling
if (extension.type === 'mark' && extension.config.exitable) { if (extension.type === 'mark' && extension.config.exitable) {
defaultBindings.ArrowRight = () => Mark.handleExit({ editor, mark: (extension as Mark) }) defaultBindings.ArrowRight = () => Mark.handleExit({ editor, mark: extension as Mark })
} }
if (addKeyboardShortcuts) { if (addKeyboardShortcuts) {
const bindings = Object.fromEntries( const bindings = Object.fromEntries(
Object Object.entries(addKeyboardShortcuts()).map(([shortcut, method]) => {
.entries(addKeyboardShortcuts()) return [shortcut, () => method({ editor })]
.map(([shortcut, method]) => { }),
return [shortcut, () => method({ editor })]
}),
) )
defaultBindings = { ...defaultBindings, ...bindings } defaultBindings = { ...defaultBindings, ...bindings }
@ -332,46 +312,50 @@ export class ExtensionManager {
const { editor } = this const { editor } = this
const { nodeExtensions } = splitExtensions(this.extensions) const { nodeExtensions } = splitExtensions(this.extensions)
return Object.fromEntries(nodeExtensions return Object.fromEntries(
.filter(extension => !!getExtensionField(extension, 'addNodeView')) nodeExtensions
.map(extension => { .filter(extension => !!getExtensionField(extension, 'addNodeView'))
const extensionAttributes = this.attributes.filter(attribute => attribute.type === extension.name) .map(extension => {
const context = { const extensionAttributes = this.attributes.filter(
name: extension.name, attribute => attribute.type === extension.name,
options: extension.options, )
storage: extension.storage, const context = {
editor, name: extension.name,
type: getNodeType(extension.name, this.schema), options: extension.options,
} storage: extension.storage,
const addNodeView = getExtensionField<NodeConfig['addNodeView']>(
extension,
'addNodeView',
context,
)
if (!addNodeView) {
return []
}
const nodeview = (
node: ProsemirrorNode,
view: EditorView,
getPos: (() => number) | boolean,
decorations: Decoration[],
) => {
const HTMLAttributes = getRenderedAttributes(node, extensionAttributes)
return addNodeView()({
editor, editor,
node, type: getNodeType(extension.name, this.schema),
getPos, }
decorations, const addNodeView = getExtensionField<NodeConfig['addNodeView']>(
HTMLAttributes,
extension, extension,
}) 'addNodeView',
} context,
)
return [extension.name, nodeview] if (!addNodeView) {
})) return []
}
const nodeview = (
node: ProsemirrorNode,
view: EditorView,
getPos: (() => number) | boolean,
decorations: Decoration[],
) => {
const HTMLAttributes = getRenderedAttributes(node, extensionAttributes)
return addNodeView()({
editor,
node,
getPos,
decorations,
HTMLAttributes,
extension,
})
}
return [extension.name, nodeview]
}),
)
} }
} }

View File

@ -1,4 +1,4 @@
import { EditorState, Plugin, TextSelection } from 'prosemirror-state' import { EditorState, Plugin, TextSelection } from '@tiptap/pm/state'
import { CommandManager } from './CommandManager' import { CommandManager } from './CommandManager'
import { Editor } from './Editor' import { Editor } from './Editor'
@ -14,46 +14,47 @@ import {
import { isRegExp } from './utilities/isRegExp' import { isRegExp } from './utilities/isRegExp'
export type InputRuleMatch = { export type InputRuleMatch = {
index: number, index: number
text: string, text: string
replaceWith?: string, replaceWith?: string
match?: RegExpMatchArray, match?: RegExpMatchArray
data?: Record<string, any>, data?: Record<string, any>
} }
export type InputRuleFinder = export type InputRuleFinder = RegExp | ((text: string) => InputRuleMatch | null)
| RegExp
| ((text: string) => InputRuleMatch | null)
export class InputRule { export class InputRule {
find: InputRuleFinder find: InputRuleFinder
handler: (props: { handler: (props: {
state: EditorState, state: EditorState
range: Range, range: Range
match: ExtendedRegExpMatchArray, match: ExtendedRegExpMatchArray
commands: SingleCommands, commands: SingleCommands
chain: () => ChainedCommands, chain: () => ChainedCommands
can: () => CanCommands, can: () => CanCommands
}) => void | null }) => void | null
constructor(config: { constructor(config: {
find: InputRuleFinder, find: InputRuleFinder
handler: (props: { handler: (props: {
state: EditorState, state: EditorState
range: Range, range: Range
match: ExtendedRegExpMatchArray, match: ExtendedRegExpMatchArray
commands: SingleCommands, commands: SingleCommands
chain: () => ChainedCommands, chain: () => ChainedCommands
can: () => CanCommands, can: () => CanCommands
}) => void | null, }) => void | null
}) { }) {
this.find = config.find this.find = config.find
this.handler = config.handler this.handler = config.handler
} }
} }
const inputRuleMatcherHandler = (text: string, find: InputRuleFinder): ExtendedRegExpMatchArray | null => { const inputRuleMatcherHandler = (
text: string,
find: InputRuleFinder,
): ExtendedRegExpMatchArray | null => {
if (isRegExp(find)) { if (isRegExp(find)) {
return find.exec(text) return find.exec(text)
} }
@ -72,7 +73,9 @@ const inputRuleMatcherHandler = (text: string, find: InputRuleFinder): ExtendedR
if (inputRuleMatch.replaceWith) { if (inputRuleMatch.replaceWith) {
if (!inputRuleMatch.text.includes(inputRuleMatch.replaceWith)) { if (!inputRuleMatch.text.includes(inputRuleMatch.replaceWith)) {
console.warn('[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".') console.warn(
'[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".',
)
} }
result.push(inputRuleMatch.replaceWith) result.push(inputRuleMatch.replaceWith)
@ -82,20 +85,15 @@ const inputRuleMatcherHandler = (text: string, find: InputRuleFinder): ExtendedR
} }
function run(config: { function run(config: {
editor: Editor, editor: Editor
from: number, from: number
to: number, to: number
text: string, text: string
rules: InputRule[], rules: InputRule[]
plugin: Plugin, plugin: Plugin
}): boolean { }): boolean {
const { const {
editor, editor, from, to, text, rules, plugin,
from,
to,
text,
rules,
plugin,
} = config } = config
const { view } = editor const { view } = editor
@ -179,7 +177,7 @@ function run(config: {
* input that matches any of the given rules to trigger the rules * input that matches any of the given rules to trigger the rules
* action. * action.
*/ */
export function inputRulesPlugin(props: { editor: Editor, rules: InputRule[] }): Plugin { export function inputRulesPlugin(props: { editor: Editor; rules: InputRule[] }): Plugin {
const { editor, rules } = props const { editor, rules } = props
const plugin = new Plugin({ const plugin = new Plugin({
state: { state: {
@ -193,9 +191,7 @@ export function inputRulesPlugin(props: { editor: Editor, rules: InputRule[] }):
return stored return stored
} }
return tr.selectionSet || tr.docChanged return tr.selectionSet || tr.docChanged ? null : prev
? null
: prev
}, },
}, },

View File

@ -1,10 +1,7 @@
import { import {
DOMOutputSpec, DOMOutputSpec, Mark as ProseMirrorMark, MarkSpec, MarkType,
Mark as ProseMirrorMark, } from '@tiptap/pm/model'
MarkSpec, import { Plugin, Transaction } from '@tiptap/pm/state'
MarkType,
} from 'prosemirror-model'
import { Plugin, Transaction } from 'prosemirror-state'
import { MarkConfig } from '.' import { MarkConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
@ -26,358 +23,386 @@ import { mergeDeep } from './utilities/mergeDeep'
declare module '@tiptap/core' { declare module '@tiptap/core' {
export interface MarkConfig<Options = any, Storage = any> { export interface MarkConfig<Options = any, Storage = any> {
[key: string]: any; [key: string]: any
/** /**
* Name * Name
*/ */
name: string, name: string
/** /**
* Priority * Priority
*/ */
priority?: number, priority?: number
/** /**
* Default options * Default options
*/ */
defaultOptions?: Options, defaultOptions?: Options
/** /**
* Default Options * Default Options
*/ */
addOptions?: (this: { addOptions?: (this: {
name: string, name: string
parent: Exclude<ParentConfig<MarkConfig<Options, Storage>>['addOptions'], undefined>, parent: Exclude<ParentConfig<MarkConfig<Options, Storage>>['addOptions'], undefined>
}) => Options, }) => Options
/** /**
* Default Storage * Default Storage
*/ */
addStorage?: (this: { addStorage?: (this: {
name: string, name: string
options: Options, options: Options
parent: Exclude<ParentConfig<MarkConfig<Options, Storage>>['addStorage'], undefined>, parent: Exclude<ParentConfig<MarkConfig<Options, Storage>>['addStorage'], undefined>
}) => Storage, }) => Storage
/** /**
* Global attributes * Global attributes
*/ */
addGlobalAttributes?: (this: { addGlobalAttributes?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['addGlobalAttributes'], parent: ParentConfig<MarkConfig<Options, Storage>>['addGlobalAttributes']
}) => GlobalAttributes | {}, }) => GlobalAttributes | {}
/** /**
* Raw * Raw
*/ */
addCommands?: (this: { addCommands?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: MarkType, type: MarkType
parent: ParentConfig<MarkConfig<Options, Storage>>['addCommands'], parent: ParentConfig<MarkConfig<Options, Storage>>['addCommands']
}) => Partial<RawCommands>, }) => Partial<RawCommands>
/** /**
* Keyboard shortcuts * Keyboard shortcuts
*/ */
addKeyboardShortcuts?: (this: { addKeyboardShortcuts?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: MarkType, type: MarkType
parent: ParentConfig<MarkConfig<Options, Storage>>['addKeyboardShortcuts'], parent: ParentConfig<MarkConfig<Options, Storage>>['addKeyboardShortcuts']
}) => { }) => {
[key: string]: KeyboardShortcutCommand, [key: string]: KeyboardShortcutCommand
}, }
/** /**
* Input rules * Input rules
*/ */
addInputRules?: (this: { addInputRules?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: MarkType, type: MarkType
parent: ParentConfig<MarkConfig<Options, Storage>>['addInputRules'], parent: ParentConfig<MarkConfig<Options, Storage>>['addInputRules']
}) => InputRule[], }) => InputRule[]
/** /**
* Paste rules * Paste rules
*/ */
addPasteRules?: (this: { addPasteRules?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: MarkType, type: MarkType
parent: ParentConfig<MarkConfig<Options, Storage>>['addPasteRules'], parent: ParentConfig<MarkConfig<Options, Storage>>['addPasteRules']
}) => PasteRule[], }) => PasteRule[]
/** /**
* ProseMirror plugins * ProseMirror plugins
*/ */
addProseMirrorPlugins?: (this: { addProseMirrorPlugins?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: MarkType, type: MarkType
parent: ParentConfig<MarkConfig<Options, Storage>>['addProseMirrorPlugins'], parent: ParentConfig<MarkConfig<Options, Storage>>['addProseMirrorPlugins']
}) => Plugin[], }) => Plugin[]
/** /**
* Extensions * Extensions
*/ */
addExtensions?: (this: { addExtensions?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['addExtensions'], parent: ParentConfig<MarkConfig<Options, Storage>>['addExtensions']
}) => Extensions, }) => Extensions
/** /**
* Extend Node Schema * Extend Node Schema
*/ */
extendNodeSchema?: (( extendNodeSchema?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<MarkConfig<Options, Storage>>['extendNodeSchema'], storage: Storage
}, parent: ParentConfig<MarkConfig<Options, Storage>>['extendNodeSchema']
extension: Node, },
) => Record<string, any>) | null, extension: Node,
) => Record<string, any>)
| null
/** /**
* Extend Mark Schema * Extend Mark Schema
*/ */
extendMarkSchema?: (( extendMarkSchema?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<MarkConfig<Options, Storage>>['extendMarkSchema'], storage: Storage
}, parent: ParentConfig<MarkConfig<Options, Storage>>['extendMarkSchema']
extension: Mark, },
) => Record<string, any>) | null, extension: Mark,
) => Record<string, any>)
| null
/** /**
* The editor is not ready yet. * The editor is not ready yet.
*/ */
onBeforeCreate?: ((this: { onBeforeCreate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onBeforeCreate'], type: MarkType
}) => void) | null, parent: ParentConfig<MarkConfig<Options, Storage>>['onBeforeCreate']
}) => void)
| null
/** /**
* The editor is ready. * The editor is ready.
*/ */
onCreate?: ((this: { onCreate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onCreate'], type: MarkType
}) => void) | null, parent: ParentConfig<MarkConfig<Options, Storage>>['onCreate']
}) => void)
| null
/** /**
* The content has changed. * The content has changed.
*/ */
onUpdate?: ((this: { onUpdate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onUpdate'], type: MarkType
}) => void) | null, parent: ParentConfig<MarkConfig<Options, Storage>>['onUpdate']
}) => void)
| null
/** /**
* The selection has changed. * The selection has changed.
*/ */
onSelectionUpdate?: ((this: { onSelectionUpdate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onSelectionUpdate'], type: MarkType
}) => void) | null, parent: ParentConfig<MarkConfig<Options, Storage>>['onSelectionUpdate']
}) => void)
| null
/** /**
* The editor state has changed. * The editor state has changed.
*/ */
onTransaction?: (( onTransaction?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onTransaction'], type: MarkType
}, parent: ParentConfig<MarkConfig<Options, Storage>>['onTransaction']
props: { },
transaction: Transaction, props: {
}, transaction: Transaction
) => void) | null, },
) => void)
| null
/** /**
* The editor is focused. * The editor is focused.
*/ */
onFocus?: (( onFocus?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onFocus'], type: MarkType
}, parent: ParentConfig<MarkConfig<Options, Storage>>['onFocus']
props: { },
event: FocusEvent, props: {
}, event: FocusEvent
) => void) | null, },
) => void)
| null
/** /**
* The editor isnt focused anymore. * The editor isnt focused anymore.
*/ */
onBlur?: (( onBlur?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onBlur'], type: MarkType
}, parent: ParentConfig<MarkConfig<Options, Storage>>['onBlur']
props: { },
event: FocusEvent, props: {
}, event: FocusEvent
) => void) | null, },
) => void)
| null
/** /**
* The editor is destroyed. * The editor is destroyed.
*/ */
onDestroy?: ((this: { onDestroy?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: MarkType, editor: Editor
parent: ParentConfig<MarkConfig<Options, Storage>>['onDestroy'], type: MarkType
}) => void) | null, parent: ParentConfig<MarkConfig<Options, Storage>>['onDestroy']
}) => void)
| null
/** /**
* Keep mark after split node * Keep mark after split node
*/ */
keepOnSplit?: boolean | (() => boolean), keepOnSplit?: boolean | (() => boolean)
/** /**
* Inclusive * Inclusive
*/ */
inclusive?: MarkSpec['inclusive'] | ((this: { inclusive?:
name: string, | MarkSpec['inclusive']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<MarkConfig<Options, Storage>>['inclusive'], options: Options
}) => MarkSpec['inclusive']), storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['inclusive']
}) => MarkSpec['inclusive'])
/** /**
* Excludes * Excludes
*/ */
excludes?: MarkSpec['excludes'] | ((this: { excludes?:
name: string, | MarkSpec['excludes']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<MarkConfig<Options, Storage>>['excludes'], options: Options
}) => MarkSpec['excludes']), storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['excludes']
}) => MarkSpec['excludes'])
/** /**
* Marks this Mark as exitable * Marks this Mark as exitable
*/ */
exitable?: boolean | (() => boolean), exitable?: boolean | (() => boolean)
/** /**
* Group * Group
*/ */
group?: MarkSpec['group'] | ((this: { group?:
name: string, | MarkSpec['group']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<MarkConfig<Options, Storage>>['group'], options: Options
}) => MarkSpec['group']), storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['group']
}) => MarkSpec['group'])
/** /**
* Spanning * Spanning
*/ */
spanning?: MarkSpec['spanning'] | ((this: { spanning?:
name: string, | MarkSpec['spanning']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<MarkConfig<Options, Storage>>['spanning'], options: Options
}) => MarkSpec['spanning']), storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['spanning']
}) => MarkSpec['spanning'])
/** /**
* Code * Code
*/ */
code?: boolean | ((this: { code?:
name: string, | boolean
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<MarkConfig<Options, Storage>>['code'], options: Options
}) => boolean), storage: Storage
parent: ParentConfig<MarkConfig<Options, Storage>>['code']
}) => boolean)
/** /**
* Parse HTML * Parse HTML
*/ */
parseHTML?: ( parseHTML?: (this: {
this: { name: string
name: string, options: Options
options: Options, storage: Storage
storage: Storage, parent: ParentConfig<MarkConfig<Options, Storage>>['parseHTML']
parent: ParentConfig<MarkConfig<Options, Storage>>['parseHTML'], }) => MarkSpec['parseDOM']
},
) => MarkSpec['parseDOM'],
/** /**
* Render HTML * Render HTML
*/ */
renderHTML?: (( renderHTML?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<MarkConfig<Options, Storage>>['renderHTML'], storage: Storage
}, parent: ParentConfig<MarkConfig<Options, Storage>>['renderHTML']
props: { },
mark: ProseMirrorMark, props: {
HTMLAttributes: Record<string, any>, mark: ProseMirrorMark
}, HTMLAttributes: Record<string, any>
) => DOMOutputSpec) | null, },
) => DOMOutputSpec)
| null
/** /**
* Attributes * Attributes
*/ */
addAttributes?: ( addAttributes?: (this: {
this: { name: string
name: string, options: Options
options: Options, storage: Storage
storage: Storage, parent: ParentConfig<MarkConfig<Options, Storage>>['addAttributes']
parent: ParentConfig<MarkConfig<Options, Storage>>['addAttributes'], }) => Attributes | {}
},
) => Attributes | {},
} }
} }
@ -408,30 +433,28 @@ export class Mark<Options = any, Storage = any> {
this.name = this.config.name this.name = this.config.name
if (config.defaultOptions) { if (config.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`) console.warn(
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`,
)
} }
// TODO: remove `addOptions` fallback // TODO: remove `addOptions` fallback
this.options = this.config.defaultOptions this.options = this.config.defaultOptions
if (this.config.addOptions) { if (this.config.addOptions) {
this.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>( this.options = callOrReturn(
this, getExtensionField<AnyConfig['addOptions']>(this, 'addOptions', {
'addOptions',
{
name: this.name, name: this.name,
}, }),
)) )
} }
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( this.storage = callOrReturn(
this, getExtensionField<AnyConfig['addStorage']>(this, 'addStorage', {
'addStorage',
{
name: this.name, name: this.name,
options: this.options, options: this.options,
}, }),
)) || {} ) || {}
} }
static create<O = any, S = any>(config: Partial<MarkConfig<O, S>> = {}) { static create<O = any, S = any>(config: Partial<MarkConfig<O, S>> = {}) {
@ -445,60 +468,50 @@ export class Mark<Options = any, Storage = any> {
extension.options = mergeDeep(this.options as Record<string, any>, options) as Options extension.options = mergeDeep(this.options as Record<string, any>, options) as Options
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( extension.storage = callOrReturn(
extension, getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
'addStorage',
{
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
}, }),
)) )
return extension return extension
} }
extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> = {}) { extend<ExtendedOptions = Options, ExtendedStorage = Storage>(
extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> = {},
) {
const extension = new Mark<ExtendedOptions, ExtendedStorage>(extendedConfig) const extension = new Mark<ExtendedOptions, ExtendedStorage>(extendedConfig)
extension.parent = this extension.parent = this
this.child = extension this.child = extension
extension.name = extendedConfig.name extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
? extendedConfig.name
: extension.parent.name
if (extendedConfig.defaultOptions) { if (extendedConfig.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`) console.warn(
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
)
} }
extension.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>( extension.options = callOrReturn(
extension, getExtensionField<AnyConfig['addOptions']>(extension, 'addOptions', {
'addOptions',
{
name: extension.name, name: extension.name,
}, }),
)) )
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( extension.storage = callOrReturn(
extension, getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
'addStorage',
{
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
}, }),
)) )
return extension return extension
} }
static handleExit({ static handleExit({ editor, mark }: { editor: Editor; mark: Mark }) {
editor,
mark,
}: {
editor: Editor
mark: Mark
}) {
const { tr } = editor.state const { tr } = editor.state
const currentPos = editor.state.selection.$from const currentPos = editor.state.selection.$from
const isAtEnd = currentPos.pos === currentPos.end() const isAtEnd = currentPos.pos === currentPos.end()

View File

@ -1,10 +1,7 @@
import { import {
DOMOutputSpec, DOMOutputSpec, Node as ProseMirrorNode, NodeSpec, NodeType,
Node as ProseMirrorNode, } from '@tiptap/pm/model'
NodeSpec, import { Plugin, Transaction } from '@tiptap/pm/state'
NodeType,
} from 'prosemirror-model'
import { Plugin, Transaction } from 'prosemirror-state'
import { NodeConfig } from '.' import { NodeConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
@ -26,443 +23,487 @@ import { mergeDeep } from './utilities/mergeDeep'
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface NodeConfig<Options = any, Storage = any> { interface NodeConfig<Options = any, Storage = any> {
[key: string]: any; [key: string]: any
/** /**
* Name * Name
*/ */
name: string, name: string
/** /**
* Priority * Priority
*/ */
priority?: number, priority?: number
/** /**
* Default options * Default options
*/ */
defaultOptions?: Options, defaultOptions?: Options
/** /**
* Default Options * Default Options
*/ */
addOptions?: (this: { addOptions?: (this: {
name: string, name: string
parent: Exclude<ParentConfig<NodeConfig<Options, Storage>>['addOptions'], undefined>, parent: Exclude<ParentConfig<NodeConfig<Options, Storage>>['addOptions'], undefined>
}) => Options, }) => Options
/** /**
* Default Storage * Default Storage
*/ */
addStorage?: (this: { addStorage?: (this: {
name: string, name: string
options: Options, options: Options
parent: Exclude<ParentConfig<NodeConfig<Options, Storage>>['addStorage'], undefined>, parent: Exclude<ParentConfig<NodeConfig<Options, Storage>>['addStorage'], undefined>
}) => Storage, }) => Storage
/** /**
* Global attributes * Global attributes
*/ */
addGlobalAttributes?: (this: { addGlobalAttributes?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['addGlobalAttributes'], parent: ParentConfig<NodeConfig<Options, Storage>>['addGlobalAttributes']
}) => GlobalAttributes | {}, }) => GlobalAttributes | {}
/** /**
* Raw * Raw
*/ */
addCommands?: (this: { addCommands?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: NodeType, type: NodeType
parent: ParentConfig<NodeConfig<Options, Storage>>['addCommands'], parent: ParentConfig<NodeConfig<Options, Storage>>['addCommands']
}) => Partial<RawCommands>, }) => Partial<RawCommands>
/** /**
* Keyboard shortcuts * Keyboard shortcuts
*/ */
addKeyboardShortcuts?: (this: { addKeyboardShortcuts?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: NodeType, type: NodeType
parent: ParentConfig<NodeConfig<Options, Storage>>['addKeyboardShortcuts'], parent: ParentConfig<NodeConfig<Options, Storage>>['addKeyboardShortcuts']
}) => { }) => {
[key: string]: KeyboardShortcutCommand, [key: string]: KeyboardShortcutCommand
}, }
/** /**
* Input rules * Input rules
*/ */
addInputRules?: (this: { addInputRules?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: NodeType, type: NodeType
parent: ParentConfig<NodeConfig<Options, Storage>>['addInputRules'], parent: ParentConfig<NodeConfig<Options, Storage>>['addInputRules']
}) => InputRule[], }) => InputRule[]
/** /**
* Paste rules * Paste rules
*/ */
addPasteRules?: (this: { addPasteRules?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: NodeType, type: NodeType
parent: ParentConfig<NodeConfig<Options, Storage>>['addPasteRules'], parent: ParentConfig<NodeConfig<Options, Storage>>['addPasteRules']
}) => PasteRule[], }) => PasteRule[]
/** /**
* ProseMirror plugins * ProseMirror plugins
*/ */
addProseMirrorPlugins?: (this: { addProseMirrorPlugins?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
editor: Editor, editor: Editor
type: NodeType, type: NodeType
parent: ParentConfig<NodeConfig<Options, Storage>>['addProseMirrorPlugins'], parent: ParentConfig<NodeConfig<Options, Storage>>['addProseMirrorPlugins']
}) => Plugin[], }) => Plugin[]
/** /**
* Extensions * Extensions
*/ */
addExtensions?: (this: { addExtensions?: (this: {
name: string, name: string
options: Options, options: Options
storage: Storage, storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['addExtensions'], parent: ParentConfig<NodeConfig<Options, Storage>>['addExtensions']
}) => Extensions, }) => Extensions
/** /**
* Extend Node Schema * Extend Node Schema
*/ */
extendNodeSchema?: (( extendNodeSchema?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<NodeConfig<Options, Storage>>['extendNodeSchema'], storage: Storage
}, parent: ParentConfig<NodeConfig<Options, Storage>>['extendNodeSchema']
extension: Node, },
) => Record<string, any>) | null, extension: Node,
) => Record<string, any>)
| null
/** /**
* Extend Mark Schema * Extend Mark Schema
*/ */
extendMarkSchema?: (( extendMarkSchema?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<NodeConfig<Options, Storage>>['extendMarkSchema'], storage: Storage
}, parent: ParentConfig<NodeConfig<Options, Storage>>['extendMarkSchema']
extension: Node, },
) => Record<string, any>) | null, extension: Node,
) => Record<string, any>)
| null
/** /**
* The editor is not ready yet. * The editor is not ready yet.
*/ */
onBeforeCreate?: ((this: { onBeforeCreate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onBeforeCreate'], type: NodeType
}) => void) | null, parent: ParentConfig<NodeConfig<Options, Storage>>['onBeforeCreate']
}) => void)
| null
/** /**
* The editor is ready. * The editor is ready.
*/ */
onCreate?: ((this: { onCreate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onCreate'], type: NodeType
}) => void) | null, parent: ParentConfig<NodeConfig<Options, Storage>>['onCreate']
}) => void)
| null
/** /**
* The content has changed. * The content has changed.
*/ */
onUpdate?: ((this: { onUpdate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onUpdate'], type: NodeType
}) => void) | null, parent: ParentConfig<NodeConfig<Options, Storage>>['onUpdate']
}) => void)
| null
/** /**
* The selection has changed. * The selection has changed.
*/ */
onSelectionUpdate?: ((this: { onSelectionUpdate?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onSelectionUpdate'], type: NodeType
}) => void) | null, parent: ParentConfig<NodeConfig<Options, Storage>>['onSelectionUpdate']
}) => void)
| null
/** /**
* The editor state has changed. * The editor state has changed.
*/ */
onTransaction?: (( onTransaction?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onTransaction'], type: NodeType
}, parent: ParentConfig<NodeConfig<Options, Storage>>['onTransaction']
props: { },
transaction: Transaction, props: {
}, transaction: Transaction
) => void) | null, },
) => void)
| null
/** /**
* The editor is focused. * The editor is focused.
*/ */
onFocus?: (( onFocus?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onFocus'], type: NodeType
}, parent: ParentConfig<NodeConfig<Options, Storage>>['onFocus']
props: { },
event: FocusEvent, props: {
}, event: FocusEvent
) => void) | null, },
) => void)
| null
/** /**
* The editor isnt focused anymore. * The editor isnt focused anymore.
*/ */
onBlur?: (( onBlur?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onBlur'], type: NodeType
}, parent: ParentConfig<NodeConfig<Options, Storage>>['onBlur']
props: { },
event: FocusEvent, props: {
}, event: FocusEvent
) => void) | null, },
) => void)
| null
/** /**
* The editor is destroyed. * The editor is destroyed.
*/ */
onDestroy?: ((this: { onDestroy?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['onDestroy'], type: NodeType
}) => void) | null, parent: ParentConfig<NodeConfig<Options, Storage>>['onDestroy']
}) => void)
| null
/** /**
* Node View * Node View
*/ */
addNodeView?: ((this: { addNodeView?:
name: string, | ((this: {
options: Options, name: string
storage: Storage, options: Options
editor: Editor, storage: Storage
type: NodeType, editor: Editor
parent: ParentConfig<NodeConfig<Options, Storage>>['addNodeView'], type: NodeType
}) => NodeViewRenderer) | null, parent: ParentConfig<NodeConfig<Options, Storage>>['addNodeView']
}) => NodeViewRenderer)
| null
/** /**
* TopNode * TopNode
*/ */
topNode?: boolean, topNode?: boolean
/** /**
* Content * Content
*/ */
content?: NodeSpec['content'] | ((this: { content?:
name: string, | NodeSpec['content']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['content'], options: Options
}) => NodeSpec['content']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['content']
}) => NodeSpec['content'])
/** /**
* Marks * Marks
*/ */
marks?: NodeSpec['marks'] | ((this: { marks?:
name: string, | NodeSpec['marks']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['marks'], options: Options
}) => NodeSpec['marks']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['marks']
}) => NodeSpec['marks'])
/** /**
* Group * Group
*/ */
group?: NodeSpec['group'] | ((this: { group?:
name: string, | NodeSpec['group']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['group'], options: Options
}) => NodeSpec['group']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['group']
}) => NodeSpec['group'])
/** /**
* Inline * Inline
*/ */
inline?: NodeSpec['inline'] | ((this: { inline?:
name: string, | NodeSpec['inline']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['inline'], options: Options
}) => NodeSpec['inline']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['inline']
}) => NodeSpec['inline'])
/** /**
* Atom * Atom
*/ */
atom?: NodeSpec['atom'] | ((this: { atom?:
name: string, | NodeSpec['atom']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['atom'], options: Options
}) => NodeSpec['atom']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['atom']
}) => NodeSpec['atom'])
/** /**
* Selectable * Selectable
*/ */
selectable?: NodeSpec['selectable'] | ((this: { selectable?:
name: string, | NodeSpec['selectable']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['selectable'], options: Options
}) => NodeSpec['selectable']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['selectable']
}) => NodeSpec['selectable'])
/** /**
* Draggable * Draggable
*/ */
draggable?: NodeSpec['draggable'] | ((this: { draggable?:
name: string, | NodeSpec['draggable']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['draggable'], options: Options
}) => NodeSpec['draggable']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['draggable']
}) => NodeSpec['draggable'])
/** /**
* Code * Code
*/ */
code?: NodeSpec['code'] | ((this: { code?:
name: string, | NodeSpec['code']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['code'], options: Options
}) => NodeSpec['code']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['code']
}) => NodeSpec['code'])
/** /**
* Whitespace * Whitespace
*/ */
whitespace?: NodeSpec['whitespace'] | ((this: { whitespace?:
name: string, | NodeSpec['whitespace']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['whitespace'], options: Options
}) => NodeSpec['whitespace']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['whitespace']
}) => NodeSpec['whitespace'])
/** /**
* Defining * Defining
*/ */
defining?: NodeSpec['defining'] | ((this: { defining?:
name: string, | NodeSpec['defining']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['defining'], options: Options
}) => NodeSpec['defining']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['defining']
}) => NodeSpec['defining'])
/** /**
* Isolating * Isolating
*/ */
isolating?: NodeSpec['isolating'] | ((this: { isolating?:
name: string, | NodeSpec['isolating']
options: Options, | ((this: {
storage: Storage, name: string
parent: ParentConfig<NodeConfig<Options, Storage>>['isolating'], options: Options
}) => NodeSpec['isolating']), storage: Storage
parent: ParentConfig<NodeConfig<Options, Storage>>['isolating']
}) => NodeSpec['isolating'])
/** /**
* Parse HTML * Parse HTML
*/ */
parseHTML?: ( parseHTML?: (this: {
this: { name: string
name: string, options: Options
options: Options, storage: Storage
storage: Storage, parent: ParentConfig<NodeConfig<Options, Storage>>['parseHTML']
parent: ParentConfig<NodeConfig<Options, Storage>>['parseHTML'], }) => NodeSpec['parseDOM']
},
) => NodeSpec['parseDOM'],
/** /**
* Render HTML * Render HTML
*/ */
renderHTML?: (( renderHTML?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<NodeConfig<Options, Storage>>['renderHTML'], storage: Storage
}, parent: ParentConfig<NodeConfig<Options, Storage>>['renderHTML']
props: { },
node: ProseMirrorNode, props: {
HTMLAttributes: Record<string, any>, node: ProseMirrorNode
} HTMLAttributes: Record<string, any>
) => DOMOutputSpec) | null, },
) => DOMOutputSpec)
| null
/** /**
* Render Text * Render Text
*/ */
renderText?: (( renderText?:
this: { | ((
name: string, this: {
options: Options, name: string
storage: Storage, options: Options
parent: ParentConfig<NodeConfig<Options, Storage>>['renderText'], storage: Storage
}, parent: ParentConfig<NodeConfig<Options, Storage>>['renderText']
props: { },
node: ProseMirrorNode, props: {
pos: number, node: ProseMirrorNode
parent: ProseMirrorNode, pos: number
index: number, parent: ProseMirrorNode
} index: number
) => string) | null, },
) => string)
| null
/** /**
* Add Attributes * Add Attributes
*/ */
addAttributes?: ( addAttributes?: (this: {
this: { name: string
name: string, options: Options
options: Options, storage: Storage
storage: Storage, parent: ParentConfig<NodeConfig<Options, Storage>>['addAttributes']
parent: ParentConfig<NodeConfig<Options, Storage>>['addAttributes'], }) => Attributes | {}
},
) => Attributes | {},
} }
} }
@ -493,30 +534,28 @@ export class Node<Options = any, Storage = any> {
this.name = this.config.name this.name = this.config.name
if (config.defaultOptions) { if (config.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`) console.warn(
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`,
)
} }
// TODO: remove `addOptions` fallback // TODO: remove `addOptions` fallback
this.options = this.config.defaultOptions this.options = this.config.defaultOptions
if (this.config.addOptions) { if (this.config.addOptions) {
this.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>( this.options = callOrReturn(
this, getExtensionField<AnyConfig['addOptions']>(this, 'addOptions', {
'addOptions',
{
name: this.name, name: this.name,
}, }),
)) )
} }
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( this.storage = callOrReturn(
this, getExtensionField<AnyConfig['addStorage']>(this, 'addStorage', {
'addStorage',
{
name: this.name, name: this.name,
options: this.options, options: this.options,
}, }),
)) || {} ) || {}
} }
static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> = {}) { static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> = {}) {
@ -530,49 +569,45 @@ export class Node<Options = any, Storage = any> {
extension.options = mergeDeep(this.options as Record<string, any>, options) as Options extension.options = mergeDeep(this.options as Record<string, any>, options) as Options
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( extension.storage = callOrReturn(
extension, getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
'addStorage',
{
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
}, }),
)) )
return extension return extension
} }
extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>> = {}) { extend<ExtendedOptions = Options, ExtendedStorage = Storage>(
extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>> = {},
) {
const extension = new Node<ExtendedOptions, ExtendedStorage>(extendedConfig) const extension = new Node<ExtendedOptions, ExtendedStorage>(extendedConfig)
extension.parent = this extension.parent = this
this.child = extension this.child = extension
extension.name = extendedConfig.name extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
? extendedConfig.name
: extension.parent.name
if (extendedConfig.defaultOptions) { if (extendedConfig.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`) console.warn(
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
)
} }
extension.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>( extension.options = callOrReturn(
extension, getExtensionField<AnyConfig['addOptions']>(extension, 'addOptions', {
'addOptions',
{
name: extension.name, name: extension.name,
}, }),
)) )
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>( extension.storage = callOrReturn(
extension, getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
'addStorage',
{
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
}, }),
)) )
return extension return extension
} }

View File

@ -1,6 +1,6 @@
import { Node as ProseMirrorNode } from 'prosemirror-model' import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { NodeSelection } from 'prosemirror-state' import { NodeSelection } from '@tiptap/pm/state'
import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view' import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
import { Editor as CoreEditor } from './Editor' import { Editor as CoreEditor } from './Editor'
import { Node } from './Node' import { Node } from './Node'
@ -12,7 +12,6 @@ export class NodeView<
Editor extends CoreEditor = CoreEditor, Editor extends CoreEditor = CoreEditor,
Options extends NodeViewRendererOptions = NodeViewRendererOptions, Options extends NodeViewRendererOptions = NodeViewRendererOptions,
> implements ProseMirrorNodeView { > implements ProseMirrorNodeView {
component: Component component: Component
editor: Editor editor: Editor
@ -59,7 +58,7 @@ export class NodeView<
onDragStart(event: DragEvent) { onDragStart(event: DragEvent) {
const { view } = this.editor const { view } = this.editor
const target = (event.target as HTMLElement) const target = event.target as HTMLElement
// get the drag handle element // get the drag handle element
// `closest` is not available for text nodes so we may have to use its parent // `closest` is not available for text nodes so we may have to use its parent
@ -67,11 +66,7 @@ export class NodeView<
? target.parentElement?.closest('[data-drag-handle]') ? target.parentElement?.closest('[data-drag-handle]')
: target.closest('[data-drag-handle]') : target.closest('[data-drag-handle]')
if ( if (!this.dom || this.contentDOM?.contains(target) || !dragHandle) {
!this.dom
|| this.contentDOM?.contains(target)
|| !dragHandle
) {
return return
} }
@ -110,7 +105,7 @@ export class NodeView<
return this.options.stopEvent({ event }) return this.options.stopEvent({ event })
} }
const target = (event.target as HTMLElement) const target = event.target as HTMLElement
const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target) const isInElement = this.dom.contains(target) && !this.contentDOM?.contains(target)
// any event from child nodes should be handled by ProseMirror // any event from child nodes should be handled by ProseMirror
@ -119,8 +114,7 @@ export class NodeView<
} }
const isDropEvent = event.type === 'drop' const isDropEvent = event.type === 'drop'
const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName) const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName) || target.isContentEditable
|| target.isContentEditable
// any input event within node views should be ignored by ProseMirror // any input event within node views should be ignored by ProseMirror
if (isInput && !isDropEvent) { if (isInput && !isDropEvent) {
@ -152,19 +146,26 @@ export class NodeView<
// we have to store that dragging started // we have to store that dragging started
if (isDraggable && isEditable && !isDragging && isClickEvent) { if (isDraggable && isEditable && !isDragging && isClickEvent) {
const dragHandle = target.closest('[data-drag-handle]') const dragHandle = target.closest('[data-drag-handle]')
const isValidDragHandle = dragHandle const isValidDragHandle = dragHandle && (this.dom === dragHandle || this.dom.contains(dragHandle))
&& (this.dom === dragHandle || (this.dom.contains(dragHandle)))
if (isValidDragHandle) { if (isValidDragHandle) {
this.isDragging = true this.isDragging = true
document.addEventListener('dragend', () => { document.addEventListener(
this.isDragging = false 'dragend',
}, { once: true }) () => {
this.isDragging = false
},
{ once: true },
)
document.addEventListener('mouseup', () => { document.addEventListener(
this.isDragging = false 'mouseup',
}, { once: true }) () => {
this.isDragging = false
},
{ once: true },
)
} }
} }
@ -183,7 +184,7 @@ export class NodeView<
return true return true
} }
ignoreMutation(mutation: MutationRecord | { type: 'selection', target: Element }) { ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
if (!this.dom || !this.contentDOM) { if (!this.dom || !this.contentDOM) {
return true return true
} }

View File

@ -1,4 +1,4 @@
import { EditorState, Plugin } from 'prosemirror-state' import { EditorState, Plugin } from '@tiptap/pm/state'
import { CommandManager } from './CommandManager' import { CommandManager } from './CommandManager'
import { Editor } from './Editor' import { Editor } from './Editor'
@ -14,46 +14,47 @@ import { isNumber } from './utilities/isNumber'
import { isRegExp } from './utilities/isRegExp' import { isRegExp } from './utilities/isRegExp'
export type PasteRuleMatch = { export type PasteRuleMatch = {
index: number, index: number
text: string, text: string
replaceWith?: string, replaceWith?: string
match?: RegExpMatchArray, match?: RegExpMatchArray
data?: Record<string, any>, data?: Record<string, any>
} }
export type PasteRuleFinder = export type PasteRuleFinder = RegExp | ((text: string) => PasteRuleMatch[] | null | undefined)
| RegExp
| ((text: string) => PasteRuleMatch[] | null | undefined)
export class PasteRule { export class PasteRule {
find: PasteRuleFinder find: PasteRuleFinder
handler: (props: { handler: (props: {
state: EditorState, state: EditorState
range: Range, range: Range
match: ExtendedRegExpMatchArray, match: ExtendedRegExpMatchArray
commands: SingleCommands, commands: SingleCommands
chain: () => ChainedCommands, chain: () => ChainedCommands
can: () => CanCommands, can: () => CanCommands
}) => void | null }) => void | null
constructor(config: { constructor(config: {
find: PasteRuleFinder, find: PasteRuleFinder
handler: (props: { handler: (props: {
state: EditorState, state: EditorState
range: Range, range: Range
match: ExtendedRegExpMatchArray, match: ExtendedRegExpMatchArray
commands: SingleCommands, commands: SingleCommands
chain: () => ChainedCommands, chain: () => ChainedCommands
can: () => CanCommands, can: () => CanCommands
}) => void | null, }) => void | null
}) { }) {
this.find = config.find this.find = config.find
this.handler = config.handler this.handler = config.handler
} }
} }
const pasteRuleMatcherHandler = (text: string, find: PasteRuleFinder): ExtendedRegExpMatchArray[] => { const pasteRuleMatcherHandler = (
text: string,
find: PasteRuleFinder,
): ExtendedRegExpMatchArray[] => {
if (isRegExp(find)) { if (isRegExp(find)) {
return [...text.matchAll(find)] return [...text.matchAll(find)]
} }
@ -73,7 +74,9 @@ const pasteRuleMatcherHandler = (text: string, find: PasteRuleFinder): ExtendedR
if (pasteRuleMatch.replaceWith) { if (pasteRuleMatch.replaceWith) {
if (!pasteRuleMatch.text.includes(pasteRuleMatch.replaceWith)) { if (!pasteRuleMatch.text.includes(pasteRuleMatch.replaceWith)) {
console.warn('[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".') console.warn(
'[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".',
)
} }
result.push(pasteRuleMatch.replaceWith) result.push(pasteRuleMatch.replaceWith)
@ -84,18 +87,14 @@ const pasteRuleMatcherHandler = (text: string, find: PasteRuleFinder): ExtendedR
} }
function run(config: { function run(config: {
editor: Editor, editor: Editor
state: EditorState, state: EditorState
from: number, from: number
to: number, to: number
rule: PasteRule, rule: PasteRule
}): boolean { }): boolean {
const { const {
editor, editor, state, from, to, rule,
state,
from,
to,
rule,
} = config } = config
const { commands, chain, can } = new CommandManager({ const { commands, chain, can } = new CommandManager({
@ -112,12 +111,7 @@ function run(config: {
const resolvedFrom = Math.max(from, pos) const resolvedFrom = Math.max(from, pos)
const resolvedTo = Math.min(to, pos + node.content.size) const resolvedTo = Math.min(to, pos + node.content.size)
const textToMatch = node.textBetween( const textToMatch = node.textBetween(resolvedFrom - pos, resolvedTo - pos, undefined, '\ufffc')
resolvedFrom - pos,
resolvedTo - pos,
undefined,
'\ufffc',
)
const matches = pasteRuleMatcherHandler(textToMatch, rule.find) const matches = pasteRuleMatcherHandler(textToMatch, rule.find)
@ -156,7 +150,7 @@ function run(config: {
* text that matches any of the given rules to trigger the rules * text that matches any of the given rules to trigger the rules
* action. * action.
*/ */
export function pasteRulesPlugin(props: { editor: Editor, rules: PasteRule[] }): Plugin[] { export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }): Plugin[] {
const { editor, rules } = props const { editor, rules } = props
let dragSourceElement: Element | null = null let dragSourceElement: Element | null = null
let isPastedFromProseMirror = false let isPastedFromProseMirror = false

View File

@ -1,12 +1,11 @@
import { Transaction } from 'prosemirror-state' import { Transaction } from '@tiptap/pm/state'
export interface TrackerResult { export interface TrackerResult {
position: number, position: number
deleted: boolean, deleted: boolean
} }
export class Tracker { export class Tracker {
transaction: Transaction transaction: Transaction
currentStep: number currentStep: number
@ -22,9 +21,7 @@ export class Tracker {
const mappedPosition = this.transaction.steps const mappedPosition = this.transaction.steps
.slice(this.currentStep) .slice(this.currentStep)
.reduce((newPosition, step) => { .reduce((newPosition, step) => {
const mapResult = step const mapResult = step.getMap().mapResult(newPosition)
.getMap()
.mapResult(newPosition)
if (mapResult.deleted) { if (mapResult.deleted) {
deleted = true deleted = true
@ -38,5 +35,4 @@ export class Tracker {
deleted, deleted,
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { liftTarget } from 'prosemirror-transform' import { liftTarget } from '@tiptap/pm/transform'
import { RawCommands } from '../types' import { RawCommands } from '../types'

View File

@ -1,4 +1,4 @@
import { createParagraphNear as originalCreateParagraphNear } from 'prosemirror-commands' import { createParagraphNear as originalCreateParagraphNear } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Create a paragraph nearby. * Create a paragraph nearby.
*/ */
createParagraphNear: () => ReturnType, createParagraphNear: () => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { RawCommands } from '../types' import { RawCommands } from '../types'

View File

@ -1,4 +1,4 @@
import { deleteSelection as originalDeleteSelection } from 'prosemirror-commands' import { deleteSelection as originalDeleteSelection } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Delete the selection, if there is one. * Delete the selection, if there is one.
*/ */
deleteSelection: () => ReturnType, deleteSelection: () => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { exitCode as originalExitCode } from 'prosemirror-commands' import { exitCode as originalExitCode } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Exit from a code block. * Exit from a code block.
*/ */
exitCode: () => ReturnType, exitCode: () => ReturnType
} }
} }
} }

View File

@ -1,5 +1,5 @@
import { MarkType } from 'prosemirror-model' import { MarkType } from '@tiptap/pm/model'
import { TextSelection } from 'prosemirror-state' import { TextSelection } from '@tiptap/pm/state'
import { getMarkRange } from '../helpers/getMarkRange' import { getMarkRange } from '../helpers/getMarkRange'
import { getMarkType } from '../helpers/getMarkType' import { getMarkType } from '../helpers/getMarkType'
@ -11,7 +11,10 @@ declare module '@tiptap/core' {
/** /**
* Extends the text selection to the current mark. * Extends the text selection to the current mark.
*/ */
extendMarkRange: (typeOrName: string | MarkType, attributes?: Record<string, any>) => ReturnType, extendMarkRange: (
typeOrName: string | MarkType,
attributes?: Record<string, any>,
) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { ParseOptions } from 'prosemirror-model' import { ParseOptions } from '@tiptap/pm/model'
import { Content, RawCommands } from '../types' import { Content, RawCommands } from '../types'
@ -11,14 +11,18 @@ declare module '@tiptap/core' {
insertContent: ( insertContent: (
value: Content, value: Content,
options?: { options?: {
parseOptions?: ParseOptions, parseOptions?: ParseOptions
updateSelection?: boolean, updateSelection?: boolean
}, },
) => ReturnType, ) => ReturnType
} }
} }
} }
export const insertContent: RawCommands['insertContent'] = (value, options) => ({ tr, commands }) => { export const insertContent: RawCommands['insertContent'] = (value, options) => ({ tr, commands }) => {
return commands.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options) return commands.insertContentAt(
{ from: tr.selection.from, to: tr.selection.to },
value,
options,
)
} }

View File

@ -1,12 +1,8 @@
import { Fragment, Node as ProseMirrorNode, ParseOptions } from 'prosemirror-model' import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model'
import { createNodeFromContent } from '../helpers/createNodeFromContent' import { createNodeFromContent } from '../helpers/createNodeFromContent'
import { selectionToInsertionEnd } from '../helpers/selectionToInsertionEnd' import { selectionToInsertionEnd } from '../helpers/selectionToInsertionEnd'
import { import { Content, Range, RawCommands } from '../types'
Content,
Range,
RawCommands,
} from '../types'
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {
@ -18,10 +14,10 @@ declare module '@tiptap/core' {
position: number | Range, position: number | Range,
value: Content, value: Content,
options?: { options?: {
parseOptions?: ParseOptions, parseOptions?: ParseOptions
updateSelection?: boolean, updateSelection?: boolean
}, },
) => ReturnType, ) => ReturnType
} }
} }
} }
@ -50,27 +46,19 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
return true return true
} }
let { from, to } = typeof position === 'number' let { from, to } = typeof position === 'number' ? { from: position, to: position } : position
? { from: position, to: position }
: position
let isOnlyTextContent = true let isOnlyTextContent = true
let isOnlyBlockContent = true let isOnlyBlockContent = true
const nodes = isFragment(content) const nodes = isFragment(content) ? content : [content]
? content
: [content]
nodes.forEach(node => { nodes.forEach(node => {
// check if added node is valid // check if added node is valid
node.check() node.check()
isOnlyTextContent = isOnlyTextContent isOnlyTextContent = isOnlyTextContent ? node.isText && node.marks.length === 0 : false
? node.isText && node.marks.length === 0
: false
isOnlyBlockContent = isOnlyBlockContent isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false
? node.isBlock
: false
}) })
// check if we can replace the wrapping node by // check if we can replace the wrapping node by
@ -80,9 +68,7 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
// instead of inserting the image below the paragraph // instead of inserting the image below the paragraph
if (from === to && isOnlyBlockContent) { if (from === to && isOnlyBlockContent) {
const { parent } = tr.doc.resolve(from) const { parent } = tr.doc.resolve(from)
const isEmptyTextBlock = parent.isTextblock const isEmptyTextBlock = parent.isTextblock && !parent.type.spec.code && !parent.childCount
&& !parent.type.spec.code
&& !parent.childCount
if (isEmptyTextBlock) { if (isEmptyTextBlock) {
from -= 1 from -= 1

View File

@ -1,6 +1,9 @@
import { import {
joinBackward as originalJoinBackward, joinDown as originalJoinDown, joinForward as originalJoinForward, joinUp as originalJoinUp, joinBackward as originalJoinBackward,
} from 'prosemirror-commands' joinDown as originalJoinDown,
joinForward as originalJoinForward,
joinUp as originalJoinUp,
} from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,25 +13,25 @@ declare module '@tiptap/core' {
/** /**
* Join two nodes Up. * Join two nodes Up.
*/ */
joinUp: () => ReturnType, joinUp: () => ReturnType
} }
joinDown: { joinDown: {
/** /**
* Join two nodes Down. * Join two nodes Down.
*/ */
joinDown: () => ReturnType, joinDown: () => ReturnType
} }
joinBackward: { joinBackward: {
/** /**
* Join two nodes Backwards. * Join two nodes Backwards.
*/ */
joinBackward: () => ReturnType, joinBackward: () => ReturnType
} }
joinForward: { joinForward: {
/** /**
* Join two nodes Forwards. * Join two nodes Forwards.
*/ */
joinForward: () => ReturnType, joinForward: () => ReturnType
} }
} }
} }

View File

@ -1,5 +1,5 @@
import { lift as originalLift } from 'prosemirror-commands' import { lift as originalLift } from '@tiptap/pm/commands'
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { isNodeActive } from '../helpers/isNodeActive' import { isNodeActive } from '../helpers/isNodeActive'
@ -11,7 +11,7 @@ declare module '@tiptap/core' {
/** /**
* Removes an existing wrap. * Removes an existing wrap.
*/ */
lift: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType, lift: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { liftEmptyBlock as originalLiftEmptyBlock } from 'prosemirror-commands' import { liftEmptyBlock as originalLiftEmptyBlock } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'

View File

@ -1,5 +1,5 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { liftListItem as originalLiftListItem } from 'prosemirror-schema-list' import { liftListItem as originalLiftListItem } from '@tiptap/pm/schema-list'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Lift the list item into a wrapping list. * Lift the list item into a wrapping list.
*/ */
liftListItem: (typeOrName: string | NodeType) => ReturnType, liftListItem: (typeOrName: string | NodeType) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { newlineInCode as originalNewlineInCode } from 'prosemirror-commands' import { newlineInCode as originalNewlineInCode } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Add a newline character in code. * Add a newline character in code.
*/ */
newlineInCode: () => ReturnType, newlineInCode: () => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { MarkType, NodeType } from 'prosemirror-model' import { MarkType, NodeType } from '@tiptap/pm/model'
import { getMarkType } from '../helpers/getMarkType' import { getMarkType } from '../helpers/getMarkType'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
@ -12,7 +12,10 @@ declare module '@tiptap/core' {
/** /**
* Resets some node attributes to the default value. * Resets some node attributes to the default value.
*/ */
resetAttributes: (typeOrName: string | NodeType | MarkType, attributes: string | string[]) => ReturnType, resetAttributes: (
typeOrName: string | NodeType | MarkType,
attributes: string | string[],
) => ReturnType
} }
} }
} }
@ -22,9 +25,7 @@ export const resetAttributes: RawCommands['resetAttributes'] = (typeOrName, attr
let markType: MarkType | null = null let markType: MarkType | null = null
const schemaType = getSchemaTypeNameByName( const schemaType = getSchemaTypeNameByName(
typeof typeOrName === 'string' typeof typeOrName === 'string' ? typeOrName : typeOrName.name,
? typeOrName
: typeOrName.name,
state.schema, state.schema,
) )
@ -50,7 +51,11 @@ export const resetAttributes: RawCommands['resetAttributes'] = (typeOrName, attr
if (markType && node.marks.length) { if (markType && node.marks.length) {
node.marks.forEach(mark => { node.marks.forEach(mark => {
if (markType === mark.type) { if (markType === mark.type) {
tr.addMark(pos, pos + node.nodeSize, markType.create(deleteProps(mark.attrs, attributes))) tr.addMark(
pos,
pos + node.nodeSize,
markType.create(deleteProps(mark.attrs, attributes)),
)
} }
}) })
} }

View File

@ -1,4 +1,4 @@
import { selectNodeBackward as originalSelectNodeBackward } from 'prosemirror-commands' import { selectNodeBackward as originalSelectNodeBackward } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Select a node backward. * Select a node backward.
*/ */
selectNodeBackward: () => ReturnType, selectNodeBackward: () => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { selectNodeForward as originalSelectNodeForward } from 'prosemirror-commands' import { selectNodeForward as originalSelectNodeForward } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Select a node forward. * Select a node forward.
*/ */
selectNodeForward: () => ReturnType, selectNodeForward: () => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { selectParentNode as originalSelectParentNode } from 'prosemirror-commands' import { selectParentNode as originalSelectParentNode } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -8,7 +8,7 @@ declare module '@tiptap/core' {
/** /**
* Select the parent node. * Select the parent node.
*/ */
selectParentNode: () => ReturnType, selectParentNode: () => ReturnType
} }
} }
} }

View File

@ -1,6 +1,6 @@
// @ts-ignore // @ts-ignore
// TODO: add types to @types/prosemirror-commands // TODO: add types to @types/prosemirror-commands
import { selectTextblockEnd as originalSelectTextblockEnd } from 'prosemirror-commands' import { selectTextblockEnd as originalSelectTextblockEnd } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Moves the cursor to the end of current text block. * Moves the cursor to the end of current text block.
*/ */
selectTextblockEnd: () => ReturnType, selectTextblockEnd: () => ReturnType
} }
} }
} }

View File

@ -1,6 +1,6 @@
// @ts-ignore // @ts-ignore
// TODO: add types to @types/prosemirror-commands // TODO: add types to @types/prosemirror-commands
import { selectTextblockStart as originalSelectTextblockStart } from 'prosemirror-commands' import { selectTextblockStart as originalSelectTextblockStart } from '@tiptap/pm/commands'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Moves the cursor to the start of current text block. * Moves the cursor to the start of current text block.
*/ */
selectTextblockStart: () => ReturnType, selectTextblockStart: () => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { ParseOptions } from 'prosemirror-model' import { ParseOptions } from '@tiptap/pm/model'
import { createDocument } from '../helpers/createDocument' import { createDocument } from '../helpers/createDocument'
import { Content, RawCommands } from '../types' import { Content, RawCommands } from '../types'
@ -13,7 +13,7 @@ declare module '@tiptap/core' {
content: Content, content: Content,
emitUpdate?: boolean, emitUpdate?: boolean,
parseOptions?: ParseOptions, parseOptions?: ParseOptions,
) => ReturnType, ) => ReturnType
} }
} }
} }
@ -23,8 +23,7 @@ export const setContent: RawCommands['setContent'] = (content, emitUpdate = fals
const document = createDocument(content, editor.schema, parseOptions) const document = createDocument(content, editor.schema, parseOptions)
if (dispatch) { if (dispatch) {
tr.replaceWith(0, doc.content.size, document) tr.replaceWith(0, doc.content.size, document).setMeta('preventUpdate', !emitUpdate)
.setMeta('preventUpdate', !emitUpdate)
} }
return true return true

View File

@ -1,5 +1,5 @@
import { MarkType, ResolvedPos } from 'prosemirror-model' import { MarkType, ResolvedPos } from '@tiptap/pm/model'
import { EditorState, Transaction } from 'prosemirror-state' import { EditorState, Transaction } from '@tiptap/pm/state'
import { isTextSelection } from '../helpers' import { isTextSelection } from '../helpers'
import { getMarkAttributes } from '../helpers/getMarkAttributes' import { getMarkAttributes } from '../helpers/getMarkAttributes'
@ -12,7 +12,7 @@ declare module '@tiptap/core' {
/** /**
* Add a mark with new attributes. * Add a mark with new attributes.
*/ */
setMark: (typeOrName: string | MarkType, attributes?: Record<string, any>) => ReturnType, setMark: (typeOrName: string | MarkType, attributes?: Record<string, any>) => ReturnType
} }
} }
} }
@ -29,13 +29,18 @@ function canSetMark(state: EditorState, tr: Transaction, newMarkType: MarkType)
const currentMarks = state.storedMarks ?? cursor.marks() const currentMarks = state.storedMarks ?? cursor.marks()
// There can be no current marks that exclude the new mark // There can be no current marks that exclude the new mark
return !!newMarkType.isInSet(currentMarks) || !currentMarks.some(mark => mark.type.excludes(newMarkType)) return (
!!newMarkType.isInSet(currentMarks)
|| !currentMarks.some(mark => mark.type.excludes(newMarkType))
)
} }
const { ranges } = selection const { ranges } = selection
return ranges.some(({ $from, $to }) => { return ranges.some(({ $from, $to }) => {
let someNodeSupportsMark = $from.depth === 0 ? state.doc.inlineContent && state.doc.type.allowsMarkType(newMarkType) : false let someNodeSupportsMark = $from.depth === 0
? state.doc.inlineContent && state.doc.type.allowsMarkType(newMarkType)
: false
state.doc.nodesBetween($from.pos, $to.pos, (node, _pos, parent) => { state.doc.nodesBetween($from.pos, $to.pos, (node, _pos, parent) => {
// If we already found a mark that we can enable, return false to bypass the remaining search // If we already found a mark that we can enable, return false to bypass the remaining search
@ -45,7 +50,8 @@ function canSetMark(state: EditorState, tr: Transaction, newMarkType: MarkType)
if (node.isInline) { if (node.isInline) {
const parentAllowsMarkType = !parent || parent.type.allowsMarkType(newMarkType) const parentAllowsMarkType = !parent || parent.type.allowsMarkType(newMarkType)
const currentMarksAllowMarkType = !!newMarkType.isInSet(node.marks) || !node.marks.some(otherMark => otherMark.type.excludes(newMarkType)) const currentMarksAllowMarkType = !!newMarkType.isInSet(node.marks)
|| !node.marks.some(otherMark => otherMark.type.excludes(newMarkType))
someNodeSupportsMark = parentAllowsMarkType && currentMarksAllowMarkType someNodeSupportsMark = parentAllowsMarkType && currentMarksAllowMarkType
} }
@ -54,7 +60,6 @@ function canSetMark(state: EditorState, tr: Transaction, newMarkType: MarkType)
return someNodeSupportsMark return someNodeSupportsMark
}) })
} }
export const setMark: RawCommands['setMark'] = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => { export const setMark: RawCommands['setMark'] = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
const { selection } = tr const { selection } = tr
@ -65,10 +70,12 @@ export const setMark: RawCommands['setMark'] = (typeOrName, attributes = {}) =>
if (empty) { if (empty) {
const oldAttributes = getMarkAttributes(state, type) const oldAttributes = getMarkAttributes(state, type)
tr.addStoredMark(type.create({ tr.addStoredMark(
...oldAttributes, type.create({
...attributes, ...oldAttributes,
})) ...attributes,
}),
)
} else { } else {
ranges.forEach(range => { ranges.forEach(range => {
const from = range.$from.pos const from = range.$from.pos
@ -83,13 +90,16 @@ export const setMark: RawCommands['setMark'] = (typeOrName, attributes = {}) =>
// we know that we have to merge its attributes // we know that we have to merge its attributes
// otherwise we add a fresh new mark // otherwise we add a fresh new mark
if (someHasMark) { if (someHasMark) {
node.marks.forEach(mark => { node.marks.forEach(mark => {
if (type === mark.type) { if (type === mark.type) {
tr.addMark(trimmedFrom, trimmedTo, type.create({ tr.addMark(
...mark.attrs, trimmedFrom,
...attributes, trimmedTo,
})) type.create({
...mark.attrs,
...attributes,
}),
)
} }
}) })
} else { } else {

View File

@ -1,5 +1,5 @@
import { setBlockType } from 'prosemirror-commands' import { setBlockType } from '@tiptap/pm/commands'
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Replace a given range with a node. * Replace a given range with a node.
*/ */
setNode: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType, setNode: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType
} }
} }
} }
@ -25,19 +25,21 @@ export const setNode: RawCommands['setNode'] = (typeOrName, attributes = {}) =>
return false return false
} }
return chain() return (
chain()
// try to convert node to default node if needed // try to convert node to default node if needed
.command(({ commands }) => { .command(({ commands }) => {
const canSetBlock = setBlockType(type, attributes)(state) const canSetBlock = setBlockType(type, attributes)(state)
if (canSetBlock) { if (canSetBlock) {
return true return true
} }
return commands.clearNodes() return commands.clearNodes()
}) })
.command(({ state: updatedState }) => { .command(({ state: updatedState }) => {
return setBlockType(type, attributes)(updatedState, dispatch) return setBlockType(type, attributes)(updatedState, dispatch)
}) })
.run() .run()
)
} }

View File

@ -1,4 +1,4 @@
import { NodeSelection } from 'prosemirror-state' import { NodeSelection } from '@tiptap/pm/state'
import { RawCommands } from '../types' import { RawCommands } from '../types'
import { minMax } from '../utilities/minMax' import { minMax } from '../utilities/minMax'
@ -9,7 +9,7 @@ declare module '@tiptap/core' {
/** /**
* Creates a NodeSelection. * Creates a NodeSelection.
*/ */
setNodeSelection: (position: number) => ReturnType, setNodeSelection: (position: number) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { TextSelection } from 'prosemirror-state' import { TextSelection } from '@tiptap/pm/state'
import { Range, RawCommands } from '../types' import { Range, RawCommands } from '../types'
import { minMax } from '../utilities/minMax' import { minMax } from '../utilities/minMax'
@ -9,7 +9,7 @@ declare module '@tiptap/core' {
/** /**
* Creates a TextSelection. * Creates a TextSelection.
*/ */
setTextSelection: (position: number | Range) => ReturnType, setTextSelection: (position: number | Range) => ReturnType
} }
} }
} }
@ -17,9 +17,7 @@ declare module '@tiptap/core' {
export const setTextSelection: RawCommands['setTextSelection'] = position => ({ tr, dispatch }) => { export const setTextSelection: RawCommands['setTextSelection'] = position => ({ tr, dispatch }) => {
if (dispatch) { if (dispatch) {
const { doc } = tr const { doc } = tr
const { from, to } = typeof position === 'number' const { from, to } = typeof position === 'number' ? { from: position, to: position } : position
? { from: position, to: position }
: position
const minPos = TextSelection.atStart(doc).from const minPos = TextSelection.atStart(doc).from
const maxPos = TextSelection.atEnd(doc).to const maxPos = TextSelection.atEnd(doc).to
const resolvedFrom = minMax(from, minPos, maxPos) const resolvedFrom = minMax(from, minPos, maxPos)

View File

@ -1,5 +1,5 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { sinkListItem as originalSinkListItem } from 'prosemirror-schema-list' import { sinkListItem as originalSinkListItem } from '@tiptap/pm/schema-list'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Sink the list item down into an inner list. * Sink the list item down into an inner list.
*/ */
sinkListItem: (typeOrName: string | NodeType) => ReturnType, sinkListItem: (typeOrName: string | NodeType) => ReturnType
} }
} }
} }

View File

@ -1,13 +1,12 @@
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state' import { EditorState, NodeSelection, TextSelection } from '@tiptap/pm/state'
import { canSplit } from 'prosemirror-transform' import { canSplit } from '@tiptap/pm/transform'
import { defaultBlockAt } from '../helpers/defaultBlockAt' import { defaultBlockAt } from '../helpers/defaultBlockAt'
import { getSplittedAttributes } from '../helpers/getSplittedAttributes' import { getSplittedAttributes } from '../helpers/getSplittedAttributes'
import { RawCommands } from '../types' import { RawCommands } from '../types'
function ensureMarks(state: EditorState, splittableMarks?: string[]) { function ensureMarks(state: EditorState, splittableMarks?: string[]) {
const marks = state.storedMarks const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks())
|| (state.selection.$to.parentOffset && state.selection.$from.marks())
if (marks) { if (marks) {
const filteredMarks = marks.filter(mark => splittableMarks?.includes(mark.type.name)) const filteredMarks = marks.filter(mark => splittableMarks?.includes(mark.type.name))
@ -22,16 +21,13 @@ declare module '@tiptap/core' {
/** /**
* Forks a new node from an existing node. * Forks a new node from an existing node.
*/ */
splitBlock: (options?: { keepMarks?: boolean }) => ReturnType, splitBlock: (options?: { keepMarks?: boolean }) => ReturnType
} }
} }
} }
export const splitBlock: RawCommands['splitBlock'] = ({ keepMarks = true } = {}) => ({ export const splitBlock: RawCommands['splitBlock'] = ({ keepMarks = true } = {}) => ({
tr, tr, state, dispatch, editor,
state,
dispatch,
editor,
}) => { }) => {
const { selection, doc } = tr const { selection, doc } = tr
const { $from, $to } = selection const { $from, $to } = selection
@ -74,37 +70,36 @@ export const splitBlock: RawCommands['splitBlock'] = ({ keepMarks = true } = {})
: defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1))) : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
let types = atEnd && deflt let types = atEnd && deflt
? [{ ? [
type: deflt, {
attrs: newAttributes, type: deflt,
}] attrs: newAttributes,
},
]
: undefined : undefined
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types) let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
if ( if (
!types !types
&& !can && !can
&& canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined) && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)
) { ) {
can = true can = true
types = deflt types = deflt
? [{ ? [
type: deflt, {
attrs: newAttributes, type: deflt,
}] attrs: newAttributes,
},
]
: undefined : undefined
} }
if (can) { if (can) {
tr.split(tr.mapping.map($from.pos), 1, types) tr.split(tr.mapping.map($from.pos), 1, types)
if ( if (deflt && !atEnd && !$from.parentOffset && $from.parent.type !== deflt) {
deflt
&& !atEnd
&& !$from.parentOffset
&& $from.parent.type !== deflt
) {
const first = tr.mapping.map($from.before()) const first = tr.mapping.map($from.before())
const $first = tr.doc.resolve(first) const $first = tr.doc.resolve(first)

View File

@ -1,11 +1,8 @@
import { import {
Fragment, Fragment, Node as ProseMirrorNode, NodeType, Slice,
Node as ProseMirrorNode, } from '@tiptap/pm/model'
NodeType, import { TextSelection } from '@tiptap/pm/state'
Slice, import { canSplit } from '@tiptap/pm/transform'
} from 'prosemirror-model'
import { TextSelection } from 'prosemirror-state'
import { canSplit } from 'prosemirror-transform'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { getSplittedAttributes } from '../helpers/getSplittedAttributes' import { getSplittedAttributes } from '../helpers/getSplittedAttributes'
@ -17,7 +14,7 @@ declare module '@tiptap/core' {
/** /**
* Splits one list item into two list items. * Splits one list item into two list items.
*/ */
splitListItem: (typeOrName: string | NodeType) => ReturnType, splitListItem: (typeOrName: string | NodeType) => ReturnType
} }
} }
} }
@ -30,7 +27,7 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
// @ts-ignore // @ts-ignore
// eslint-disable-next-line // eslint-disable-next-line
const node: ProseMirrorNode = state.selection.node const node: ProseMirrorNode = state.selection.node
if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) { if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) {
return false return false
@ -50,8 +47,8 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
// command handle lifting. // command handle lifting.
if ( if (
$from.depth === 2 $from.depth === 2
|| $from.node(-3).type !== type || $from.node(-3).type !== type
|| $from.index(-2) !== $from.node(-2).childCount - 1 || $from.index(-2) !== $from.node(-2).childCount - 1
) { ) {
return false return false
} }
@ -59,11 +56,7 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
if (dispatch) { if (dispatch) {
let wrap = Fragment.empty let wrap = Fragment.empty
// eslint-disable-next-line // eslint-disable-next-line
const depthBefore = $from.index(-1) const depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3
? 1
: $from.index(-2)
? 2
: 3
// Build a fragment containing empty versions of the structure // Build a fragment containing empty versions of the structure
// from the outer list item to the parent node of the cursor // from the outer list item to the parent node of the cursor
@ -72,11 +65,7 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
} }
// eslint-disable-next-line // eslint-disable-next-line
const depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount const depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1 : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3
? 1
: $from.indexAfter(-2) < $from.node(-3).childCount
? 2
: 3
// Add a second list item with an empty default start node // Add a second list item with an empty default start node
const newNextTypeAttributes = getSplittedAttributes( const newNextTypeAttributes = getSplittedAttributes(
@ -114,9 +103,7 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
return true return true
} }
const nextType = $to.pos === $from.end() const nextType = $to.pos === $from.end() ? grandParent.contentMatchAt(0).defaultType : null
? grandParent.contentMatchAt(0).defaultType
: null
const newTypeAttributes = getSplittedAttributes( const newTypeAttributes = getSplittedAttributes(
extensionAttributes, extensionAttributes,
@ -132,7 +119,10 @@ export const splitListItem: RawCommands['splitListItem'] = typeOrName => ({
tr.delete($from.pos, $to.pos) tr.delete($from.pos, $to.pos)
const types = nextType const types = nextType
? [{ type, attrs: newTypeAttributes }, { type: nextType, attrs: newNextTypeAttributes }] ? [
{ type, attrs: newTypeAttributes },
{ type: nextType, attrs: newNextTypeAttributes },
]
: [{ type, attrs: newTypeAttributes }] : [{ type, attrs: newTypeAttributes }]
if (!canSplit(tr.doc, $from.pos, 2)) { if (!canSplit(tr.doc, $from.pos, 2)) {

View File

@ -1,6 +1,6 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { Transaction } from 'prosemirror-state' import { Transaction } from '@tiptap/pm/state'
import { canJoin } from 'prosemirror-transform' import { canJoin } from '@tiptap/pm/transform'
import { findParentNode } from '../helpers/findParentNode' import { findParentNode } from '../helpers/findParentNode'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
@ -21,8 +21,7 @@ const joinListBackwards = (tr: Transaction, listType: NodeType): boolean => {
} }
const nodeBefore = tr.doc.nodeAt(before) const nodeBefore = tr.doc.nodeAt(before)
const canJoinBackwards = list.node.type === nodeBefore?.type const canJoinBackwards = list.node.type === nodeBefore?.type && canJoin(tr.doc, list.pos)
&& canJoin(tr.doc, list.pos)
if (!canJoinBackwards) { if (!canJoinBackwards) {
return true return true
@ -47,8 +46,7 @@ const joinListForwards = (tr: Transaction, listType: NodeType): boolean => {
} }
const nodeAfter = tr.doc.nodeAt(after) const nodeAfter = tr.doc.nodeAt(after)
const canJoinForwards = list.node.type === nodeAfter?.type const canJoinForwards = list.node.type === nodeAfter?.type && canJoin(tr.doc, after)
&& canJoin(tr.doc, after)
if (!canJoinForwards) { if (!canJoinForwards) {
return true return true
@ -65,7 +63,10 @@ declare module '@tiptap/core' {
/** /**
* Toggle between different list types. * Toggle between different list types.
*/ */
toggleList: (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType) => ReturnType, toggleList: (
listTypeOrName: string | NodeType,
itemTypeOrName: string | NodeType,
) => ReturnType
} }
} }
} }
@ -95,8 +96,8 @@ export const toggleList: RawCommands['toggleList'] = (listTypeOrName, itemTypeOr
// change list type // change list type
if ( if (
isList(parentList.node.type.name, extensions) isList(parentList.node.type.name, extensions)
&& listType.validContent(parentList.node.content) && listType.validContent(parentList.node.content)
&& dispatch && dispatch
) { ) {
return chain() return chain()
.command(() => { .command(() => {
@ -110,19 +111,21 @@ export const toggleList: RawCommands['toggleList'] = (listTypeOrName, itemTypeOr
} }
} }
return chain() return (
chain()
// try to convert node to default node if needed // try to convert node to default node if needed
.command(() => { .command(() => {
const canWrapInList = can().wrapInList(listType) const canWrapInList = can().wrapInList(listType)
if (canWrapInList) { if (canWrapInList) {
return true return true
} }
return commands.clearNodes() return commands.clearNodes()
}) })
.wrapInList(listType) .wrapInList(listType)
.command(() => joinListBackwards(tr, listType)) .command(() => joinListBackwards(tr, listType))
.command(() => joinListForwards(tr, listType)) .command(() => joinListForwards(tr, listType))
.run() .run()
)
} }

View File

@ -1,4 +1,4 @@
import { MarkType } from 'prosemirror-model' import { MarkType } from '@tiptap/pm/model'
import { getMarkType } from '../helpers/getMarkType' import { getMarkType } from '../helpers/getMarkType'
import { isMarkActive } from '../helpers/isMarkActive' import { isMarkActive } from '../helpers/isMarkActive'
@ -17,9 +17,9 @@ declare module '@tiptap/core' {
/** /**
* Removes the mark even across the current selection. Defaults to `false`. * Removes the mark even across the current selection. Defaults to `false`.
*/ */
extendEmptyMarkRange?: boolean, extendEmptyMarkRange?: boolean
}, },
) => ReturnType, ) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { isNodeActive } from '../helpers/isNodeActive' import { isNodeActive } from '../helpers/isNodeActive'
@ -10,7 +10,11 @@ declare module '@tiptap/core' {
/** /**
* Toggle a node with another node. * Toggle a node with another node.
*/ */
toggleNode: (typeOrName: string | NodeType, toggleTypeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType, toggleNode: (
typeOrName: string | NodeType,
toggleTypeOrName: string | NodeType,
attributes?: Record<string, any>,
) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { isNodeActive } from '../helpers/isNodeActive' import { isNodeActive } from '../helpers/isNodeActive'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Wraps nodes in another node, or removes an existing wrap. * Wraps nodes in another node, or removes an existing wrap.
*/ */
toggleWrap: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType, toggleWrap: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { MarkType } from 'prosemirror-model' import { MarkType } from '@tiptap/pm/model'
import { getMarkRange } from '../helpers/getMarkRange' import { getMarkRange } from '../helpers/getMarkRange'
import { getMarkType } from '../helpers/getMarkType' import { getMarkType } from '../helpers/getMarkType'
@ -16,9 +16,9 @@ declare module '@tiptap/core' {
/** /**
* Removes the mark even across the current selection. Defaults to `false`. * Removes the mark even across the current selection. Defaults to `false`.
*/ */
extendEmptyMarkRange?: boolean, extendEmptyMarkRange?: boolean
}, },
) => ReturnType, ) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { MarkType, NodeType } from 'prosemirror-model' import { MarkType, NodeType } from '@tiptap/pm/model'
import { getMarkType } from '../helpers/getMarkType' import { getMarkType } from '../helpers/getMarkType'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
@ -11,7 +11,10 @@ declare module '@tiptap/core' {
/** /**
* Update attributes of a node or mark. * Update attributes of a node or mark.
*/ */
updateAttributes: (typeOrName: string | NodeType | MarkType, attributes: Record<string, any>) => ReturnType, updateAttributes: (
typeOrName: string | NodeType | MarkType,
attributes: Record<string, any>,
) => ReturnType
} }
} }
} }
@ -21,9 +24,7 @@ export const updateAttributes: RawCommands['updateAttributes'] = (typeOrName, at
let markType: MarkType | null = null let markType: MarkType | null = null
const schemaType = getSchemaTypeNameByName( const schemaType = getSchemaTypeNameByName(
typeof typeOrName === 'string' typeof typeOrName === 'string' ? typeOrName : typeOrName.name,
? typeOrName
: typeOrName.name,
state.schema, state.schema,
) )
@ -58,10 +59,14 @@ export const updateAttributes: RawCommands['updateAttributes'] = (typeOrName, at
const trimmedFrom = Math.max(pos, from) const trimmedFrom = Math.max(pos, from)
const trimmedTo = Math.min(pos + node.nodeSize, to) const trimmedTo = Math.min(pos + node.nodeSize, to)
tr.addMark(trimmedFrom, trimmedTo, markType.create({ tr.addMark(
...mark.attrs, trimmedFrom,
...attributes, trimmedTo,
})) markType.create({
...mark.attrs,
...attributes,
}),
)
} }
}) })
} }

View File

@ -1,5 +1,5 @@
import { wrapIn as originalWrapIn } from 'prosemirror-commands' import { wrapIn as originalWrapIn } from '@tiptap/pm/commands'
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Wraps nodes in another node. * Wraps nodes in another node.
*/ */
wrapIn: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType, wrapIn: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType
} }
} }
} }

View File

@ -1,5 +1,5 @@
import { NodeType } from 'prosemirror-model' import { NodeType } from '@tiptap/pm/model'
import { wrapInList as originalWrapInList } from 'prosemirror-schema-list' import { wrapInList as originalWrapInList } from '@tiptap/pm/schema-list'
import { getNodeType } from '../helpers/getNodeType' import { getNodeType } from '../helpers/getNodeType'
import { RawCommands } from '../types' import { RawCommands } from '../types'
@ -10,7 +10,7 @@ declare module '@tiptap/core' {
/** /**
* Wrap a node in a list. * Wrap a node in a list.
*/ */
wrapInList: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType, wrapInList: (typeOrName: string | NodeType, attributes?: Record<string, any>) => ReturnType
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Extension } from '../Extension' import { Extension } from '../Extension'
import { getTextBetween } from '../helpers/getTextBetween' import { getTextBetween } from '../helpers/getTextBetween'

View File

@ -1,4 +1,4 @@
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Extension } from '../Extension' import { Extension } from '../Extension'

View File

@ -1,4 +1,4 @@
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Extension } from '../Extension' import { Extension } from '../Extension'

View File

@ -1,4 +1,4 @@
import { Plugin, PluginKey, Selection } from 'prosemirror-state' import { Plugin, PluginKey, Selection } from '@tiptap/pm/state'
import { CommandManager } from '../CommandManager' import { CommandManager } from '../CommandManager'
import { Extension } from '../Extension' import { Extension } from '../Extension'
@ -19,12 +19,7 @@ export const Keymap = Extension.create({
const { pos, parent } = $anchor const { pos, parent } = $anchor
const isAtStart = Selection.atStart(doc).from === pos const isAtStart = Selection.atStart(doc).from === pos
if ( if (!empty || !isAtStart || !parent.type.isTextblock || parent.textContent.length) {
!empty
|| !isAtStart
|| !parent.type.isTextblock
|| parent.textContent.length
) {
return false return false
} }

View File

@ -1,4 +1,4 @@
import { Plugin, PluginKey } from 'prosemirror-state' import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Extension } from '../Extension' import { Extension } from '../Extension'

View File

@ -1,11 +1,14 @@
import { Node as ProseMirrorNode } from 'prosemirror-model' import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Transaction } from 'prosemirror-state' import { Transaction } from '@tiptap/pm/state'
import { Transform } from 'prosemirror-transform' import { Transform } from '@tiptap/pm/transform'
/** /**
* Returns a new `Transform` based on all steps of the passed transactions. * Returns a new `Transform` based on all steps of the passed transactions.
*/ */
export function combineTransactionSteps(oldDoc: ProseMirrorNode, transactions: Transaction[]): Transform { export function combineTransactionSteps(
oldDoc: ProseMirrorNode,
transactions: Transaction[],
): Transform {
const transform = new Transform(oldDoc) const transform = new Transform(oldDoc)
transactions.forEach(transaction => { transactions.forEach(transaction => {

View File

@ -1,8 +1,8 @@
import { EditorState, Transaction } from 'prosemirror-state' import { EditorState, Transaction } from '@tiptap/pm/state'
export function createChainableState(config: { export function createChainableState(config: {
transaction: Transaction, transaction: Transaction
state: EditorState, state: EditorState
}): EditorState { }): EditorState {
const { state, transaction } = config const { state, transaction } = config
let { selection } = transaction let { selection } = transaction

View File

@ -1,4 +1,4 @@
import { Node as ProseMirrorNode, ParseOptions, Schema } from 'prosemirror-model' import { Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'
import { Content } from '../types' import { Content } from '../types'
import { createNodeFromContent } from './createNodeFromContent' import { createNodeFromContent } from './createNodeFromContent'

View File

@ -4,14 +4,14 @@ import {
Node as ProseMirrorNode, Node as ProseMirrorNode,
ParseOptions, ParseOptions,
Schema, Schema,
} from 'prosemirror-model' } from '@tiptap/pm/model'
import { Content } from '../types' import { Content } from '../types'
import { elementFromString } from '../utilities/elementFromString' import { elementFromString } from '../utilities/elementFromString'
export type CreateNodeFromContentOptions = { export type CreateNodeFromContentOptions = {
slice?: boolean, slice?: boolean
parseOptions?: ParseOptions, parseOptions?: ParseOptions
} }
export function createNodeFromContent( export function createNodeFromContent(
@ -33,13 +33,7 @@ export function createNodeFromContent(
return schema.nodeFromJSON(content) return schema.nodeFromJSON(content)
} catch (error) { } catch (error) {
console.warn( console.warn('[tiptap warn]: Invalid content.', 'Passed value:', content, 'Error:', error)
'[tiptap warn]: Invalid content.',
'Passed value:',
content,
'Error:',
error,
)
return createNodeFromContent('', schema, options) return createNodeFromContent('', schema, options)
} }

Some files were not shown because too many files have changed in this diff Show More