feat: Add extension storage (#2069)

This commit is contained in:
Philipp Kühn
2021-10-22 08:52:54 +02:00
committed by GitHub
parent 6987505fda
commit 7ffabf251c
26 changed files with 555 additions and 105 deletions

View File

@@ -31,7 +31,7 @@ module.exports = {
extends: [ extends: [
'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:vue/strongly-recommended', 'plugin:vue/vue3-strongly-recommended',
'airbnb-base', 'airbnb-base',
], ],
rules: { rules: {

View File

@@ -0,0 +1,19 @@
import { Extension } from '@tiptap/core'
type CustomStorage = {
foo: number,
}
export const CustomExtension = Extension.create<{}, CustomStorage>({
name: 'custom',
addStorage() {
return {
foo: 123,
}
},
onUpdate() {
this.storage.foo += 1
},
})

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from '../../../../setup/react.ts'
import source from '@source'
setup('Experiments/ExtensionStorage', source)
</script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
import React from 'react'
import { useEditor, EditorContent } from '@tiptap/react'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { CustomExtension } from './CustomExtension'
import './styles.scss'
export default () => {
const editor = useEditor({
extensions: [
Document,
Paragraph,
Text,
CustomExtension,
],
content: `
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though.
</p>
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`,
})
return (
<>
reactive storage: {editor?.storage.custom.foo}
<EditorContent editor={editor} />
</>
)
}

View File

@@ -0,0 +1,6 @@
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}

View File

@@ -0,0 +1,19 @@
import { Extension } from '@tiptap/core'
type CustomStorage = {
foo: number,
}
export const CustomExtension = Extension.create<{}, CustomStorage>({
name: 'custom',
addStorage() {
return {
foo: 123,
}
},
onUpdate() {
this.storage.foo += 1
},
})

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from '../../../../setup/vue.ts'
import source from '@source'
setup('Experiments/ExtensionStorage', source)
</script>
</body>
</html>

View File

@@ -0,0 +1,56 @@
<template>
reactive storage: {{ editor?.storage.custom.foo }}
<editor-content :editor="editor" />
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { CustomExtension } from './CustomExtension'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
CustomExtension,
],
content: `
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though.
</p>
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`,
})
},
beforeUnmount() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
</style>

View File

