Skip to content

Commit

Permalink
feat(core): add support for markviews
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 committed Nov 8, 2024
1 parent 4ee59c1 commit a36b9f0
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/core/src/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ export class Editor extends EventEmitter<EditorEvents> {
}

this.view.setProps({
markViews: this.extensionManager.markViews,
nodeViews: this.extensionManager.nodeViews,
})
}
Expand Down
56 changes: 54 additions & 2 deletions packages/core/src/ExtensionManager.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -317,6 +317,58 @@ export class ExtensionManager {
)
}

get markViews(): Record<string, MarkViewConstructor> {
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<MarkConfig['addMarkView']>(
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.
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/Mark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Extensions,
GlobalAttributes,
KeyboardShortcutCommand,
MarkViewRenderer,
ParentConfig,
RawCommands,
} from './types.js'
Expand Down Expand Up @@ -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<MarkConfig<Options, Storage>>['addMarkView']
}) => MarkViewRenderer)
| null

/**
* Keep mark after split node
*/
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DecorationAttrs,
EditorProps,
EditorView,
MarkViewConstructor,
NodeView,
NodeViewConstructor,
} from '@tiptap/pm/view'
Expand Down Expand Up @@ -305,6 +306,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<MarkViewConstructor>[0];
/**
* The editor's view.
*/
view: Parameters<MarkViewConstructor>[1];
/**
* indicates whether the mark's content is inline
*/
inline: Parameters<MarkViewConstructor>[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<string, any>;
}

export type MarkViewRenderer = (props: MarkViewRendererProps) => ReturnType<MarkViewConstructor>;

export type AnyCommands = Record<string, (...args: any[]) => Command>;

export type UnionCommands<T = Command> = UnionToIntersection<
Expand Down

0 comments on commit a36b9f0

Please sign in to comment.