diff --git a/package-lock.json b/package-lock.json index bf66efc1bb..feab588169 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4980,6 +4980,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.7", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", @@ -4987,6 +4993,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -14684,16 +14706,6 @@ "w3c-keyname": "^2.2.0" } }, - "node_modules/prosemirror-markdown": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.0.tgz", - "integrity": "sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==", - "license": "MIT", - "dependencies": { - "markdown-it": "^14.0.0", - "prosemirror-model": "^1.20.0" - } - }, "node_modules/prosemirror-menu": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", @@ -14746,19 +14758,6 @@ "prosemirror-view": "^1.27.0" } }, - "node_modules/prosemirror-tables": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.4.0.tgz", - "integrity": "sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.1.2", - "prosemirror-model": "^1.8.1", - "prosemirror-state": "^1.3.1", - "prosemirror-transform": "^1.2.1", - "prosemirror-view": "^1.13.3" - } - }, "node_modules/prosemirror-trailing-node": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", @@ -19714,28 +19713,92 @@ "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.0", + "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.0", + "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.22.3", + "prosemirror-model": "^1.23.0", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.4.0", + "prosemirror-tables": "^1.6.1", "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.34.3" + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.36.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" } }, + "packages/pm/node_modules/prosemirror-commands": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz", + "integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "packages/pm/node_modules/prosemirror-markdown": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz", + "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.20.0" + } + }, + "packages/pm/node_modules/prosemirror-model": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "packages/pm/node_modules/prosemirror-tables": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz", + "integrity": "sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "packages/pm/node_modules/prosemirror-transform": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "packages/pm/node_modules/prosemirror-view": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.36.0.tgz", + "integrity": "sha512-U0GQd5yFvV5qUtT41X1zCQfbw14vkbbKwLlQXhdylEmgpYVHkefXYcC4HHwWOfZa3x6Y8wxDLUBv7dxN5XQ3nA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "packages/react": { "name": "@tiptap/react", "version": "2.9.1", diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index d3378a47b0..dd93c5def6 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -400,6 +400,7 @@ export class Editor extends EventEmitter { } this.view.setProps({ + markViews: this.extensionManager.markViews, nodeViews: this.extensionManager.nodeViews, }) } diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index a0fda44be2..db42d715ef 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -1,7 +1,7 @@ import { keymap } from '@tiptap/pm/keymap' import { Schema } from '@tiptap/pm/model' import { Plugin } from '@tiptap/pm/state' -import { NodeViewConstructor } from '@tiptap/pm/view' +import { MarkViewConstructor, NodeViewConstructor } from '@tiptap/pm/view' import type { Editor } from './Editor.js' import { getAttributesFromExtensions } from './helpers/getAttributesFromExtensions.js' @@ -12,7 +12,7 @@ import { getSchemaByResolvedExtensions } from './helpers/getSchemaByResolvedExte import { getSchemaTypeByName } from './helpers/getSchemaTypeByName.js' import { isExtensionRulesEnabled } from './helpers/isExtensionRulesEnabled.js' import { splitExtensions } from './helpers/splitExtensions.js' -import type { NodeConfig } from './index.js' +import { type MarkConfig, type NodeConfig, getMarkType } from './index.js' import { InputRule, inputRulesPlugin } from './InputRule.js' import { Mark } from './Mark.js' import { PasteRule, pasteRulesPlugin } from './PasteRule.js' @@ -317,6 +317,58 @@ export class ExtensionManager { ) } + get markViews(): Record { + const { editor } = this + const { markExtensions } = splitExtensions(this.extensions) + + return Object.fromEntries( + markExtensions + .filter(extension => !!getExtensionField(extension, 'addMarkView')) + .map(extension => { + const extensionAttributes = this.attributes.filter( + attribute => attribute.type === extension.name, + ) + const context = { + name: extension.name, + options: extension.options, + storage: extension.storage, + editor, + type: getMarkType(extension.name, this.schema), + } + const addMarkView = getExtensionField( + extension, + 'addMarkView', + context, + ) + + if (!addMarkView) { + return [] + } + + const markView: MarkViewConstructor = ( + mark, + view, + inline, + ) => { + const HTMLAttributes = getRenderedAttributes(mark, extensionAttributes) + + return addMarkView()({ + // pass-through + mark, + view, + inline, + // tiptap-specific + editor, + extension, + HTMLAttributes, + }) + } + + return [extension.name, markView] + }), + ) + } + /** * Go through all extensions, create extension storages & setup marks * & bind editor event listener. diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts index ea57edd7f3..db39eed9fb 100644 --- a/packages/core/src/Mark.ts +++ b/packages/core/src/Mark.ts @@ -15,6 +15,7 @@ import { Extensions, GlobalAttributes, KeyboardShortcutCommand, + MarkViewRenderer, ParentConfig, RawCommands, } from './types.js' @@ -410,6 +411,20 @@ declare module '@tiptap/core' { }) => void) | null + /** + * Node View + */ + addMarkView?: + | ((this: { + name: string + options: Options + storage: Storage + editor: Editor + type: MarkType + parent: ParentConfig>['addMarkView'] + }) => MarkViewRenderer) + | null + /** * Keep mark after split node */ diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 3077058d72..a9ca9595dd 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -11,6 +11,8 @@ import { DecorationAttrs, EditorProps, EditorView, + MarkView, + MarkViewConstructor, NodeView, NodeViewConstructor, } from '@tiptap/pm/view' @@ -305,6 +307,37 @@ export interface NodeViewRendererProps { export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView; +export interface MarkViewRendererProps { + // pass-through from prosemirror + /** + * The node that is being rendered. + */ + mark: Parameters[0]; + /** + * The editor's view. + */ + view: Parameters[1]; + /** + * indicates whether the mark's content is inline + */ + inline: Parameters[2]; + // tiptap-specific + /** + * The editor instance. + */ + editor: Editor; + /** + * The extension that is responsible for the mark. + */ + extension: Mark; + /** + * The HTML attributes that should be added to the mark's DOM element. + */ + HTMLAttributes: Record; +} + +export type MarkViewRenderer = (props: MarkViewRendererProps) => MarkView; + export type AnyCommands = Record Command>; export type UnionCommands = UnionToIntersection< diff --git a/packages/pm/package.json b/packages/pm/package.json index d4bb8b0f3b..45235e76e8 100644 --- a/packages/pm/package.json +++ b/packages/pm/package.json @@ -128,22 +128,22 @@ "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.0", + "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.0", + "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.22.3", + "prosemirror-model": "^1.23.0", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.4.0", + "prosemirror-tables": "^1.6.1", "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.34.3" + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.36.0" }, "repository": { "type": "git",