@@ -78,6 +78,39 @@ const CustomHeading = Heading.extend({
}) })
``` ```
### Storage
At some point you probably want to save some data within your extension instance. This data is mutable. You can access it within the extension under `this.storage`.
```js
import { Extension } from '@tiptap/core'
const CustomExtension = Extension.create({
name: 'customExtension',
addStorage() {
return {
awesomeness: 100,
}
},
onUpdate() {
this.storage.awesomeness += 1
},
})
```
Outside the extension you have access via `editor.storage`. Make sure that each extension has a unique name.
```js
const editor = new Editor({
extensions: [
CustomExtension,
],
})
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.

View File

@@ -32,6 +32,25 @@ const CustomExtension = Extension.create<CustomExtensionOptions>({
}) })
``` ```
### Storage types
To add types for your extension storage, youll have to pass that as a second type parameter.
```ts
import { Extension } from '@tiptap/core'
export interface CustomExtensionStorage {
awesomeness: number,
}
const CustomExtension = Extension.create<{}, CustomExtensionStorage>({
addStorage() {
return {
awesomeness: 100,
}
},
})
```
### Command type ### Command type
The core package also exports a `Command` type, which needs to be added to all commands that you specify in your code. Here is an example: The core package also exports a `Command` type, which needs to be added to all commands that you specify in your code. Here is an example:

View File

@@ -50,6 +50,8 @@ export class Editor extends EventEmitter<EditorEvents> {
public isFocused = false public isFocused = false
public extensionStorage: Record<string, any> = {}
public options: EditorOptions = { public options: EditorOptions = {
element: document.createElement('div'), element: document.createElement('div'),
content: '', content: '',
@@ -100,6 +102,13 @@ export class Editor extends EventEmitter<EditorEvents> {
}, 0) }, 0)
} }
/**
* Returns the editor storage.
*/
public get storage(): Record<string, any> {
return this.extensionStorage
}
/** /**
* An object of all registered commands. * An object of all registered commands.
*/ */

View File

@@ -5,7 +5,10 @@ import { Editor } from './Editor'
import { Node } from './Node' import { Node } from './Node'
import { Mark } from './Mark' import { Mark } from './Mark'
import mergeDeep from './utilities/mergeDeep' import mergeDeep from './utilities/mergeDeep'
import callOrReturn from './utilities/callOrReturn'
import getExtensionField from './helpers/getExtensionField'
import { import {
AnyConfig,
Extensions, Extensions,
GlobalAttributes, GlobalAttributes,
RawCommands, RawCommands,
@@ -15,7 +18,7 @@ import {
import { ExtensionConfig } from '.' import { ExtensionConfig } from '.'
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface ExtensionConfig<Options = any> { interface ExtensionConfig<Options = any, Storage = any> {
[key: string]: any; [key: string]: any;
/** /**
@@ -33,13 +36,23 @@ declare module '@tiptap/core' {
*/ */
defaultOptions?: Options, defaultOptions?: Options,
/**
* Default Storage
*/
addStorage?: (this: {
name: string,
options: Options,
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addGlobalAttributes'],
}) => Storage,
/** /**
* Global attributes * Global attributes
*/ */
addGlobalAttributes?: (this: { addGlobalAttributes?: (this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<ExtensionConfig<Options>>['addGlobalAttributes'], storage: Storage,
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addGlobalAttributes'],
}) => GlobalAttributes | {}, }) => GlobalAttributes | {},
/** /**
@@ -48,8 +61,9 @@ declare module '@tiptap/core' {
addCommands?: (this: { addCommands?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['addCommands'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addCommands'],
}) => Partial<RawCommands>, }) => Partial<RawCommands>,
/** /**
@@ -58,8 +72,9 @@ declare module '@tiptap/core' {
addKeyboardShortcuts?: (this: { addKeyboardShortcuts?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['addKeyboardShortcuts'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addKeyboardShortcuts'],
}) => { }) => {
[key: string]: KeyboardShortcutCommand, [key: string]: KeyboardShortcutCommand,
}, },
@@ -70,8 +85,9 @@ declare module '@tiptap/core' {
addInputRules?: (this: { addInputRules?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['addInputRules'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addInputRules'],
}) => InputRule[], }) => InputRule[],
/** /**
@@ -80,8 +96,9 @@ declare module '@tiptap/core' {
addPasteRules?: (this: { addPasteRules?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['addPasteRules'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addPasteRules'],
}) => PasteRule[], }) => PasteRule[],
/** /**
@@ -90,8 +107,9 @@ declare module '@tiptap/core' {
addProseMirrorPlugins?: (this: { addProseMirrorPlugins?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['addProseMirrorPlugins'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['addProseMirrorPlugins'],
}) => Plugin[], }) => Plugin[],
/** /**
@@ -100,7 +118,8 @@ declare module '@tiptap/core' {
addExtensions?: (this: { addExtensions?: (this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<ExtensionConfig<Options>>['addExtensions'], storage: Storage,
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addExtensions'],
}) => Extensions, }) => Extensions,
/** /**
@@ -110,7 +129,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<ExtensionConfig<Options>>['extendNodeSchema'], storage: Storage,
parent: ParentConfig<ExtensionConfig<Options, Storage>>['extendNodeSchema'],
}, },
extension: Node, extension: Node,
) => Record<string, any>) | null, ) => Record<string, any>) | null,
@@ -122,7 +142,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<ExtensionConfig<Options>>['extendMarkSchema'], storage: Storage,
parent: ParentConfig<ExtensionConfig<Options, Storage>>['extendMarkSchema'],
}, },
extension: Mark, extension: Mark,
) => Record<string, any>) | null, ) => Record<string, any>) | null,
@@ -133,8 +154,9 @@ declare module '@tiptap/core' {
onBeforeCreate?: ((this: { onBeforeCreate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onBeforeCreate'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onBeforeCreate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -143,8 +165,9 @@ declare module '@tiptap/core' {
onCreate?: ((this: { onCreate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onCreate'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onCreate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -153,8 +176,9 @@ declare module '@tiptap/core' {
onUpdate?: ((this: { onUpdate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onUpdate'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onUpdate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -163,8 +187,9 @@ declare module '@tiptap/core' {
onSelectionUpdate?: ((this: { onSelectionUpdate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onSelectionUpdate'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onSelectionUpdate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -174,8 +199,9 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onTransaction'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onTransaction'],
}, },
props: { props: {
transaction: Transaction, transaction: Transaction,
@@ -189,8 +215,9 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onFocus'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onFocus'],
}, },
props: { props: {
event: FocusEvent, event: FocusEvent,
@@ -204,8 +231,9 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onBlur'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onBlur'],
}, },
props: { props: {
event: FocusEvent, event: FocusEvent,
@@ -218,13 +246,14 @@ declare module '@tiptap/core' {
onDestroy?: ((this: { onDestroy?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
parent: ParentConfig<ExtensionConfig<Options>>['onDestroy'], parent: ParentConfig<ExtensionConfig<Options, Storage>>['onDestroy'],
}) => void) | null, }) => void) | null,
} }
} }
export class Extension<Options = any> { export class Extension<Options = any, Storage = any> {
type = 'extension' type = 'extension'
name = 'extension' name = 'extension'
@@ -235,12 +264,14 @@ export class Extension<Options = any> {
options: Options options: Options
storage: Storage
config: ExtensionConfig = { config: ExtensionConfig = {
name: this.name, name: this.name,
defaultOptions: {}, defaultOptions: {},
} }
constructor(config: Partial<ExtensionConfig<Options>> = {}) { constructor(config: Partial<ExtensionConfig<Options, Storage>> = {}) {
this.config = { this.config = {
...this.config, ...this.config,
...config, ...config,
@@ -248,10 +279,18 @@ export class Extension<Options = any> {
this.name = this.config.name this.name = this.config.name
this.options = this.config.defaultOptions this.options = this.config.defaultOptions
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
this,
'addStorage',
{
name: this.name,
options: this.options,
},
))
} }
static create<O>(config: Partial<ExtensionConfig<O>> = {}) { static create<O = any, S = any>(config: Partial<ExtensionConfig<O, S>> = {}) {
return new Extension<O>(config) return new Extension<O, S>(config)
} }
configure(options: Partial<Options> = {}) { configure(options: Partial<Options> = {}) {
@@ -264,8 +303,8 @@ export class Extension<Options = any> {
return extension return extension
} }
extend<ExtendedOptions = Options>(extendedConfig: Partial<ExtensionConfig<ExtendedOptions>> = {}) { extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<ExtensionConfig<ExtendedOptions, ExtendedStorage>> = {}) {
const extension = new Extension<ExtendedOptions>(extendedConfig) const extension = new Extension<ExtendedOptions, ExtendedStorage>(extendedConfig)
extension.parent = this extension.parent = this
@@ -279,6 +318,15 @@ export class Extension<Options = any> {
? extendedConfig.defaultOptions ? extendedConfig.defaultOptions
: extension.parent.options : extension.parent.options
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
extension,
'addStorage',
{
name: extension.name,
options: extension.options,
},
))
return extension return extension
} }
} }

View File

@@ -14,6 +14,7 @@ import splitExtensions from './helpers/splitExtensions'
import getAttributesFromExtensions from './helpers/getAttributesFromExtensions' import getAttributesFromExtensions from './helpers/getAttributesFromExtensions'
import getRenderedAttributes from './helpers/getRenderedAttributes' import getRenderedAttributes from './helpers/getRenderedAttributes'
import callOrReturn from './utilities/callOrReturn' import callOrReturn from './utilities/callOrReturn'
import findDuplicates from './utilities/findDuplicates'
import { NodeConfig } from '.' import { NodeConfig } from '.'
export default class ExtensionManager { export default class ExtensionManager {
@@ -32,9 +33,13 @@ export default class ExtensionManager {
this.schema = getSchemaByResolvedExtensions(this.extensions) this.schema = getSchemaByResolvedExtensions(this.extensions)
this.extensions.forEach(extension => { this.extensions.forEach(extension => {
// store extension storage in editor
this.editor.extensionStorage[extension.name] = extension.storage
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.name, this.schema),
} }
@@ -130,7 +135,14 @@ export default class ExtensionManager {
} }
static resolve(extensions: Extensions): Extensions { static resolve(extensions: Extensions): Extensions {
return ExtensionManager.sort(ExtensionManager.flatten(extensions)) const resolvedExtensions = ExtensionManager.sort(ExtensionManager.flatten(extensions))
const duplicatedNames = findDuplicates(resolvedExtensions.map(extension => extension.name))
if (duplicatedNames.length) {
console.warn(`[tiptap warn]: Duplicate extension names found: [${duplicatedNames.map(item => `'${item}'`).join(', ')}]. This can lead to issues.`)
}
return resolvedExtensions
} }
static flatten(extensions: Extensions): Extensions { static flatten(extensions: Extensions): Extensions {
@@ -139,6 +151,7 @@ export default class ExtensionManager {
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
const addExtensions = getExtensionField<AnyConfig['addExtensions']>( const addExtensions = getExtensionField<AnyConfig['addExtensions']>(
@@ -184,6 +197,7 @@ export default class ExtensionManager {
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
editor: this.editor, editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.name, this.schema),
} }
@@ -223,6 +237,7 @@ export default class ExtensionManager {
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
editor, editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.name, this.schema),
} }
@@ -313,6 +328,7 @@ export default class ExtensionManager {
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
editor, editor,
type: getNodeType(extension.name, this.schema), type: getNodeType(extension.name, this.schema),
} }

View File

@@ -8,7 +8,10 @@ import { Plugin, Transaction } from 'prosemirror-state'
import { InputRule } from './InputRule' import { InputRule } from './InputRule'
import { PasteRule } from './PasteRule' import { PasteRule } from './PasteRule'
import mergeDeep from './utilities/mergeDeep' import mergeDeep from './utilities/mergeDeep'
import callOrReturn from './utilities/callOrReturn'
import getExtensionField from './helpers/getExtensionField'
import { import {
AnyConfig,
Extensions, Extensions,
Attributes, Attributes,
RawCommands, RawCommands,
@@ -21,7 +24,7 @@ import { MarkConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
declare module '@tiptap/core' { declare module '@tiptap/core' {
export interface MarkConfig<Options = any> { export interface MarkConfig<Options = any, Storage = any> {
[key: string]: any; [key: string]: any;
/** /**
@@ -39,13 +42,23 @@ declare module '@tiptap/core' {
*/ */
defaultOptions?: Options, defaultOptions?: Options,
/**
* Default Storage
*/
addStorage?: (this: {
name: string,
options: Options,
parent: ParentConfig<MarkConfig<Options, Storage>>['addGlobalAttributes'],
}) => Storage,
/** /**
* Global attributes * Global attributes
*/ */
addGlobalAttributes?: (this: { addGlobalAttributes?: (this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['addGlobalAttributes'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['addGlobalAttributes'],
}) => GlobalAttributes | {}, }) => GlobalAttributes | {},
/** /**
@@ -54,9 +67,10 @@ declare module '@tiptap/core' {
addCommands?: (this: { addCommands?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['addCommands'], parent: ParentConfig<MarkConfig<Options, Storage>>['addCommands'],
}) => Partial<RawCommands>, }) => Partial<RawCommands>,
/** /**
@@ -65,9 +79,10 @@ declare module '@tiptap/core' {
addKeyboardShortcuts?: (this: { addKeyboardShortcuts?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['addKeyboardShortcuts'], parent: ParentConfig<MarkConfig<Options, Storage>>['addKeyboardShortcuts'],
}) => { }) => {
[key: string]: KeyboardShortcutCommand, [key: string]: KeyboardShortcutCommand,
}, },
@@ -78,9 +93,10 @@ declare module '@tiptap/core' {
addInputRules?: (this: { addInputRules?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['addInputRules'], parent: ParentConfig<MarkConfig<Options, Storage>>['addInputRules'],
}) => InputRule[], }) => InputRule[],
/** /**
@@ -89,9 +105,10 @@ declare module '@tiptap/core' {
addPasteRules?: (this: { addPasteRules?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['addPasteRules'], parent: ParentConfig<MarkConfig<Options, Storage>>['addPasteRules'],
}) => PasteRule[], }) => PasteRule[],
/** /**
@@ -100,9 +117,10 @@ declare module '@tiptap/core' {
addProseMirrorPlugins?: (this: { addProseMirrorPlugins?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['addProseMirrorPlugins'], parent: ParentConfig<MarkConfig<Options, Storage>>['addProseMirrorPlugins'],
}) => Plugin[], }) => Plugin[],
/** /**
@@ -111,7 +129,8 @@ declare module '@tiptap/core' {
addExtensions?: (this: { addExtensions?: (this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['addExtensions'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['addExtensions'],
}) => Extensions, }) => Extensions,
/** /**
@@ -121,7 +140,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['extendNodeSchema'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['extendNodeSchema'],
}, },
extension: Node, extension: Node,
) => Record<string, any>) | null, ) => Record<string, any>) | null,
@@ -133,7 +153,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['extendMarkSchema'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['extendMarkSchema'],
}, },
extension: Mark, extension: Mark,
) => Record<string, any>) | null, ) => Record<string, any>) | null,
@@ -144,9 +165,10 @@ declare module '@tiptap/core' {
onBeforeCreate?: ((this: { onBeforeCreate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onBeforeCreate'], parent: ParentConfig<MarkConfig<Options, Storage>>['onBeforeCreate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -155,9 +177,10 @@ declare module '@tiptap/core' {
onCreate?: ((this: { onCreate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onCreate'], parent: ParentConfig<MarkConfig<Options, Storage>>['onCreate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -166,9 +189,10 @@ declare module '@tiptap/core' {
onUpdate?: ((this: { onUpdate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onUpdate'], parent: ParentConfig<MarkConfig<Options, Storage>>['onUpdate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -177,9 +201,10 @@ declare module '@tiptap/core' {
onSelectionUpdate?: ((this: { onSelectionUpdate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onSelectionUpdate'], parent: ParentConfig<MarkConfig<Options, Storage>>['onSelectionUpdate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -189,9 +214,10 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onTransaction'], parent: ParentConfig<MarkConfig<Options, Storage>>['onTransaction'],
}, },
props: { props: {
transaction: Transaction, transaction: Transaction,
@@ -205,9 +231,10 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onFocus'], parent: ParentConfig<MarkConfig<Options, Storage>>['onFocus'],
}, },
props: { props: {
event: FocusEvent, event: FocusEvent,
@@ -221,9 +248,10 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onBlur'], parent: ParentConfig<MarkConfig<Options, Storage>>['onBlur'],
}, },
props: { props: {
event: FocusEvent, event: FocusEvent,
@@ -236,9 +264,10 @@ declare module '@tiptap/core' {
onDestroy?: ((this: { onDestroy?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: MarkType, type: MarkType,
parent: ParentConfig<MarkConfig<Options>>['onDestroy'], parent: ParentConfig<MarkConfig<Options, Storage>>['onDestroy'],
}) => void) | null, }) => void) | null,
/** /**
@@ -252,7 +281,8 @@ declare module '@tiptap/core' {
inclusive?: MarkSpec['inclusive'] | ((this: { inclusive?: MarkSpec['inclusive'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['inclusive'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['inclusive'],
}) => MarkSpec['inclusive']), }) => MarkSpec['inclusive']),
/** /**
@@ -261,7 +291,8 @@ declare module '@tiptap/core' {
excludes?: MarkSpec['excludes'] | ((this: { excludes?: MarkSpec['excludes'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['excludes'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['excludes'],
}) => MarkSpec['excludes']), }) => MarkSpec['excludes']),
/** /**
@@ -270,7 +301,8 @@ declare module '@tiptap/core' {
group?: MarkSpec['group'] | ((this: { group?: MarkSpec['group'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['group'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['group'],
}) => MarkSpec['group']), }) => MarkSpec['group']),
/** /**
@@ -279,7 +311,8 @@ declare module '@tiptap/core' {
spanning?: MarkSpec['spanning'] | ((this: { spanning?: MarkSpec['spanning'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['spanning'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['spanning'],
}) => MarkSpec['spanning']), }) => MarkSpec['spanning']),
/** /**
@@ -288,7 +321,8 @@ declare module '@tiptap/core' {
code?: boolean | ((this: { code?: boolean | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['code'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['code'],
}) => boolean), }) => boolean),
/** /**
@@ -298,7 +332,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['parseHTML'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['parseHTML'],
}, },
) => MarkSpec['parseDOM'], ) => MarkSpec['parseDOM'],
@@ -309,7 +344,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['renderHTML'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['renderHTML'],
}, },
props: { props: {
mark: ProseMirrorMark, mark: ProseMirrorMark,
@@ -324,13 +360,14 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<MarkConfig<Options>>['addAttributes'], storage: Storage,
parent: ParentConfig<MarkConfig<Options, Storage>>['addAttributes'],
}, },
) => Attributes | {}, ) => Attributes | {},
} }
} }
export class Mark<Options = any> { export class Mark<Options = any, Storage = any> {
type = 'mark' type = 'mark'
name = 'mark' name = 'mark'
@@ -341,12 +378,14 @@ export class Mark<Options = any> {
options: Options options: Options
storage: Storage
config: MarkConfig = { config: MarkConfig = {
name: this.name, name: this.name,
defaultOptions: {}, defaultOptions: {},
} }
constructor(config: Partial<MarkConfig<Options>> = {}) { constructor(config: Partial<MarkConfig<Options, Storage>> = {}) {
this.config = { this.config = {
...this.config, ...this.config,
...config, ...config,
@@ -354,10 +393,18 @@ export class Mark<Options = any> {
this.name = this.config.name this.name = this.config.name
this.options = this.config.defaultOptions this.options = this.config.defaultOptions
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
this,
'addStorage',
{
name: this.name,
options: this.options,
},
))
} }
static create<O>(config: Partial<MarkConfig<O>> = {}) { static create<O = any, S = any>(config: Partial<MarkConfig<O, S>> = {}) {
return new Mark<O>(config) return new Mark<O, S>(config)
} }
configure(options: Partial<Options> = {}) { configure(options: Partial<Options> = {}) {
@@ -370,8 +417,8 @@ export class Mark<Options = any> {
return extension return extension
} }
extend<ExtendedOptions = Options>(extendedConfig: Partial<MarkConfig<ExtendedOptions>> = {}) { extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> = {}) {
const extension = new Mark<ExtendedOptions>(extendedConfig) const extension = new Mark<ExtendedOptions, ExtendedStorage>(extendedConfig)
extension.parent = this extension.parent = this
@@ -385,6 +432,15 @@ export class Mark<Options = any> {
? extendedConfig.defaultOptions ? extendedConfig.defaultOptions
: extension.parent.options : extension.parent.options
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
extension,
'addStorage',
{
name: extension.name,
options: extension.options,
},
))
return extension return extension
} }
} }

View File

@@ -8,7 +8,10 @@ import { Plugin, Transaction } from 'prosemirror-state'
import { InputRule } from './InputRule' import { InputRule } from './InputRule'
import { PasteRule } from './PasteRule' import { PasteRule } from './PasteRule'
import mergeDeep from './utilities/mergeDeep' import mergeDeep from './utilities/mergeDeep'
import callOrReturn from './utilities/callOrReturn'
import getExtensionField from './helpers/getExtensionField'
import { import {
AnyConfig,
Extensions, Extensions,
Attributes, Attributes,
NodeViewRenderer, NodeViewRenderer,
@@ -21,7 +24,7 @@ import { NodeConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface NodeConfig<Options = any> { interface NodeConfig<Options = any, Storage = any> {
[key: string]: any; [key: string]: any;
/** /**
@@ -39,13 +42,23 @@ declare module '@tiptap/core' {
*/ */
defaultOptions?: Options, defaultOptions?: Options,
/**
* Default Storage
*/
addStorage?: (this: {
name: string,
options: Options,
parent: ParentConfig<NodeConfig<Options, Storage>>['addGlobalAttributes'],
}) => Storage,
/** /**
* Global attributes * Global attributes
*/ */
addGlobalAttributes?: (this: { addGlobalAttributes?: (this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['addGlobalAttributes'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['addGlobalAttributes'],
}) => GlobalAttributes | {}, }) => GlobalAttributes | {},
/** /**
@@ -54,9 +67,10 @@ declare module '@tiptap/core' {
addCommands?: (this: { addCommands?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['addCommands'], parent: ParentConfig<NodeConfig<Options, Storage>>['addCommands'],
}) => Partial<RawCommands>, }) => Partial<RawCommands>,
/** /**
@@ -65,9 +79,10 @@ declare module '@tiptap/core' {
addKeyboardShortcuts?: (this: { addKeyboardShortcuts?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['addKeyboardShortcuts'], parent: ParentConfig<NodeConfig<Options, Storage>>['addKeyboardShortcuts'],
}) => { }) => {
[key: string]: KeyboardShortcutCommand, [key: string]: KeyboardShortcutCommand,
}, },
@@ -78,9 +93,10 @@ declare module '@tiptap/core' {
addInputRules?: (this: { addInputRules?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['addInputRules'], parent: ParentConfig<NodeConfig<Options, Storage>>['addInputRules'],
}) => InputRule[], }) => InputRule[],
/** /**
@@ -89,9 +105,10 @@ declare module '@tiptap/core' {
addPasteRules?: (this: { addPasteRules?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['addPasteRules'], parent: ParentConfig<NodeConfig<Options, Storage>>['addPasteRules'],
}) => PasteRule[], }) => PasteRule[],
/** /**
@@ -100,9 +117,10 @@ declare module '@tiptap/core' {
addProseMirrorPlugins?: (this: { addProseMirrorPlugins?: (this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['addProseMirrorPlugins'], parent: ParentConfig<NodeConfig<Options, Storage>>['addProseMirrorPlugins'],
}) => Plugin[], }) => Plugin[],
/** /**
@@ -111,7 +129,8 @@ declare module '@tiptap/core' {
addExtensions?: (this: { addExtensions?: (this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['addExtensions'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['addExtensions'],
}) => Extensions, }) => Extensions,
/** /**
@@ -121,7 +140,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['extendNodeSchema'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['extendNodeSchema'],
}, },
extension: Node, extension: Node,
) => Record<string, any>) | null, ) => Record<string, any>) | null,
@@ -133,7 +153,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['extendMarkSchema'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['extendMarkSchema'],
}, },
extension: Node, extension: Node,
) => Record<string, any>) | null, ) => Record<string, any>) | null,
@@ -144,9 +165,10 @@ declare module '@tiptap/core' {
onBeforeCreate?: ((this: { onBeforeCreate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onBeforeCreate'], parent: ParentConfig<NodeConfig<Options, Storage>>['onBeforeCreate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -155,9 +177,10 @@ declare module '@tiptap/core' {
onCreate?: ((this: { onCreate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onCreate'], parent: ParentConfig<NodeConfig<Options, Storage>>['onCreate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -166,9 +189,10 @@ declare module '@tiptap/core' {
onUpdate?: ((this: { onUpdate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onUpdate'], parent: ParentConfig<NodeConfig<Options, Storage>>['onUpdate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -177,9 +201,10 @@ declare module '@tiptap/core' {
onSelectionUpdate?: ((this: { onSelectionUpdate?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onSelectionUpdate'], parent: ParentConfig<NodeConfig<Options, Storage>>['onSelectionUpdate'],
}) => void) | null, }) => void) | null,
/** /**
@@ -189,9 +214,10 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onTransaction'], parent: ParentConfig<NodeConfig<Options, Storage>>['onTransaction'],
}, },
props: { props: {
transaction: Transaction, transaction: Transaction,
@@ -205,9 +231,10 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onFocus'], parent: ParentConfig<NodeConfig<Options, Storage>>['onFocus'],
}, },
props: { props: {
event: FocusEvent, event: FocusEvent,
@@ -221,9 +248,10 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onBlur'], parent: ParentConfig<NodeConfig<Options, Storage>>['onBlur'],
}, },
props: { props: {
event: FocusEvent, event: FocusEvent,
@@ -236,9 +264,10 @@ declare module '@tiptap/core' {
onDestroy?: ((this: { onDestroy?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['onDestroy'], parent: ParentConfig<NodeConfig<Options, Storage>>['onDestroy'],
}) => void) | null, }) => void) | null,
/** /**
@@ -247,9 +276,10 @@ declare module '@tiptap/core' {
addNodeView?: ((this: { addNodeView?: ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
editor: Editor, editor: Editor,
type: NodeType, type: NodeType,
parent: ParentConfig<NodeConfig<Options>>['addNodeView'], parent: ParentConfig<NodeConfig<Options, Storage>>['addNodeView'],
}) => NodeViewRenderer) | null, }) => NodeViewRenderer) | null,
/** /**
@@ -263,7 +293,8 @@ declare module '@tiptap/core' {
content?: NodeSpec['content'] | ((this: { content?: NodeSpec['content'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['content'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['content'],
}) => NodeSpec['content']), }) => NodeSpec['content']),
/** /**
@@ -272,7 +303,8 @@ declare module '@tiptap/core' {
marks?: NodeSpec['marks'] | ((this: { marks?: NodeSpec['marks'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['marks'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['marks'],
}) => NodeSpec['marks']), }) => NodeSpec['marks']),
/** /**
@@ -281,7 +313,8 @@ declare module '@tiptap/core' {
group?: NodeSpec['group'] | ((this: { group?: NodeSpec['group'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['group'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['group'],
}) => NodeSpec['group']), }) => NodeSpec['group']),
/** /**
@@ -290,7 +323,8 @@ declare module '@tiptap/core' {
inline?: NodeSpec['inline'] | ((this: { inline?: NodeSpec['inline'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['inline'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['inline'],
}) => NodeSpec['inline']), }) => NodeSpec['inline']),
/** /**
@@ -299,7 +333,8 @@ declare module '@tiptap/core' {
atom?: NodeSpec['atom'] | ((this: { atom?: NodeSpec['atom'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['atom'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['atom'],
}) => NodeSpec['atom']), }) => NodeSpec['atom']),
/** /**
@@ -308,7 +343,8 @@ declare module '@tiptap/core' {
selectable?: NodeSpec['selectable'] | ((this: { selectable?: NodeSpec['selectable'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['selectable'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['selectable'],
}) => NodeSpec['selectable']), }) => NodeSpec['selectable']),
/** /**
@@ -317,7 +353,8 @@ declare module '@tiptap/core' {
draggable?: NodeSpec['draggable'] | ((this: { draggable?: NodeSpec['draggable'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['draggable'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['draggable'],
}) => NodeSpec['draggable']), }) => NodeSpec['draggable']),
/** /**
@@ -326,7 +363,8 @@ declare module '@tiptap/core' {
code?: NodeSpec['code'] | ((this: { code?: NodeSpec['code'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['code'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['code'],
}) => NodeSpec['code']), }) => NodeSpec['code']),
/** /**
@@ -335,7 +373,8 @@ declare module '@tiptap/core' {
defining?: NodeSpec['defining'] | ((this: { defining?: NodeSpec['defining'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['defining'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['defining'],
}) => NodeSpec['defining']), }) => NodeSpec['defining']),
/** /**
@@ -344,7 +383,8 @@ declare module '@tiptap/core' {
isolating?: NodeSpec['isolating'] | ((this: { isolating?: NodeSpec['isolating'] | ((this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['isolating'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['isolating'],
}) => NodeSpec['isolating']), }) => NodeSpec['isolating']),
/** /**
@@ -354,7 +394,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['parseHTML'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['parseHTML'],
}, },
) => NodeSpec['parseDOM'], ) => NodeSpec['parseDOM'],
@@ -365,7 +406,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['renderHTML'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['renderHTML'],
}, },
props: { props: {
node: ProseMirrorNode, node: ProseMirrorNode,
@@ -380,7 +422,8 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['renderText'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['renderText'],
}, },
props: { props: {
node: ProseMirrorNode, node: ProseMirrorNode,
@@ -397,13 +440,14 @@ declare module '@tiptap/core' {
this: { this: {
name: string, name: string,
options: Options, options: Options,
parent: ParentConfig<NodeConfig<Options>>['addAttributes'], storage: Storage,
parent: ParentConfig<NodeConfig<Options, Storage>>['addAttributes'],
}, },
) => Attributes | {}, ) => Attributes | {},
} }
} }
export class Node<Options = any> { export class Node<Options = any, Storage = any> {
type = 'node' type = 'node'
name = 'node' name = 'node'
@@ -414,12 +458,14 @@ export class Node<Options = any> {
options: Options options: Options
storage: Storage
config: NodeConfig = { config: NodeConfig = {
name: this.name, name: this.name,
defaultOptions: {}, defaultOptions: {},
} }
constructor(config: Partial<NodeConfig<Options>> = {}) { constructor(config: Partial<NodeConfig<Options, Storage>> = {}) {
this.config = { this.config = {
...this.config, ...this.config,
...config, ...config,
@@ -427,10 +473,18 @@ export class Node<Options = any> {
this.name = this.config.name this.name = this.config.name
this.options = this.config.defaultOptions this.options = this.config.defaultOptions
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
this,
'addStorage',
{
name: this.name,
options: this.options,
},
))
} }
static create<O>(config: Partial<NodeConfig<O>> = {}) { static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> = {}) {
return new Node<O>(config) return new Node<O, S>(config)
} }
configure(options: Partial<Options> = {}) { configure(options: Partial<Options> = {}) {
@@ -443,8 +497,8 @@ export class Node<Options = any> {
return extension return extension
} }
extend<ExtendedOptions = Options>(extendedConfig: Partial<NodeConfig<ExtendedOptions>> = {}) { extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>> = {}) {
const extension = new Node<ExtendedOptions>(extendedConfig) const extension = new Node<ExtendedOptions, ExtendedStorage>(extendedConfig)
extension.parent = this extension.parent = this
@@ -458,6 +512,15 @@ export class Node<Options = any> {
? extendedConfig.defaultOptions ? extendedConfig.defaultOptions
: extension.parent.options : extension.parent.options
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
extension,
'addStorage',
{
name: extension.name,
options: extension.options,
},
))
return extension return extension
} }
} }

View File

@@ -30,6 +30,7 @@ export default function getAttributesFromExtensions(extensions: Extensions): Ext
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
const addGlobalAttributes = getExtensionField<AnyConfig['addGlobalAttributes']>( const addGlobalAttributes = getExtensionField<AnyConfig['addGlobalAttributes']>(
@@ -67,6 +68,7 @@ export default function getAttributesFromExtensions(extensions: Extensions): Ext
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
const addAttributes = getExtensionField<NodeConfig['addAttributes'] | MarkConfig['addAttributes']>( const addAttributes = getExtensionField<NodeConfig['addAttributes'] | MarkConfig['addAttributes']>(

View File

@@ -1,9 +1,9 @@
import { AnyExtension, RemoveThis } from '../types' import { AnyExtension, RemoveThis, MaybeThisParameterType } from '../types'
export default function getExtensionField<T = any>( export default function getExtensionField<T = any>(
extension: AnyExtension, extension: AnyExtension,
field: string, field: string,
context: Record<string, any> = {}, context?: Omit<MaybeThisParameterType<T>, 'parent'>,
): RemoveThis<T> { ): RemoveThis<T> {
if (extension.config[field] === undefined && extension.parent) { if (extension.config[field] === undefined && extension.parent) {

View File

@@ -29,6 +29,7 @@ export default function getSchemaByResolvedExtensions(extensions: Extensions): S
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
const extraNodeFields = extensions.reduce((fields, e) => { const extraNodeFields = extensions.reduce((fields, e) => {
@@ -91,6 +92,7 @@ export default function getSchemaByResolvedExtensions(extensions: Extensions): S
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
const extraMarkFields = extensions.reduce((fields, e) => { const extraMarkFields = extensions.reduce((fields, e) => {

View File

@@ -15,6 +15,7 @@ export default function isList(name: string, extensions: Extensions): boolean {
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
const group = callOrReturn(getExtensionField<NodeConfig['group']>(extension, 'group', context)) const group = callOrReturn(getExtensionField<NodeConfig['group']>(extension, 'group', context))

View File

@@ -56,10 +56,10 @@ export { default as posToDOMRect } from './helpers/posToDOMRect'
export interface Commands<ReturnType = any> {} export interface Commands<ReturnType = any> {}
// eslint-disable-next-line // eslint-disable-next-line
export interface ExtensionConfig<Options = any> {} export interface ExtensionConfig<Options = any, Storage = any> {}
// eslint-disable-next-line // eslint-disable-next-line
export interface NodeConfig<Options = any> {} export interface NodeConfig<Options = any, Storage = any> {}
// eslint-disable-next-line // eslint-disable-next-line
export interface MarkConfig<Options = any> {} export interface MarkConfig<Options = any, Storage = any> {}

View File

@@ -31,6 +31,15 @@ export type ParentConfig<T> = Partial<{
: T[P] : T[P]
}> }>
export type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint
export type RemoveThis<T> = T extends (...args: any) => any export type RemoveThis<T> = T extends (...args: any) => any
? (...args: Parameters<T>) => ReturnType<T> ? (...args: Parameters<T>) => ReturnType<T>
: T : T
@@ -39,6 +48,10 @@ export type MaybeReturnType<T> = T extends (...args: any) => any
? ReturnType<T> ? ReturnType<T>
: T : T
export type MaybeThisParameterType<T> = Exclude<T, Primitive> extends (...args: any) => any
? ThisParameterType<Exclude<T, Primitive>>
: any
export interface EditorEvents { export interface EditorEvents {
beforeCreate: { editor: Editor }, beforeCreate: { editor: Editor },
create: { editor: Editor }, create: { editor: Editor },

View File

@@ -0,0 +1,5 @@
export default function findDuplicates(items: any[]): any[] {
const filtered = items.filter((el, index) => items.indexOf(el) !== index)
return [...new Set(filtered)]
}

View File

@@ -7,7 +7,7 @@ import {
import { gapCursor } from 'prosemirror-gapcursor' import { gapCursor } from 'prosemirror-gapcursor'
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface NodeConfig<Options> { interface NodeConfig<Options, Storage> {
/** /**
* Allow gap cursor * Allow gap cursor
*/ */
@@ -17,6 +17,7 @@ declare module '@tiptap/core' {
| ((this: { | ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
parent: ParentConfig<NodeConfig<Options>>['allowGapCursor'], parent: ParentConfig<NodeConfig<Options>>['allowGapCursor'],
}) => boolean | null), }) => boolean | null),
} }
@@ -35,6 +36,7 @@ export const Gapcursor = Extension.create({
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
return { return {

View File

@@ -66,13 +66,14 @@ declare module '@tiptap/core' {
} }
} }
interface NodeConfig<Options> { interface NodeConfig<Options, Storage> {
/** /**
* Table Role * Table Role
*/ */
tableRole?: string | ((this: { tableRole?: string | ((this: {
name: string, name: string,
options: Options, options: Options,
storage: Storage,
parent: ParentConfig<NodeConfig<Options>>['tableRole'], parent: ParentConfig<NodeConfig<Options>>['tableRole'],
}) => string), }) => string),
} }
@@ -245,6 +246,7 @@ export const Table = Node.create<TableOptions>({
const context = { const context = {
name: extension.name, name: extension.name,
options: extension.options, options: extension.options,
storage: extension.storage,
} }
return { return {

View File

@@ -17,7 +17,13 @@ export const useEditor = (options: Partial<EditorOptions> = {}, deps: Dependency
setEditor(instance) setEditor(instance)
instance.on('transaction', forceUpdate) instance.on('transaction', () => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
forceUpdate()
})
})
})
return () => { return () => {
instance.destroy() instance.destroy()

View File

@@ -39,6 +39,8 @@ export type ContentComponent = ComponentInternalInstance & {
export class Editor extends CoreEditor { export class Editor extends CoreEditor {
private reactiveState: Ref<EditorState> private reactiveState: Ref<EditorState>
private reactiveExtensionStorage: Ref<Record<string, any>>
public vueRenderers = reactive<Map<string, VueRenderer>>(new Map()) public vueRenderers = reactive<Map<string, VueRenderer>>(new Map())
public contentComponent: ContentComponent | null = null public contentComponent: ContentComponent | null = null
@@ -47,9 +49,11 @@ export class Editor extends CoreEditor {
super(options) super(options)
this.reactiveState = useDebouncedRef(this.view.state) this.reactiveState = useDebouncedRef(this.view.state)
this.reactiveExtensionStorage = useDebouncedRef(this.extensionStorage)
this.on('transaction', () => { this.on('transaction', () => {
this.reactiveState.value = this.view.state this.reactiveState.value = this.view.state
this.reactiveExtensionStorage.value = this.extensionStorage
}) })
return markRaw(this) return markRaw(this)
@@ -61,6 +65,12 @@ export class Editor extends CoreEditor {
: this.view.state : this.view.state
} }
get storage() {
return this.reactiveExtensionStorage
? this.reactiveExtensionStorage.value
: super.storage
}
/** /**
* Register a ProseMirror plugin. * Register a ProseMirror plugin.
*/ */