mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-27 14:59:27 +08:00
feat(core): beginnings of a decorations API
This commit is contained in:
parent
f7453a3292
commit
36489ba873
557
packages/core/src/Decoration.ts
Normal file
557
packages/core/src/Decoration.ts
Normal file
@ -0,0 +1,557 @@
|
||||
import { Plugin, Transaction } from '@tiptap/pm/state'
|
||||
|
||||
import { Editor } from './Editor.js'
|
||||
import { getExtensionField } from './helpers/getExtensionField.js'
|
||||
import { DecorationConfig } from './index.js'
|
||||
import { InputRule } from './InputRule.js'
|
||||
import { Mark } from './Mark.js'
|
||||
import { Node } from './Node.js'
|
||||
import { PasteRule } from './PasteRule.js'
|
||||
import {
|
||||
AnyConfig,
|
||||
Extensions,
|
||||
GlobalAttributes,
|
||||
KeyboardShortcutCommand,
|
||||
ParentConfig,
|
||||
RawCommands,
|
||||
} from './types.js'
|
||||
import { callOrReturn } from './utilities/callOrReturn.js'
|
||||
import { mergeDeep } from './utilities/mergeDeep.js'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface DecorationConfig<Options = any, Storage = any, Attrs = any, Spec = any> {
|
||||
// @ts-ignore - this is a dynamic key
|
||||
[key: string]: any;
|
||||
|
||||
/**
|
||||
* The extension name - this must be unique.
|
||||
* It will be used to identify the extension.
|
||||
*
|
||||
* @example 'myDecoration'
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The priority of your extension. The higher, the earlier it will be called
|
||||
* and will take precedence over other extensions with a lower priority.
|
||||
* @default 100
|
||||
* @example 101
|
||||
*/
|
||||
priority?: number;
|
||||
|
||||
/**
|
||||
* The default options for this extension.
|
||||
* @example
|
||||
* defaultOptions: {
|
||||
* myOption: 'foo',
|
||||
* myOtherOption: 10,
|
||||
* }
|
||||
*/
|
||||
defaultOptions?: Options;
|
||||
|
||||
/**
|
||||
* This method will add options to this extension
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#settings
|
||||
* @example
|
||||
* addOptions() {
|
||||
* return {
|
||||
* myOption: 'foo',
|
||||
* myOtherOption: 10,
|
||||
* }
|
||||
*/
|
||||
addOptions?: (this: {
|
||||
name: string;
|
||||
parent: Exclude<ParentConfig<DecorationConfig<Options, Storage>>['addOptions'], undefined>;
|
||||
}) => Options;
|
||||
|
||||
/**
|
||||
* The default storage this extension can save data to.
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#storage
|
||||
* @example
|
||||
* defaultStorage: {
|
||||
* prefetchedUsers: [],
|
||||
* loading: false,
|
||||
* }
|
||||
*/
|
||||
addStorage?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
parent: Exclude<ParentConfig<DecorationConfig<Options, Storage>>['addStorage'], undefined>;
|
||||
}) => Storage;
|
||||
|
||||
/**
|
||||
* This function adds globalAttributes to specific nodes.
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#global-attributes
|
||||
* @example
|
||||
* addGlobalAttributes() {
|
||||
* return [
|
||||
* {
|
||||
// Extend the following extensions
|
||||
* types: [
|
||||
* 'heading',
|
||||
* 'paragraph',
|
||||
* ],
|
||||
* // … with those attributes
|
||||
* attributes: {
|
||||
* textAlign: {
|
||||
* default: 'left',
|
||||
* renderHTML: attributes => ({
|
||||
* style: `text-align: ${attributes.textAlign}`,
|
||||
* }),
|
||||
* parseHTML: element => element.style.textAlign || 'left',
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
addGlobalAttributes?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
extensions: (Node | Mark)[];
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addGlobalAttributes'];
|
||||
}) => GlobalAttributes;
|
||||
|
||||
/**
|
||||
* This function adds commands to the editor
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#commands
|
||||
* @example
|
||||
* addCommands() {
|
||||
* return {
|
||||
* myCommand: () => ({ chain }) => chain().setMark('type', 'foo').run(),
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
addCommands?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addCommands'];
|
||||
}) => Partial<RawCommands>;
|
||||
|
||||
/**
|
||||
* This function registers keyboard shortcuts.
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#keyboard-shortcuts
|
||||
* @example
|
||||
* addKeyboardShortcuts() {
|
||||
* return {
|
||||
* 'Mod-l': () => this.editor.commands.toggleBulletList(),
|
||||
* }
|
||||
* },
|
||||
*/
|
||||
addKeyboardShortcuts?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addKeyboardShortcuts'];
|
||||
}) => {
|
||||
[key: string]: KeyboardShortcutCommand;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function adds input rules to the editor.
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#input-rules
|
||||
* @example
|
||||
* addInputRules() {
|
||||
* return [
|
||||
* markInputRule({
|
||||
* find: inputRegex,
|
||||
* type: this.type,
|
||||
* }),
|
||||
* ]
|
||||
* },
|
||||
*/
|
||||
addInputRules?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addInputRules'];
|
||||
}) => InputRule[];
|
||||
|
||||
/**
|
||||
* This function adds paste rules to the editor.
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#paste-rules
|
||||
* @example
|
||||
* addPasteRules() {
|
||||
* return [
|
||||
* markPasteRule({
|
||||
* find: pasteRegex,
|
||||
* type: this.type,
|
||||
* }),
|
||||
* ]
|
||||
* },
|
||||
*/
|
||||
addPasteRules?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addPasteRules'];
|
||||
}) => PasteRule[];
|
||||
|
||||
/**
|
||||
* This function adds Prosemirror plugins to the editor
|
||||
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#prosemirror-plugins
|
||||
* @example
|
||||
* addProseMirrorPlugins() {
|
||||
* return [
|
||||
* customPlugin(),
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
addProseMirrorPlugins?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addProseMirrorPlugins'];
|
||||
}) => Plugin[];
|
||||
|
||||
/**
|
||||
* This function adds additional extensions to the editor. This is useful for
|
||||
* building extension kits.
|
||||
* @example
|
||||
* addExtensions() {
|
||||
* return [
|
||||
* BulletList,
|
||||
* OrderedList,
|
||||
* ListItem
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
addExtensions?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addExtensions'];
|
||||
}) => Extensions;
|
||||
|
||||
/**
|
||||
* This function extends the schema of the node.
|
||||
* @example
|
||||
* extendNodeSchema() {
|
||||
* return {
|
||||
* group: 'inline',
|
||||
* selectable: false,
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
extendNodeSchema?:
|
||||
| ((
|
||||
this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['extendNodeSchema'];
|
||||
},
|
||||
extension: Node
|
||||
) => Record<string, any>)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* This function extends the schema of the mark.
|
||||
* @example
|
||||
* extendMarkSchema() {
|
||||
* return {
|
||||
* group: 'inline',
|
||||
* selectable: false,
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
extendMarkSchema?:
|
||||
| ((
|
||||
this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['extendMarkSchema'];
|
||||
},
|
||||
extension: Mark
|
||||
) => Record<string, any>)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* This function extends the schema of the decoration.
|
||||
* @example
|
||||
* extendMarkSchema() {
|
||||
* return {
|
||||
* group: 'inline',
|
||||
* selectable: false,
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
extendDecorationSchema?:
|
||||
| ((
|
||||
this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['extendDecorationSchema'];
|
||||
},
|
||||
extension: Decoration
|
||||
) => Record<string, any>)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The editor is not ready yet.
|
||||
*/
|
||||
onBeforeCreate?:
|
||||
| ((this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onBeforeCreate'];
|
||||
}) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The editor is ready.
|
||||
*/
|
||||
onCreate?:
|
||||
| ((this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onCreate'];
|
||||
}) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The content has changed.
|
||||
*/
|
||||
onUpdate?:
|
||||
| ((this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onUpdate'];
|
||||
}) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The selection has changed.
|
||||
*/
|
||||
onSelectionUpdate?:
|
||||
| ((this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onSelectionUpdate'];
|
||||
}) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The editor state has changed.
|
||||
*/
|
||||
onTransaction?:
|
||||
| ((
|
||||
this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onTransaction'];
|
||||
},
|
||||
props: {
|
||||
editor: Editor;
|
||||
transaction: Transaction;
|
||||
}
|
||||
) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The editor is focused.
|
||||
*/
|
||||
onFocus?:
|
||||
| ((
|
||||
this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onFocus'];
|
||||
},
|
||||
props: {
|
||||
event: FocusEvent;
|
||||
}
|
||||
) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The editor isn’t focused anymore.
|
||||
*/
|
||||
onBlur?:
|
||||
| ((
|
||||
this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onBlur'];
|
||||
},
|
||||
props: {
|
||||
event: FocusEvent;
|
||||
}
|
||||
) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* The editor is destroyed.
|
||||
*/
|
||||
onDestroy?:
|
||||
| ((this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
editor: Editor;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['onDestroy'];
|
||||
}) => void)
|
||||
| null;
|
||||
|
||||
/**
|
||||
* Add attributes to the node
|
||||
* @example addAttributes: () => ({ class: 'foo' })
|
||||
*/
|
||||
addAttributes?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addAttributes'];
|
||||
editor?: Editor;
|
||||
}) => Partial<Attrs>;
|
||||
|
||||
/**
|
||||
* Add attributes to the node
|
||||
* @example addSpec: () => ({ ctx: 'foo' })
|
||||
*/
|
||||
addSpec?: (this: {
|
||||
name: string;
|
||||
options: Options;
|
||||
storage: Storage;
|
||||
parent: ParentConfig<DecorationConfig<Options, Storage>>['addAttributes'];
|
||||
editor?: Editor;
|
||||
}) => Partial<Spec>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Extension class is the base class for all extensions.
|
||||
* @see https://tiptap.dev/api/extensions#create-a-new-extension
|
||||
*/
|
||||
export class Decoration<Options = any, Storage = any, Attrs = any, Spec = any> {
|
||||
type = 'decoration'
|
||||
|
||||
name = 'decoration'
|
||||
|
||||
parent: Decoration | null = null
|
||||
|
||||
child: Decoration | null = null
|
||||
|
||||
options: Options
|
||||
|
||||
storage: Storage
|
||||
|
||||
config: DecorationConfig = {
|
||||
name: this.name,
|
||||
defaultOptions: {},
|
||||
}
|
||||
|
||||
constructor(config: Partial<DecorationConfig<Options, Storage, Attrs, Spec>> = {}) {
|
||||
this.config = {
|
||||
...this.config,
|
||||
...config,
|
||||
}
|
||||
|
||||
this.name = this.config.name
|
||||
|
||||
if (config.defaultOptions && Object.keys(config.defaultOptions).length > 0) {
|
||||
console.warn(
|
||||
`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
this.options = this.config.defaultOptions
|
||||
|
||||
if (this.config.addOptions) {
|
||||
this.options = callOrReturn(
|
||||
getExtensionField<AnyConfig['addOptions']>(this, 'addOptions', {
|
||||
name: this.name,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
this.storage = callOrReturn(
|
||||
getExtensionField<AnyConfig['addStorage']>(this, 'addStorage', {
|
||||
name: this.name,
|
||||
options: this.options,
|
||||
}),
|
||||
) || {}
|
||||
}
|
||||
|
||||
static create<TOptions = any, TStorage = any, TAttrs = any, TSpec = any>(config: Partial<DecorationConfig<TOptions, TStorage, TAttrs, TSpec>> = {}) {
|
||||
return new Decoration<TOptions, TStorage, TAttrs, TSpec>(config)
|
||||
}
|
||||
|
||||
configure(options: Partial<Options> = {}) {
|
||||
// return a new instance so we can use the same extension
|
||||
// with different calls of `configure`
|
||||
const extension = this.extend<Options, Storage, Attrs, Spec>({
|
||||
...this.config,
|
||||
addOptions: () => {
|
||||
return mergeDeep(this.options as Record<string, any>, options) as Options
|
||||
},
|
||||
})
|
||||
|
||||
// Always preserve the current name
|
||||
extension.name = this.name
|
||||
// Set the parent to be our parent
|
||||
extension.parent = this.parent
|
||||
|
||||
return extension
|
||||
}
|
||||
|
||||
extend<ExtendedOptions = Options, ExtendedStorage = Storage, ExtendedAttrs = Attrs, ExtendSpec = Spec>(
|
||||
extendedConfig: Partial<DecorationConfig<ExtendedOptions, ExtendedStorage, ExtendedAttrs, ExtendSpec>> = {},
|
||||
) {
|
||||
const extension = new Decoration<ExtendedOptions, ExtendedStorage, ExtendedAttrs, ExtendSpec>({
|
||||
...this.config,
|
||||
...extendedConfig,
|
||||
})
|
||||
|
||||
extension.parent = this
|
||||
|
||||
this.child = extension
|
||||
|
||||
extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name
|
||||
|
||||
if (extendedConfig.defaultOptions && Object.keys(extendedConfig.defaultOptions).length > 0) {
|
||||
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, 'addOptions', {
|
||||
name: extension.name,
|
||||
}),
|
||||
)
|
||||
|
||||
extension.storage = callOrReturn(
|
||||
getExtensionField<AnyConfig['addStorage']>(extension, 'addStorage', {
|
||||
name: extension.name,
|
||||
options: extension.options,
|
||||
}),
|
||||
)
|
||||
|
||||
return extension
|
||||
}
|
||||
}
|
156
packages/core/src/DecorationManager.ts
Normal file
156
packages/core/src/DecorationManager.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { Node } from '@tiptap/pm/model'
|
||||
import { Plugin, PluginKey, Selection } from '@tiptap/pm/state'
|
||||
import { DecorationSet, DecorationSource } from '@tiptap/pm/view'
|
||||
|
||||
import { Editor } from './Editor.js'
|
||||
import { NodePos } from './NodePos.js'
|
||||
import { Range } from './types.js'
|
||||
|
||||
type AttachToOptions = {
|
||||
/**
|
||||
* Force the decoration to be a specific type.
|
||||
*/
|
||||
type?: 'node' | 'inline' | 'widget';
|
||||
/**
|
||||
* Offset the target position by a number or an object with start and end properties.
|
||||
*/
|
||||
offset?:
|
||||
| number
|
||||
| {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
};
|
||||
|
||||
type ResolvedDecorationOptions = {
|
||||
type: 'widget' | 'inline' | 'node';
|
||||
from: number;
|
||||
to: number;
|
||||
};
|
||||
|
||||
type DecorationManagerPluginState = {};
|
||||
const pluginKey = new PluginKey<DecorationManagerPluginState>('tiptapDecorationManager')
|
||||
|
||||
export class DecorationManager {
|
||||
editor: Editor
|
||||
|
||||
decorations: DecorationSource
|
||||
|
||||
constructor(props: { editor: Editor }) {
|
||||
this.editor = props.editor
|
||||
this.decorations = DecorationSet.empty
|
||||
}
|
||||
|
||||
private resolvePositionsAndType(
|
||||
target: Node | NodePos | Range | Selection | number,
|
||||
options?: Pick<AttachToOptions, 'offset' | 'type'>,
|
||||
): ResolvedDecorationOptions {
|
||||
let offsetFrom = 0
|
||||
let offsetTo = 0
|
||||
|
||||
if (options?.offset) {
|
||||
if (typeof options.offset === 'number') {
|
||||
offsetFrom = options.offset
|
||||
offsetTo = options.offset
|
||||
} else {
|
||||
offsetFrom = options.offset.start
|
||||
offsetTo = options.offset.end
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof target === 'number') {
|
||||
return {
|
||||
type: options?.type ?? 'widget',
|
||||
from: target + offsetFrom,
|
||||
to: target + offsetTo,
|
||||
}
|
||||
}
|
||||
|
||||
if (target instanceof Selection) {
|
||||
return {
|
||||
type: options?.type ?? 'inline',
|
||||
from: target.from + offsetFrom,
|
||||
to: target.to + offsetFrom,
|
||||
}
|
||||
}
|
||||
|
||||
if (target instanceof NodePos) {
|
||||
return {
|
||||
type: options?.type ?? (offsetFrom !== 0 || offsetTo !== 0 ? 'inline' : 'node'),
|
||||
from: target.pos + offsetFrom,
|
||||
to: target.pos + target.node.nodeSize + offsetTo,
|
||||
}
|
||||
}
|
||||
|
||||
if ('from' in target && 'to' in target) {
|
||||
return {
|
||||
type: options?.type ?? 'inline',
|
||||
from: target.from + offsetFrom,
|
||||
to: target.to + offsetTo,
|
||||
}
|
||||
}
|
||||
|
||||
if (target instanceof Node) {
|
||||
// This may be too expensive for large documents, but it’s the only way to find the correct position
|
||||
let from = -1
|
||||
let to = -1
|
||||
|
||||
// Perhaps we can find the node faster than doing a full traversal
|
||||
this.editor.state.doc.descendants((node, pos) => {
|
||||
if (from !== -1 && to !== -1) {
|
||||
return false
|
||||
}
|
||||
if (node === target) {
|
||||
from = pos
|
||||
to = pos + node.nodeSize
|
||||
}
|
||||
})
|
||||
|
||||
if (from === -1 || to === -1) {
|
||||
throw new Error('Node not found', { cause: target })
|
||||
}
|
||||
|
||||
return {
|
||||
// a node can’t have an offset, so we need to convert it to an inline decoration
|
||||
type: options?.type ?? (offsetFrom !== 0 || offsetTo !== 0 ? 'inline' : 'node'),
|
||||
from,
|
||||
to,
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Invalid target: ${target}`, { cause: target })
|
||||
}
|
||||
|
||||
attachTo(targetNode: Node, options?: AttachToOptions): void;
|
||||
attachTo(targetNode: NodePos, options?: AttachToOptions): void;
|
||||
attachTo(range: Range, options?: AttachToOptions): void;
|
||||
attachTo(selection: Selection, options?: AttachToOptions): void;
|
||||
attachTo(pos: number, options?: AttachToOptions): void;
|
||||
attachTo(target: Node | NodePos | Range | Selection | number, options?: AttachToOptions): void {
|
||||
this.resolvePositionsAndType(target, options)
|
||||
}
|
||||
|
||||
static get pluginKey() {
|
||||
return pluginKey
|
||||
}
|
||||
|
||||
getPlugin() {
|
||||
return new Plugin<DecorationManagerPluginState>({
|
||||
key: DecorationManager.pluginKey,
|
||||
state: {
|
||||
init(config, instance) {
|
||||
return {}
|
||||
},
|
||||
apply(tr, value, oldState, newState) {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -21,6 +21,9 @@ export interface Commands<ReturnType = any> {}
|
||||
// eslint-disable-next-line
|
||||
export interface ExtensionConfig<Options = any, Storage = any> {}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export interface DecorationConfig<Options = any, Storage = any> {}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export interface NodeConfig<Options = any, Storage = any> {}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
import { EditorState, Transaction } from '@tiptap/pm/state'
|
||||
import { Mappable } from '@tiptap/pm/transform'
|
||||
import {
|
||||
Decoration,
|
||||
Decoration as PMDecoration,
|
||||
DecorationAttrs,
|
||||
EditorProps,
|
||||
EditorView,
|
||||
@ -15,16 +15,17 @@ import {
|
||||
NodeViewConstructor,
|
||||
} from '@tiptap/pm/view'
|
||||
|
||||
import { Decoration } from './Decoration.js'
|
||||
import { Editor } from './Editor.js'
|
||||
import { Extension } from './Extension.js'
|
||||
import {
|
||||
Commands, ExtensionConfig, MarkConfig, NodeConfig,
|
||||
Commands, DecorationConfig, ExtensionConfig, MarkConfig, NodeConfig,
|
||||
} from './index.js'
|
||||
import { Mark } from './Mark.js'
|
||||
import { Node } from './Node.js'
|
||||
|
||||
export type AnyConfig = ExtensionConfig | NodeConfig | MarkConfig;
|
||||
export type AnyExtension = Extension | Node | Mark;
|
||||
export type AnyConfig = ExtensionConfig | NodeConfig | MarkConfig | DecorationConfig;
|
||||
export type AnyExtension = Extension | Node | Mark | Decoration;
|
||||
export type Extensions = AnyExtension[];
|
||||
|
||||
export type ParentConfig<T> = Partial<{
|
||||
@ -233,8 +234,8 @@ export type DOMNode = InstanceType<typeof window.Node>
|
||||
*/
|
||||
export interface DecorationType {
|
||||
spec: any
|
||||
map(mapping: Mappable, span: Decoration, offset: number, oldOffset: number): Decoration | null
|
||||
valid(node: Node, span: Decoration): boolean
|
||||
map(mapping: Mappable, span: PMDecoration, offset: number, oldOffset: number): PMDecoration | null
|
||||
valid(node: Node, span: PMDecoration): boolean
|
||||
eq(other: DecorationType): boolean
|
||||
destroy(dom: DOMNode): void
|
||||
readonly attrs: DecorationAttrs
|
||||
@ -244,7 +245,7 @@ export interface DecorationType {
|
||||
* prosemirror-view does not export the `type` property of `Decoration`.
|
||||
* This adds the `type` property to the `Decoration` type.
|
||||
*/
|
||||
export type DecorationWithType = Decoration & {
|
||||
export type DecorationWithType = PMDecoration & {
|
||||
type: DecorationType;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user