diff --git a/.eslintrc.js b/.eslintrc.js
index e6d6392bd28..17290513051 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -31,7 +31,7 @@ module.exports = {
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
- 'plugin:vue/strongly-recommended',
+ 'plugin:vue/vue3-strongly-recommended',
'airbnb-base',
],
rules: {
diff --git a/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts
new file mode 100644
index 00000000000..de407ad7e0a
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts
@@ -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
+ },
+})
diff --git a/demos/src/Experiments/ExtensionStorage/React/index.html b/demos/src/Experiments/ExtensionStorage/React/index.html
new file mode 100644
index 00000000000..538363687bc
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/React/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/src/Experiments/ExtensionStorage/React/index.jsx b/demos/src/Experiments/ExtensionStorage/React/index.jsx
new file mode 100644
index 00000000000..3f75a1da2b5
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/React/index.jsx
@@ -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: `
+
+ This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
+
+
+ The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
+
+ `,
+ })
+
+ return (
+ <>
+ reactive storage: {editor?.storage.custom.foo}
+
+ >
+ )
+}
diff --git a/demos/src/Experiments/ExtensionStorage/React/styles.scss b/demos/src/Experiments/ExtensionStorage/React/styles.scss
new file mode 100644
index 00000000000..46b51a4e147
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/React/styles.scss
@@ -0,0 +1,6 @@
+/* Basic editor styles */
+.ProseMirror {
+ > * + * {
+ margin-top: 0.75em;
+ }
+}
diff --git a/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts
new file mode 100644
index 00000000000..de407ad7e0a
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts
@@ -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
+ },
+})
diff --git a/demos/src/Experiments/ExtensionStorage/Vue/index.html b/demos/src/Experiments/ExtensionStorage/Vue/index.html
new file mode 100644
index 00000000000..f0485cddc31
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/Vue/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/src/Experiments/ExtensionStorage/Vue/index.vue b/demos/src/Experiments/ExtensionStorage/Vue/index.vue
new file mode 100644
index 00000000000..a09406c4f7b
--- /dev/null
+++ b/demos/src/Experiments/ExtensionStorage/Vue/index.vue
@@ -0,0 +1,56 @@
+
+ reactive storage: {{ editor?.storage.custom.foo }}
+
+
+
+
+
+
diff --git a/docs/guide/custom-extensions.md b/docs/guide/custom-extensions.md
index e5e0a8fba77..eeb2e2102e3 100644
--- a/docs/guide/custom-extensions.md
+++ b/docs/guide/custom-extensions.md
@@ -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
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. Let’s walk through a few common use cases.
diff --git a/docs/guide/typescript.md b/docs/guide/typescript.md
index 5eb9c6aa48a..c329a354482 100644
--- a/docs/guide/typescript.md
+++ b/docs/guide/typescript.md
@@ -32,6 +32,25 @@ const CustomExtension = Extension.create({
})
```
+### Storage types
+To add types for your extension storage, you’ll 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
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:
diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts
index 8ad0bf6998a..4788471b2f8 100644
--- a/packages/core/src/Editor.ts
+++ b/packages/core/src/Editor.ts
@@ -50,6 +50,8 @@ export class Editor extends EventEmitter {
public isFocused = false
+ public extensionStorage: Record = {}
+
public options: EditorOptions = {
element: document.createElement('div'),
content: '',
@@ -100,6 +102,13 @@ export class Editor extends EventEmitter {
}, 0)
}
+ /**
+ * Returns the editor storage.
+ */
+ public get storage(): Record {
+ return this.extensionStorage
+ }
+
/**
* An object of all registered commands.
*/
diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts
index 55ce6f21bc7..7befb141509 100644
--- a/packages/core/src/Extension.ts
+++ b/packages/core/src/Extension.ts
@@ -5,7 +5,10 @@ import { Editor } from './Editor'
import { Node } from './Node'
import { Mark } from './Mark'
import mergeDeep from './utilities/mergeDeep'
+import callOrReturn from './utilities/callOrReturn'
+import getExtensionField from './helpers/getExtensionField'
import {
+ AnyConfig,
Extensions,
GlobalAttributes,
RawCommands,
@@ -15,7 +18,7 @@ import {
import { ExtensionConfig } from '.'
declare module '@tiptap/core' {
- interface ExtensionConfig {
+ interface ExtensionConfig {
[key: string]: any;
/**
@@ -33,13 +36,23 @@ declare module '@tiptap/core' {
*/
defaultOptions?: Options,
+ /**
+ * Default Storage
+ */
+ addStorage?: (this: {
+ name: string,
+ options: Options,
+ parent: ParentConfig>['addGlobalAttributes'],
+ }) => Storage,
+
/**
* Global attributes
*/
addGlobalAttributes?: (this: {
name: string,
options: Options,
- parent: ParentConfig>['addGlobalAttributes'],
+ storage: Storage,
+ parent: ParentConfig>['addGlobalAttributes'],
}) => GlobalAttributes | {},
/**
@@ -48,8 +61,9 @@ declare module '@tiptap/core' {
addCommands?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['addCommands'],
+ parent: ParentConfig>['addCommands'],
}) => Partial,
/**
@@ -58,8 +72,9 @@ declare module '@tiptap/core' {
addKeyboardShortcuts?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['addKeyboardShortcuts'],
+ parent: ParentConfig>['addKeyboardShortcuts'],
}) => {
[key: string]: KeyboardShortcutCommand,
},
@@ -70,8 +85,9 @@ declare module '@tiptap/core' {
addInputRules?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['addInputRules'],
+ parent: ParentConfig>['addInputRules'],
}) => InputRule[],
/**
@@ -80,8 +96,9 @@ declare module '@tiptap/core' {
addPasteRules?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['addPasteRules'],
+ parent: ParentConfig>['addPasteRules'],
}) => PasteRule[],
/**
@@ -90,8 +107,9 @@ declare module '@tiptap/core' {
addProseMirrorPlugins?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['addProseMirrorPlugins'],
+ parent: ParentConfig>['addProseMirrorPlugins'],
}) => Plugin[],
/**
@@ -100,7 +118,8 @@ declare module '@tiptap/core' {
addExtensions?: (this: {
name: string,
options: Options,
- parent: ParentConfig>['addExtensions'],
+ storage: Storage,
+ parent: ParentConfig>['addExtensions'],
}) => Extensions,
/**
@@ -110,7 +129,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['extendNodeSchema'],
+ storage: Storage,
+ parent: ParentConfig>['extendNodeSchema'],
},
extension: Node,
) => Record) | null,
@@ -122,7 +142,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['extendMarkSchema'],
+ storage: Storage,
+ parent: ParentConfig>['extendMarkSchema'],
},
extension: Mark,
) => Record) | null,
@@ -133,8 +154,9 @@ declare module '@tiptap/core' {
onBeforeCreate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onBeforeCreate'],
+ parent: ParentConfig>['onBeforeCreate'],
}) => void) | null,
/**
@@ -143,8 +165,9 @@ declare module '@tiptap/core' {
onCreate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onCreate'],
+ parent: ParentConfig>['onCreate'],
}) => void) | null,
/**
@@ -153,8 +176,9 @@ declare module '@tiptap/core' {
onUpdate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onUpdate'],
+ parent: ParentConfig>['onUpdate'],
}) => void) | null,
/**
@@ -163,8 +187,9 @@ declare module '@tiptap/core' {
onSelectionUpdate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onSelectionUpdate'],
+ parent: ParentConfig>['onSelectionUpdate'],
}) => void) | null,
/**
@@ -174,8 +199,9 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onTransaction'],
+ parent: ParentConfig>['onTransaction'],
},
props: {
transaction: Transaction,
@@ -189,8 +215,9 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onFocus'],
+ parent: ParentConfig>['onFocus'],
},
props: {
event: FocusEvent,
@@ -204,8 +231,9 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onBlur'],
+ parent: ParentConfig>['onBlur'],
},
props: {
event: FocusEvent,
@@ -218,13 +246,14 @@ declare module '@tiptap/core' {
onDestroy?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
- parent: ParentConfig>['onDestroy'],
+ parent: ParentConfig>['onDestroy'],
}) => void) | null,
}
}
-export class Extension {
+export class Extension {
type = 'extension'
name = 'extension'
@@ -235,12 +264,14 @@ export class Extension {
options: Options
+ storage: Storage
+
config: ExtensionConfig = {
name: this.name,
defaultOptions: {},
}
- constructor(config: Partial> = {}) {
+ constructor(config: Partial> = {}) {
this.config = {
...this.config,
...config,
@@ -248,10 +279,18 @@ export class Extension {
this.name = this.config.name
this.options = this.config.defaultOptions
+ this.storage = callOrReturn(getExtensionField(
+ this,
+ 'addStorage',
+ {
+ name: this.name,
+ options: this.options,
+ },
+ ))
}
- static create(config: Partial> = {}) {
- return new Extension(config)
+ static create(config: Partial> = {}) {
+ return new Extension(config)
}
configure(options: Partial = {}) {
@@ -264,8 +303,8 @@ export class Extension {
return extension
}
- extend(extendedConfig: Partial> = {}) {
- const extension = new Extension(extendedConfig)
+ extend(extendedConfig: Partial> = {}) {
+ const extension = new Extension(extendedConfig)
extension.parent = this
@@ -279,6 +318,15 @@ export class Extension {
? extendedConfig.defaultOptions
: extension.parent.options
+ extension.storage = callOrReturn(getExtensionField(
+ extension,
+ 'addStorage',
+ {
+ name: extension.name,
+ options: extension.options,
+ },
+ ))
+
return extension
}
}
diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts
index 0056179d868..afd5c0a7ad5 100644
--- a/packages/core/src/ExtensionManager.ts
+++ b/packages/core/src/ExtensionManager.ts
@@ -14,6 +14,7 @@ import splitExtensions from './helpers/splitExtensions'
import getAttributesFromExtensions from './helpers/getAttributesFromExtensions'
import getRenderedAttributes from './helpers/getRenderedAttributes'
import callOrReturn from './utilities/callOrReturn'
+import findDuplicates from './utilities/findDuplicates'
import { NodeConfig } from '.'
export default class ExtensionManager {
@@ -32,9 +33,13 @@ export default class ExtensionManager {
this.schema = getSchemaByResolvedExtensions(this.extensions)
this.extensions.forEach(extension => {
+ // store extension storage in editor
+ this.editor.extensionStorage[extension.name] = extension.storage
+
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema),
}
@@ -130,7 +135,14 @@ export default class ExtensionManager {
}
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 {
@@ -139,6 +151,7 @@ export default class ExtensionManager {
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
}
const addExtensions = getExtensionField(
@@ -184,6 +197,7 @@ export default class ExtensionManager {
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema),
}
@@ -223,6 +237,7 @@ export default class ExtensionManager {
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
editor,
type: getSchemaTypeByName(extension.name, this.schema),
}
@@ -313,6 +328,7 @@ export default class ExtensionManager {
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
editor,
type: getNodeType(extension.name, this.schema),
}
diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts
index 8c8a2cbf7f9..18212aede1a 100644
--- a/packages/core/src/Mark.ts
+++ b/packages/core/src/Mark.ts
@@ -8,7 +8,10 @@ import { Plugin, Transaction } from 'prosemirror-state'
import { InputRule } from './InputRule'
import { PasteRule } from './PasteRule'
import mergeDeep from './utilities/mergeDeep'
+import callOrReturn from './utilities/callOrReturn'
+import getExtensionField from './helpers/getExtensionField'
import {
+ AnyConfig,
Extensions,
Attributes,
RawCommands,
@@ -21,7 +24,7 @@ import { MarkConfig } from '.'
import { Editor } from './Editor'
declare module '@tiptap/core' {
- export interface MarkConfig {
+ export interface MarkConfig {
[key: string]: any;
/**
@@ -39,13 +42,23 @@ declare module '@tiptap/core' {
*/
defaultOptions?: Options,
+ /**
+ * Default Storage
+ */
+ addStorage?: (this: {
+ name: string,
+ options: Options,
+ parent: ParentConfig>['addGlobalAttributes'],
+ }) => Storage,
+
/**
* Global attributes
*/
addGlobalAttributes?: (this: {
name: string,
options: Options,
- parent: ParentConfig>['addGlobalAttributes'],
+ storage: Storage,
+ parent: ParentConfig>['addGlobalAttributes'],
}) => GlobalAttributes | {},
/**
@@ -54,9 +67,10 @@ declare module '@tiptap/core' {
addCommands?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['addCommands'],
+ parent: ParentConfig>['addCommands'],
}) => Partial,
/**
@@ -65,9 +79,10 @@ declare module '@tiptap/core' {
addKeyboardShortcuts?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['addKeyboardShortcuts'],
+ parent: ParentConfig>['addKeyboardShortcuts'],
}) => {
[key: string]: KeyboardShortcutCommand,
},
@@ -78,9 +93,10 @@ declare module '@tiptap/core' {
addInputRules?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['addInputRules'],
+ parent: ParentConfig>['addInputRules'],
}) => InputRule[],
/**
@@ -89,9 +105,10 @@ declare module '@tiptap/core' {
addPasteRules?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['addPasteRules'],
+ parent: ParentConfig>['addPasteRules'],
}) => PasteRule[],
/**
@@ -100,9 +117,10 @@ declare module '@tiptap/core' {
addProseMirrorPlugins?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['addProseMirrorPlugins'],
+ parent: ParentConfig>['addProseMirrorPlugins'],
}) => Plugin[],
/**
@@ -111,7 +129,8 @@ declare module '@tiptap/core' {
addExtensions?: (this: {
name: string,
options: Options,
- parent: ParentConfig>['addExtensions'],
+ storage: Storage,
+ parent: ParentConfig>['addExtensions'],
}) => Extensions,
/**
@@ -121,7 +140,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['extendNodeSchema'],
+ storage: Storage,
+ parent: ParentConfig>['extendNodeSchema'],
},
extension: Node,
) => Record) | null,
@@ -133,7 +153,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['extendMarkSchema'],
+ storage: Storage,
+ parent: ParentConfig>['extendMarkSchema'],
},
extension: Mark,
) => Record) | null,
@@ -144,9 +165,10 @@ declare module '@tiptap/core' {
onBeforeCreate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onBeforeCreate'],
+ parent: ParentConfig>['onBeforeCreate'],
}) => void) | null,
/**
@@ -155,9 +177,10 @@ declare module '@tiptap/core' {
onCreate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onCreate'],
+ parent: ParentConfig>['onCreate'],
}) => void) | null,
/**
@@ -166,9 +189,10 @@ declare module '@tiptap/core' {
onUpdate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onUpdate'],
+ parent: ParentConfig>['onUpdate'],
}) => void) | null,
/**
@@ -177,9 +201,10 @@ declare module '@tiptap/core' {
onSelectionUpdate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onSelectionUpdate'],
+ parent: ParentConfig>['onSelectionUpdate'],
}) => void) | null,
/**
@@ -189,9 +214,10 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onTransaction'],
+ parent: ParentConfig>['onTransaction'],
},
props: {
transaction: Transaction,
@@ -205,9 +231,10 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onFocus'],
+ parent: ParentConfig>['onFocus'],
},
props: {
event: FocusEvent,
@@ -221,9 +248,10 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onBlur'],
+ parent: ParentConfig>['onBlur'],
},
props: {
event: FocusEvent,
@@ -236,9 +264,10 @@ declare module '@tiptap/core' {
onDestroy?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: MarkType,
- parent: ParentConfig>['onDestroy'],
+ parent: ParentConfig>['onDestroy'],
}) => void) | null,
/**
@@ -252,7 +281,8 @@ declare module '@tiptap/core' {
inclusive?: MarkSpec['inclusive'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['inclusive'],
+ storage: Storage,
+ parent: ParentConfig>['inclusive'],
}) => MarkSpec['inclusive']),
/**
@@ -261,7 +291,8 @@ declare module '@tiptap/core' {
excludes?: MarkSpec['excludes'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['excludes'],
+ storage: Storage,
+ parent: ParentConfig>['excludes'],
}) => MarkSpec['excludes']),
/**
@@ -270,7 +301,8 @@ declare module '@tiptap/core' {
group?: MarkSpec['group'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['group'],
+ storage: Storage,
+ parent: ParentConfig>['group'],
}) => MarkSpec['group']),
/**
@@ -279,7 +311,8 @@ declare module '@tiptap/core' {
spanning?: MarkSpec['spanning'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['spanning'],
+ storage: Storage,
+ parent: ParentConfig>['spanning'],
}) => MarkSpec['spanning']),
/**
@@ -288,7 +321,8 @@ declare module '@tiptap/core' {
code?: boolean | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['code'],
+ storage: Storage,
+ parent: ParentConfig>['code'],
}) => boolean),
/**
@@ -298,7 +332,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['parseHTML'],
+ storage: Storage,
+ parent: ParentConfig>['parseHTML'],
},
) => MarkSpec['parseDOM'],
@@ -309,7 +344,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['renderHTML'],
+ storage: Storage,
+ parent: ParentConfig>['renderHTML'],
},
props: {
mark: ProseMirrorMark,
@@ -324,13 +360,14 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['addAttributes'],
+ storage: Storage,
+ parent: ParentConfig>['addAttributes'],
},
) => Attributes | {},
}
}
-export class Mark {
+export class Mark {
type = 'mark'
name = 'mark'
@@ -341,12 +378,14 @@ export class Mark {
options: Options
+ storage: Storage
+
config: MarkConfig = {
name: this.name,
defaultOptions: {},
}
- constructor(config: Partial> = {}) {
+ constructor(config: Partial> = {}) {
this.config = {
...this.config,
...config,
@@ -354,10 +393,18 @@ export class Mark {
this.name = this.config.name
this.options = this.config.defaultOptions
+ this.storage = callOrReturn(getExtensionField(
+ this,
+ 'addStorage',
+ {
+ name: this.name,
+ options: this.options,
+ },
+ ))
}
- static create(config: Partial> = {}) {
- return new Mark(config)
+ static create(config: Partial> = {}) {
+ return new Mark(config)
}
configure(options: Partial = {}) {
@@ -370,8 +417,8 @@ export class Mark {
return extension
}
- extend(extendedConfig: Partial> = {}) {
- const extension = new Mark(extendedConfig)
+ extend(extendedConfig: Partial> = {}) {
+ const extension = new Mark(extendedConfig)
extension.parent = this
@@ -385,6 +432,15 @@ export class Mark {
? extendedConfig.defaultOptions
: extension.parent.options
+ extension.storage = callOrReturn(getExtensionField(
+ extension,
+ 'addStorage',
+ {
+ name: extension.name,
+ options: extension.options,
+ },
+ ))
+
return extension
}
}
diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts
index ec0cfd8aaf4..1f9b56ce3b5 100644
--- a/packages/core/src/Node.ts
+++ b/packages/core/src/Node.ts
@@ -8,7 +8,10 @@ import { Plugin, Transaction } from 'prosemirror-state'
import { InputRule } from './InputRule'
import { PasteRule } from './PasteRule'
import mergeDeep from './utilities/mergeDeep'
+import callOrReturn from './utilities/callOrReturn'
+import getExtensionField from './helpers/getExtensionField'
import {
+ AnyConfig,
Extensions,
Attributes,
NodeViewRenderer,
@@ -21,7 +24,7 @@ import { NodeConfig } from '.'
import { Editor } from './Editor'
declare module '@tiptap/core' {
- interface NodeConfig {
+ interface NodeConfig {
[key: string]: any;
/**
@@ -39,13 +42,23 @@ declare module '@tiptap/core' {
*/
defaultOptions?: Options,
+ /**
+ * Default Storage
+ */
+ addStorage?: (this: {
+ name: string,
+ options: Options,
+ parent: ParentConfig>['addGlobalAttributes'],
+ }) => Storage,
+
/**
* Global attributes
*/
addGlobalAttributes?: (this: {
name: string,
options: Options,
- parent: ParentConfig>['addGlobalAttributes'],
+ storage: Storage,
+ parent: ParentConfig>['addGlobalAttributes'],
}) => GlobalAttributes | {},
/**
@@ -54,9 +67,10 @@ declare module '@tiptap/core' {
addCommands?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['addCommands'],
+ parent: ParentConfig>['addCommands'],
}) => Partial,
/**
@@ -65,9 +79,10 @@ declare module '@tiptap/core' {
addKeyboardShortcuts?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['addKeyboardShortcuts'],
+ parent: ParentConfig>['addKeyboardShortcuts'],
}) => {
[key: string]: KeyboardShortcutCommand,
},
@@ -78,9 +93,10 @@ declare module '@tiptap/core' {
addInputRules?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['addInputRules'],
+ parent: ParentConfig>['addInputRules'],
}) => InputRule[],
/**
@@ -89,9 +105,10 @@ declare module '@tiptap/core' {
addPasteRules?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['addPasteRules'],
+ parent: ParentConfig>['addPasteRules'],
}) => PasteRule[],
/**
@@ -100,9 +117,10 @@ declare module '@tiptap/core' {
addProseMirrorPlugins?: (this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['addProseMirrorPlugins'],
+ parent: ParentConfig>['addProseMirrorPlugins'],
}) => Plugin[],
/**
@@ -111,7 +129,8 @@ declare module '@tiptap/core' {
addExtensions?: (this: {
name: string,
options: Options,
- parent: ParentConfig>['addExtensions'],
+ storage: Storage,
+ parent: ParentConfig>['addExtensions'],
}) => Extensions,
/**
@@ -121,7 +140,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['extendNodeSchema'],
+ storage: Storage,
+ parent: ParentConfig>['extendNodeSchema'],
},
extension: Node,
) => Record) | null,
@@ -133,7 +153,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['extendMarkSchema'],
+ storage: Storage,
+ parent: ParentConfig>['extendMarkSchema'],
},
extension: Node,
) => Record) | null,
@@ -144,9 +165,10 @@ declare module '@tiptap/core' {
onBeforeCreate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onBeforeCreate'],
+ parent: ParentConfig>['onBeforeCreate'],
}) => void) | null,
/**
@@ -155,9 +177,10 @@ declare module '@tiptap/core' {
onCreate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onCreate'],
+ parent: ParentConfig>['onCreate'],
}) => void) | null,
/**
@@ -166,9 +189,10 @@ declare module '@tiptap/core' {
onUpdate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onUpdate'],
+ parent: ParentConfig>['onUpdate'],
}) => void) | null,
/**
@@ -177,9 +201,10 @@ declare module '@tiptap/core' {
onSelectionUpdate?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onSelectionUpdate'],
+ parent: ParentConfig>['onSelectionUpdate'],
}) => void) | null,
/**
@@ -189,9 +214,10 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onTransaction'],
+ parent: ParentConfig>['onTransaction'],
},
props: {
transaction: Transaction,
@@ -205,9 +231,10 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onFocus'],
+ parent: ParentConfig>['onFocus'],
},
props: {
event: FocusEvent,
@@ -221,9 +248,10 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onBlur'],
+ parent: ParentConfig>['onBlur'],
},
props: {
event: FocusEvent,
@@ -236,9 +264,10 @@ declare module '@tiptap/core' {
onDestroy?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['onDestroy'],
+ parent: ParentConfig>['onDestroy'],
}) => void) | null,
/**
@@ -247,9 +276,10 @@ declare module '@tiptap/core' {
addNodeView?: ((this: {
name: string,
options: Options,
+ storage: Storage,
editor: Editor,
type: NodeType,
- parent: ParentConfig>['addNodeView'],
+ parent: ParentConfig>['addNodeView'],
}) => NodeViewRenderer) | null,
/**
@@ -263,7 +293,8 @@ declare module '@tiptap/core' {
content?: NodeSpec['content'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['content'],
+ storage: Storage,
+ parent: ParentConfig>['content'],
}) => NodeSpec['content']),
/**
@@ -272,7 +303,8 @@ declare module '@tiptap/core' {
marks?: NodeSpec['marks'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['marks'],
+ storage: Storage,
+ parent: ParentConfig>['marks'],
}) => NodeSpec['marks']),
/**
@@ -281,7 +313,8 @@ declare module '@tiptap/core' {
group?: NodeSpec['group'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['group'],
+ storage: Storage,
+ parent: ParentConfig>['group'],
}) => NodeSpec['group']),
/**
@@ -290,7 +323,8 @@ declare module '@tiptap/core' {
inline?: NodeSpec['inline'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['inline'],
+ storage: Storage,
+ parent: ParentConfig>['inline'],
}) => NodeSpec['inline']),
/**
@@ -299,7 +333,8 @@ declare module '@tiptap/core' {
atom?: NodeSpec['atom'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['atom'],
+ storage: Storage,
+ parent: ParentConfig>['atom'],
}) => NodeSpec['atom']),
/**
@@ -308,7 +343,8 @@ declare module '@tiptap/core' {
selectable?: NodeSpec['selectable'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['selectable'],
+ storage: Storage,
+ parent: ParentConfig>['selectable'],
}) => NodeSpec['selectable']),
/**
@@ -317,7 +353,8 @@ declare module '@tiptap/core' {
draggable?: NodeSpec['draggable'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['draggable'],
+ storage: Storage,
+ parent: ParentConfig>['draggable'],
}) => NodeSpec['draggable']),
/**
@@ -326,7 +363,8 @@ declare module '@tiptap/core' {
code?: NodeSpec['code'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['code'],
+ storage: Storage,
+ parent: ParentConfig>['code'],
}) => NodeSpec['code']),
/**
@@ -335,7 +373,8 @@ declare module '@tiptap/core' {
defining?: NodeSpec['defining'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['defining'],
+ storage: Storage,
+ parent: ParentConfig>['defining'],
}) => NodeSpec['defining']),
/**
@@ -344,7 +383,8 @@ declare module '@tiptap/core' {
isolating?: NodeSpec['isolating'] | ((this: {
name: string,
options: Options,
- parent: ParentConfig>['isolating'],
+ storage: Storage,
+ parent: ParentConfig>['isolating'],
}) => NodeSpec['isolating']),
/**
@@ -354,7 +394,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['parseHTML'],
+ storage: Storage,
+ parent: ParentConfig>['parseHTML'],
},
) => NodeSpec['parseDOM'],
@@ -365,7 +406,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['renderHTML'],
+ storage: Storage,
+ parent: ParentConfig>['renderHTML'],
},
props: {
node: ProseMirrorNode,
@@ -380,7 +422,8 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['renderText'],
+ storage: Storage,
+ parent: ParentConfig>['renderText'],
},
props: {
node: ProseMirrorNode,
@@ -397,13 +440,14 @@ declare module '@tiptap/core' {
this: {
name: string,
options: Options,
- parent: ParentConfig>['addAttributes'],
+ storage: Storage,
+ parent: ParentConfig>['addAttributes'],
},
) => Attributes | {},
}
}
-export class Node {
+export class Node {
type = 'node'
name = 'node'
@@ -414,12 +458,14 @@ export class Node {
options: Options
+ storage: Storage
+
config: NodeConfig = {
name: this.name,
defaultOptions: {},
}
- constructor(config: Partial> = {}) {
+ constructor(config: Partial> = {}) {
this.config = {
...this.config,
...config,
@@ -427,10 +473,18 @@ export class Node {
this.name = this.config.name
this.options = this.config.defaultOptions
+ this.storage = callOrReturn(getExtensionField(
+ this,
+ 'addStorage',
+ {
+ name: this.name,
+ options: this.options,
+ },
+ ))
}
- static create(config: Partial> = {}) {
- return new Node(config)
+ static create(config: Partial> = {}) {
+ return new Node(config)
}
configure(options: Partial = {}) {
@@ -443,8 +497,8 @@ export class Node {
return extension
}
- extend(extendedConfig: Partial> = {}) {
- const extension = new Node(extendedConfig)
+ extend(extendedConfig: Partial> = {}) {
+ const extension = new Node(extendedConfig)
extension.parent = this
@@ -458,6 +512,15 @@ export class Node {
? extendedConfig.defaultOptions
: extension.parent.options
+ extension.storage = callOrReturn(getExtensionField(
+ extension,
+ 'addStorage',
+ {
+ name: extension.name,
+ options: extension.options,
+ },
+ ))
+
return extension
}
}
diff --git a/packages/core/src/helpers/getAttributesFromExtensions.ts b/packages/core/src/helpers/getAttributesFromExtensions.ts
index 1a81bf5f9dd..d76a6a2dc0c 100644
--- a/packages/core/src/helpers/getAttributesFromExtensions.ts
+++ b/packages/core/src/helpers/getAttributesFromExtensions.ts
@@ -30,6 +30,7 @@ export default function getAttributesFromExtensions(extensions: Extensions): Ext
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
}
const addGlobalAttributes = getExtensionField(
@@ -67,6 +68,7 @@ export default function getAttributesFromExtensions(extensions: Extensions): Ext
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
}
const addAttributes = getExtensionField(
diff --git a/packages/core/src/helpers/getExtensionField.ts b/packages/core/src/helpers/getExtensionField.ts
index cfe03c75b71..90df54ff2e4 100644
--- a/packages/core/src/helpers/getExtensionField.ts
+++ b/packages/core/src/helpers/getExtensionField.ts
@@ -1,9 +1,9 @@
-import { AnyExtension, RemoveThis } from '../types'
+import { AnyExtension, RemoveThis, MaybeThisParameterType } from '../types'
export default function getExtensionField(
extension: AnyExtension,
field: string,
- context: Record = {},
+ context?: Omit, 'parent'>,
): RemoveThis {
if (extension.config[field] === undefined && extension.parent) {
diff --git a/packages/core/src/helpers/getSchemaByResolvedExtensions.ts b/packages/core/src/helpers/getSchemaByResolvedExtensions.ts
index 2ef0063964d..bf899c01359 100644
--- a/packages/core/src/helpers/getSchemaByResolvedExtensions.ts
+++ b/packages/core/src/helpers/getSchemaByResolvedExtensions.ts
@@ -29,6 +29,7 @@ export default function getSchemaByResolvedExtensions(extensions: Extensions): S
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
}
const extraNodeFields = extensions.reduce((fields, e) => {
@@ -91,6 +92,7 @@ export default function getSchemaByResolvedExtensions(extensions: Extensions): S
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
}
const extraMarkFields = extensions.reduce((fields, e) => {
diff --git a/packages/core/src/helpers/isList.ts b/packages/core/src/helpers/isList.ts
index 424890b8e14..1705458d7f4 100644
--- a/packages/core/src/helpers/isList.ts
+++ b/packages/core/src/helpers/isList.ts
@@ -15,6 +15,7 @@ export default function isList(name: string, extensions: Extensions): boolean {
const context = {
name: extension.name,
options: extension.options,
+ storage: extension.storage,
}
const group = callOrReturn(getExtensionField(extension, 'group', context))
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 241ceff0924..8c622edce44 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -56,10 +56,10 @@ export { default as posToDOMRect } from './helpers/posToDOMRect'
export interface Commands {}
// eslint-disable-next-line
-export interface ExtensionConfig {}
+export interface ExtensionConfig {}
// eslint-disable-next-line
-export interface NodeConfig {}
+export interface NodeConfig {}
// eslint-disable-next-line
-export interface MarkConfig