From d01ab1ccb5ccbcfa605f493aaa934412a5fb6864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 19 Oct 2021 23:28:22 +0200 Subject: [PATCH 1/8] add first storage implementation --- .../ExtensionStorage/React/CustomExtension.ts | 25 +++++++++ .../ExtensionStorage/React/index.html | 15 +++++ .../ExtensionStorage/React/index.jsx | 33 +++++++++++ .../ExtensionStorage/React/styles.scss | 6 ++ .../ExtensionStorage/Vue/CustomExtension.ts | 25 +++++++++ .../ExtensionStorage/Vue/index.html | 15 +++++ .../ExtensionStorage/Vue/index.vue | 56 +++++++++++++++++++ packages/core/src/Editor.ts | 10 ++++ packages/core/src/Extension.ts | 12 +++- packages/core/src/ExtensionManager.ts | 13 +++++ packages/core/src/Mark.ts | 12 +++- packages/core/src/Node.ts | 12 +++- packages/core/src/index.ts | 3 + packages/react/src/useEditor.ts | 8 ++- packages/vue-3/src/Editor.ts | 12 +++- 15 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts create mode 100644 demos/src/Experiments/ExtensionStorage/React/index.html create mode 100644 demos/src/Experiments/ExtensionStorage/React/index.jsx create mode 100644 demos/src/Experiments/ExtensionStorage/React/styles.scss create mode 100644 demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts create mode 100644 demos/src/Experiments/ExtensionStorage/Vue/index.html create mode 100644 demos/src/Experiments/ExtensionStorage/Vue/index.vue diff --git a/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts new file mode 100644 index 00000000000..03795754b2c --- /dev/null +++ b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts @@ -0,0 +1,25 @@ +import { Extension } from '@tiptap/core' + +declare module '@tiptap/core' { + interface Storage { + custom: { + foo: number, + } + } +} + +export const CustomExtension = Extension.create({ + name: 'custom', + + onUpdate() { + this.editor.storage.custom.foo++ + }, + + addStorage() { + return { + custom: { + foo: 123, + }, + } + }, +}) 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..03795754b2c --- /dev/null +++ b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts @@ -0,0 +1,25 @@ +import { Extension } from '@tiptap/core' + +declare module '@tiptap/core' { + interface Storage { + custom: { + foo: number, + } + } +} + +export const CustomExtension = Extension.create({ + name: 'custom', + + onUpdate() { + this.editor.storage.custom.foo++ + }, + + addStorage() { + return { + custom: { + foo: 123, + }, + } + }, +}) 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 @@ + + + + + diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index 8ad0bf6998a..44c858d4e3c 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -27,6 +27,7 @@ import { TextSerializer, EditorEvents, } from './types' +import { EditorStorage } from '.' import * as extensions from './extensions' import style from './style' @@ -50,6 +51,8 @@ export class Editor extends EventEmitter { public isFocused = false + public editorStorage!: EditorStorage + public options: EditorOptions = { element: document.createElement('div'), content: '', @@ -100,6 +103,13 @@ export class Editor extends EventEmitter { }, 0) } + /** + * Returns the editor storage. + */ + public get storage(): EditorStorage { + return this.editorStorage + } + /** * An object of all registered commands. */ diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts index 55ce6f21bc7..9885028442a 100644 --- a/packages/core/src/Extension.ts +++ b/packages/core/src/Extension.ts @@ -12,7 +12,7 @@ import { ParentConfig, KeyboardShortcutCommand, } from './types' -import { ExtensionConfig } from '.' +import { ExtensionConfig, EditorStorage } from '.' declare module '@tiptap/core' { interface ExtensionConfig { @@ -33,6 +33,16 @@ declare module '@tiptap/core' { */ defaultOptions?: Options, + /** + * Storage + */ + addStorage?: (this: { + name: string, + options: Options, + editor: Editor, + parent: ParentConfig>['addStorage'], + }) => Partial, + /** * Global attributes */ diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index 0056179d868..e2013e8ccf1 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -47,6 +47,19 @@ export default class ExtensionManager { } } + const storage = getExtensionField( + extension, + 'addStorage', + context, + ) + + if (storage) { + this.editor.editorStorage = { + ...this.editor.editorStorage, + ...storage(), + } + } + const onBeforeCreate = getExtensionField( extension, 'onBeforeCreate', diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts index 8c8a2cbf7f9..235d28226d8 100644 --- a/packages/core/src/Mark.ts +++ b/packages/core/src/Mark.ts @@ -17,7 +17,7 @@ import { KeyboardShortcutCommand, } from './types' import { Node } from './Node' -import { MarkConfig } from '.' +import { MarkConfig, EditorStorage } from '.' import { Editor } from './Editor' declare module '@tiptap/core' { @@ -39,6 +39,16 @@ declare module '@tiptap/core' { */ defaultOptions?: Options, + /** + * Storage + */ + addStorage?: (this: { + name: string, + options: Options, + editor: Editor, + parent: ParentConfig>['addStorage'], + }) => Partial, + /** * Global attributes */ diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts index ec0cfd8aaf4..849bbe7f17e 100644 --- a/packages/core/src/Node.ts +++ b/packages/core/src/Node.ts @@ -17,7 +17,7 @@ import { ParentConfig, KeyboardShortcutCommand, } from './types' -import { NodeConfig } from '.' +import { NodeConfig, EditorStorage } from '.' import { Editor } from './Editor' declare module '@tiptap/core' { @@ -39,6 +39,16 @@ declare module '@tiptap/core' { */ defaultOptions?: Options, + /** + * Storage + */ + addStorage?: (this: { + name: string, + options: Options, + editor: Editor, + parent: ParentConfig>['addStorage'], + }) => Partial, + /** * Global attributes */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 241ceff0924..d0d71460d4e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -52,6 +52,9 @@ export { default as isNodeSelection } from './helpers/isNodeSelection' export { default as isTextSelection } from './helpers/isTextSelection' export { default as posToDOMRect } from './helpers/posToDOMRect' +// eslint-disable-next-line +export interface EditorStorage {} + // eslint-disable-next-line export interface Commands {} diff --git a/packages/react/src/useEditor.ts b/packages/react/src/useEditor.ts index f2f3fcda7b3..32896f235db 100644 --- a/packages/react/src/useEditor.ts +++ b/packages/react/src/useEditor.ts @@ -17,7 +17,13 @@ export const useEditor = (options: Partial = {}, deps: Dependency setEditor(instance) - instance.on('transaction', forceUpdate) + instance.on('transaction', () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + forceUpdate() + }) + }) + }) return () => { instance.destroy() diff --git a/packages/vue-3/src/Editor.ts b/packages/vue-3/src/Editor.ts index 5c61c6c8d3b..088dbf1b89f 100644 --- a/packages/vue-3/src/Editor.ts +++ b/packages/vue-3/src/Editor.ts @@ -1,5 +1,5 @@ import { EditorState, Plugin, PluginKey } from 'prosemirror-state' -import { Editor as CoreEditor, EditorOptions } from '@tiptap/core' +import { Editor as CoreEditor, EditorOptions, EditorStorage } from '@tiptap/core' import { markRaw, Ref, @@ -39,6 +39,8 @@ export type ContentComponent = ComponentInternalInstance & { export class Editor extends CoreEditor { private reactiveState: Ref + private reactiveStorage: Ref + public vueRenderers = reactive>(new Map()) public contentComponent: ContentComponent | null = null @@ -47,9 +49,11 @@ export class Editor extends CoreEditor { super(options) this.reactiveState = useDebouncedRef(this.view.state) + this.reactiveStorage = useDebouncedRef(this.editorStorage) this.on('transaction', () => { this.reactiveState.value = this.view.state + this.reactiveStorage.value = this.editorStorage }) return markRaw(this) @@ -61,6 +65,12 @@ export class Editor extends CoreEditor { : this.view.state } + get storage() { + return this.reactiveStorage + ? this.reactiveStorage.value + : super.storage + } + /** * Register a ProseMirror plugin. */ From 05b75b30b03bf55b1a7df92fe0c770ca84bc06a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 19 Oct 2021 23:47:40 +0200 Subject: [PATCH 2/8] fix --- .../src/Experiments/ExtensionStorage/React/CustomExtension.ts | 4 ++-- demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts index 03795754b2c..645085dbd97 100644 --- a/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts +++ b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts @@ -1,7 +1,7 @@ import { Extension } from '@tiptap/core' declare module '@tiptap/core' { - interface Storage { + interface EditorStorage { custom: { foo: number, } @@ -12,7 +12,7 @@ export const CustomExtension = Extension.create({ name: 'custom', onUpdate() { - this.editor.storage.custom.foo++ + this.editor.storage.custom.foo += 1 }, addStorage() { diff --git a/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts index 03795754b2c..645085dbd97 100644 --- a/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts +++ b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts @@ -1,7 +1,7 @@ import { Extension } from '@tiptap/core' declare module '@tiptap/core' { - interface Storage { + interface EditorStorage { custom: { foo: number, } @@ -12,7 +12,7 @@ export const CustomExtension = Extension.create({ name: 'custom', onUpdate() { - this.editor.storage.custom.foo++ + this.editor.storage.custom.foo += 1 }, addStorage() { From d23603dd5d7227aff3f7b28dec6c6b9e4e3a6bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 21 Oct 2021 15:30:43 +0200 Subject: [PATCH 3/8] add new Storage prop type to extensions --- .../ExtensionStorage/React/CustomExtension.ts | 22 +-- .../ExtensionStorage/Vue/CustomExtension.ts | 22 +-- packages/core/src/Editor.ts | 7 +- packages/core/src/Extension.ts | 96 ++++++++---- packages/core/src/ExtensionManager.ts | 17 +-- packages/core/src/Mark.ts | 122 ++++++++++----- packages/core/src/Node.ts | 141 ++++++++++++------ packages/core/src/index.ts | 9 +- packages/extension-gapcursor/src/gapcursor.ts | 3 +- packages/extension-table/src/table.ts | 3 +- packages/vue-3/src/Editor.ts | 12 +- 11 files changed, 284 insertions(+), 170 deletions(-) diff --git a/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts index 645085dbd97..de407ad7e0a 100644 --- a/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts +++ b/demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts @@ -1,25 +1,19 @@ import { Extension } from '@tiptap/core' -declare module '@tiptap/core' { - interface EditorStorage { - custom: { - foo: number, - } - } +type CustomStorage = { + foo: number, } -export const CustomExtension = Extension.create({ +export const CustomExtension = Extension.create<{}, CustomStorage>({ name: 'custom', - onUpdate() { - this.editor.storage.custom.foo += 1 - }, - addStorage() { return { - custom: { - foo: 123, - }, + foo: 123, } }, + + onUpdate() { + this.storage.foo += 1 + }, }) diff --git a/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts index 645085dbd97..de407ad7e0a 100644 --- a/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts +++ b/demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts @@ -1,25 +1,19 @@ import { Extension } from '@tiptap/core' -declare module '@tiptap/core' { - interface EditorStorage { - custom: { - foo: number, - } - } +type CustomStorage = { + foo: number, } -export const CustomExtension = Extension.create({ +export const CustomExtension = Extension.create<{}, CustomStorage>({ name: 'custom', - onUpdate() { - this.editor.storage.custom.foo += 1 - }, - addStorage() { return { - custom: { - foo: 123, - }, + foo: 123, } }, + + onUpdate() { + this.storage.foo += 1 + }, }) diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index 44c858d4e3c..4788471b2f8 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -27,7 +27,6 @@ import { TextSerializer, EditorEvents, } from './types' -import { EditorStorage } from '.' import * as extensions from './extensions' import style from './style' @@ -51,7 +50,7 @@ export class Editor extends EventEmitter { public isFocused = false - public editorStorage!: EditorStorage + public extensionStorage: Record = {} public options: EditorOptions = { element: document.createElement('div'), @@ -106,8 +105,8 @@ export class Editor extends EventEmitter { /** * Returns the editor storage. */ - public get storage(): EditorStorage { - return this.editorStorage + public get storage(): Record { + return this.extensionStorage } /** diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts index 9885028442a..7befb141509 100644 --- a/packages/core/src/Extension.ts +++ b/packages/core/src/Extension.ts @@ -5,17 +5,20 @@ 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, ParentConfig, KeyboardShortcutCommand, } from './types' -import { ExtensionConfig, EditorStorage } from '.' +import { ExtensionConfig } from '.' declare module '@tiptap/core' { - interface ExtensionConfig { + interface ExtensionConfig { [key: string]: any; /** @@ -34,14 +37,13 @@ declare module '@tiptap/core' { defaultOptions?: Options, /** - * Storage + * Default Storage */ addStorage?: (this: { name: string, options: Options, - editor: Editor, - parent: ParentConfig>['addStorage'], - }) => Partial, + parent: ParentConfig>['addGlobalAttributes'], + }) => Storage, /** * Global attributes @@ -49,7 +51,8 @@ declare module '@tiptap/core' { addGlobalAttributes?: (this: { name: string, options: Options, - parent: ParentConfig>['addGlobalAttributes'], + storage: Storage, + parent: ParentConfig>['addGlobalAttributes'], }) => GlobalAttributes | {}, /** @@ -58,8 +61,9 @@ declare module '@tiptap/core' { addCommands?: (this: { name: string, options: Options, + storage: Storage, editor: Editor, - parent: ParentConfig>['addCommands'], + parent: ParentConfig>['addCommands'], }) => Partial, /** @@ -68,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, }, @@ -80,8 +85,9 @@ declare module '@tiptap/core' { addInputRules?: (this: { name: string, options: Options, + storage: Storage, editor: Editor, - parent: ParentConfig>['addInputRules'], + parent: ParentConfig>['addInputRules'], }) => InputRule[], /** @@ -90,8 +96,9 @@ declare module '@tiptap/core' { addPasteRules?: (this: { name: string, options: Options, + storage: Storage, editor: Editor, - parent: ParentConfig>['addPasteRules'], + parent: ParentConfig>['addPasteRules'], }) => PasteRule[], /** @@ -100,8 +107,9 @@ declare module '@tiptap/core' { addProseMirrorPlugins?: (this: { name: string, options: Options, + storage: Storage, editor: Editor, - parent: ParentConfig>['addProseMirrorPlugins'], + parent: ParentConfig>['addProseMirrorPlugins'], }) => Plugin[], /** @@ -110,7 +118,8 @@ declare module '@tiptap/core' { addExtensions?: (this: { name: string, options: Options, - parent: ParentConfig>['addExtensions'], + storage: Storage, + parent: ParentConfig>['addExtensions'], }) => Extensions, /** @@ -120,7 +129,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['extendNodeSchema'], + storage: Storage, + parent: ParentConfig>['extendNodeSchema'], }, extension: Node, ) => Record) | null, @@ -132,7 +142,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['extendMarkSchema'], + storage: Storage, + parent: ParentConfig>['extendMarkSchema'], }, extension: Mark, ) => Record) | null, @@ -143,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, /** @@ -153,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, /** @@ -163,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, /** @@ -173,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, /** @@ -184,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, @@ -199,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, @@ -214,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, @@ -228,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' @@ -245,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, @@ -258,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 = {}) { @@ -274,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 @@ -289,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 e2013e8ccf1..eb361b1aacb 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -32,9 +32,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), } @@ -47,19 +51,6 @@ export default class ExtensionManager { } } - const storage = getExtensionField( - extension, - 'addStorage', - context, - ) - - if (storage) { - this.editor.editorStorage = { - ...this.editor.editorStorage, - ...storage(), - } - } - const onBeforeCreate = getExtensionField( extension, 'onBeforeCreate', diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts index 235d28226d8..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, @@ -17,11 +20,11 @@ import { KeyboardShortcutCommand, } from './types' import { Node } from './Node' -import { MarkConfig, EditorStorage } from '.' +import { MarkConfig } from '.' import { Editor } from './Editor' declare module '@tiptap/core' { - export interface MarkConfig { + export interface MarkConfig { [key: string]: any; /** @@ -40,14 +43,13 @@ declare module '@tiptap/core' { defaultOptions?: Options, /** - * Storage + * Default Storage */ - addStorage?: (this: { + addStorage?: (this: { name: string, options: Options, - editor: Editor, - parent: ParentConfig>['addStorage'], - }) => Partial, + parent: ParentConfig>['addGlobalAttributes'], + }) => Storage, /** * Global attributes @@ -55,7 +57,8 @@ declare module '@tiptap/core' { addGlobalAttributes?: (this: { name: string, options: Options, - parent: ParentConfig>['addGlobalAttributes'], + storage: Storage, + parent: ParentConfig>['addGlobalAttributes'], }) => GlobalAttributes | {}, /** @@ -64,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, /** @@ -75,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, }, @@ -88,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[], /** @@ -99,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[], /** @@ -110,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[], /** @@ -121,7 +129,8 @@ declare module '@tiptap/core' { addExtensions?: (this: { name: string, options: Options, - parent: ParentConfig>['addExtensions'], + storage: Storage, + parent: ParentConfig>['addExtensions'], }) => Extensions, /** @@ -131,7 +140,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['extendNodeSchema'], + storage: Storage, + parent: ParentConfig>['extendNodeSchema'], }, extension: Node, ) => Record) | null, @@ -143,7 +153,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['extendMarkSchema'], + storage: Storage, + parent: ParentConfig>['extendMarkSchema'], }, extension: Mark, ) => Record) | null, @@ -154,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, /** @@ -165,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, /** @@ -176,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, /** @@ -187,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, /** @@ -199,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, @@ -215,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, @@ -231,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, @@ -246,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, /** @@ -262,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']), /** @@ -271,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']), /** @@ -280,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']), /** @@ -289,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']), /** @@ -298,7 +321,8 @@ declare module '@tiptap/core' { code?: boolean | ((this: { name: string, options: Options, - parent: ParentConfig>['code'], + storage: Storage, + parent: ParentConfig>['code'], }) => boolean), /** @@ -308,7 +332,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['parseHTML'], + storage: Storage, + parent: ParentConfig>['parseHTML'], }, ) => MarkSpec['parseDOM'], @@ -319,7 +344,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['renderHTML'], + storage: Storage, + parent: ParentConfig>['renderHTML'], }, props: { mark: ProseMirrorMark, @@ -334,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' @@ -351,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, @@ -364,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 = {}) { @@ -380,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 @@ -395,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 849bbe7f17e..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, @@ -17,11 +20,11 @@ import { ParentConfig, KeyboardShortcutCommand, } from './types' -import { NodeConfig, EditorStorage } from '.' +import { NodeConfig } from '.' import { Editor } from './Editor' declare module '@tiptap/core' { - interface NodeConfig { + interface NodeConfig { [key: string]: any; /** @@ -40,14 +43,13 @@ declare module '@tiptap/core' { defaultOptions?: Options, /** - * Storage + * Default Storage */ addStorage?: (this: { name: string, options: Options, - editor: Editor, - parent: ParentConfig>['addStorage'], - }) => Partial, + parent: ParentConfig>['addGlobalAttributes'], + }) => Storage, /** * Global attributes @@ -55,7 +57,8 @@ declare module '@tiptap/core' { addGlobalAttributes?: (this: { name: string, options: Options, - parent: ParentConfig>['addGlobalAttributes'], + storage: Storage, + parent: ParentConfig>['addGlobalAttributes'], }) => GlobalAttributes | {}, /** @@ -64,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, /** @@ -75,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, }, @@ -88,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[], /** @@ -99,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[], /** @@ -110,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[], /** @@ -121,7 +129,8 @@ declare module '@tiptap/core' { addExtensions?: (this: { name: string, options: Options, - parent: ParentConfig>['addExtensions'], + storage: Storage, + parent: ParentConfig>['addExtensions'], }) => Extensions, /** @@ -131,7 +140,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['extendNodeSchema'], + storage: Storage, + parent: ParentConfig>['extendNodeSchema'], }, extension: Node, ) => Record) | null, @@ -143,7 +153,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['extendMarkSchema'], + storage: Storage, + parent: ParentConfig>['extendMarkSchema'], }, extension: Node, ) => Record) | null, @@ -154,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, /** @@ -165,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, /** @@ -176,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, /** @@ -187,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, /** @@ -199,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, @@ -215,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, @@ -231,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, @@ -246,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, /** @@ -257,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, /** @@ -273,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']), /** @@ -282,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']), /** @@ -291,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']), /** @@ -300,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']), /** @@ -309,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']), /** @@ -318,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']), /** @@ -327,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']), /** @@ -336,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']), /** @@ -345,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']), /** @@ -354,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']), /** @@ -364,7 +394,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['parseHTML'], + storage: Storage, + parent: ParentConfig>['parseHTML'], }, ) => NodeSpec['parseDOM'], @@ -375,7 +406,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['renderHTML'], + storage: Storage, + parent: ParentConfig>['renderHTML'], }, props: { node: ProseMirrorNode, @@ -390,7 +422,8 @@ declare module '@tiptap/core' { this: { name: string, options: Options, - parent: ParentConfig>['renderText'], + storage: Storage, + parent: ParentConfig>['renderText'], }, props: { node: ProseMirrorNode, @@ -407,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' @@ -424,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, @@ -437,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 = {}) { @@ -453,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 @@ -468,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/index.ts b/packages/core/src/index.ts index d0d71460d4e..8c622edce44 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -52,17 +52,14 @@ export { default as isNodeSelection } from './helpers/isNodeSelection' export { default as isTextSelection } from './helpers/isTextSelection' export { default as posToDOMRect } from './helpers/posToDOMRect' -// eslint-disable-next-line -export interface EditorStorage {} - // eslint-disable-next-line 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 {} +export interface MarkConfig {} diff --git a/packages/extension-gapcursor/src/gapcursor.ts b/packages/extension-gapcursor/src/gapcursor.ts index 214445b80ea..f49bf7a9c9e 100644 --- a/packages/extension-gapcursor/src/gapcursor.ts +++ b/packages/extension-gapcursor/src/gapcursor.ts @@ -7,7 +7,7 @@ import { import { gapCursor } from 'prosemirror-gapcursor' declare module '@tiptap/core' { - interface NodeConfig { + interface NodeConfig { /** * Allow gap cursor */ @@ -17,6 +17,7 @@ declare module '@tiptap/core' { | ((this: { name: string, options: Options, + storage: Storage, parent: ParentConfig>['allowGapCursor'], }) => boolean | null), } diff --git a/packages/extension-table/src/table.ts b/packages/extension-table/src/table.ts index 0c464f8c551..c85e0c9e3e9 100644 --- a/packages/extension-table/src/table.ts +++ b/packages/extension-table/src/table.ts @@ -66,13 +66,14 @@ declare module '@tiptap/core' { } } - interface NodeConfig { + interface NodeConfig { /** * Table Role */ tableRole?: string | ((this: { name: string, options: Options, + storage: Storage, parent: ParentConfig>['tableRole'], }) => string), } diff --git a/packages/vue-3/src/Editor.ts b/packages/vue-3/src/Editor.ts index 088dbf1b89f..68b69f671c4 100644 --- a/packages/vue-3/src/Editor.ts +++ b/packages/vue-3/src/Editor.ts @@ -1,5 +1,5 @@ import { EditorState, Plugin, PluginKey } from 'prosemirror-state' -import { Editor as CoreEditor, EditorOptions, EditorStorage } from '@tiptap/core' +import { Editor as CoreEditor, EditorOptions } from '@tiptap/core' import { markRaw, Ref, @@ -39,7 +39,7 @@ export type ContentComponent = ComponentInternalInstance & { export class Editor extends CoreEditor { private reactiveState: Ref - private reactiveStorage: Ref + private reactiveExtensionStorage: Ref> public vueRenderers = reactive>(new Map()) @@ -49,11 +49,11 @@ export class Editor extends CoreEditor { super(options) this.reactiveState = useDebouncedRef(this.view.state) - this.reactiveStorage = useDebouncedRef(this.editorStorage) + this.reactiveExtensionStorage = useDebouncedRef(this.extensionStorage) this.on('transaction', () => { this.reactiveState.value = this.view.state - this.reactiveStorage.value = this.editorStorage + this.reactiveExtensionStorage.value = this.extensionStorage }) return markRaw(this) @@ -66,8 +66,8 @@ export class Editor extends CoreEditor { } get storage() { - return this.reactiveStorage - ? this.reactiveStorage.value + return this.reactiveExtensionStorage + ? this.reactiveExtensionStorage.value : super.storage } From 550b79a3777e9804a78426c146255983e925156a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 21 Oct 2021 17:04:26 +0200 Subject: [PATCH 4/8] add storage to context --- packages/core/src/ExtensionManager.ts | 4 ++++ packages/core/src/helpers/getAttributesFromExtensions.ts | 2 ++ packages/core/src/helpers/getSchemaByResolvedExtensions.ts | 2 ++ packages/core/src/helpers/isList.ts | 1 + packages/extension-gapcursor/src/gapcursor.ts | 1 + packages/extension-table/src/table.ts | 1 + 6 files changed, 11 insertions(+) diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index eb361b1aacb..6aaa900903a 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -143,6 +143,7 @@ export default class ExtensionManager { const context = { name: extension.name, options: extension.options, + storage: extension.storage, } const addExtensions = getExtensionField( @@ -188,6 +189,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), } @@ -227,6 +229,7 @@ export default class ExtensionManager { const context = { name: extension.name, options: extension.options, + storage: extension.storage, editor, type: getSchemaTypeByName(extension.name, this.schema), } @@ -317,6 +320,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/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/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/extension-gapcursor/src/gapcursor.ts b/packages/extension-gapcursor/src/gapcursor.ts index f49bf7a9c9e..686b3302cf6 100644 --- a/packages/extension-gapcursor/src/gapcursor.ts +++ b/packages/extension-gapcursor/src/gapcursor.ts @@ -36,6 +36,7 @@ export const Gapcursor = Extension.create({ const context = { name: extension.name, options: extension.options, + storage: extension.storage, } return { diff --git a/packages/extension-table/src/table.ts b/packages/extension-table/src/table.ts index c85e0c9e3e9..9df7be4285f 100644 --- a/packages/extension-table/src/table.ts +++ b/packages/extension-table/src/table.ts @@ -246,6 +246,7 @@ export const Table = Node.create({ const context = { name: extension.name, options: extension.options, + storage: extension.storage, } return { From e68f6d331050c263f00bd97f6f279884b3bc0b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 21 Oct 2021 21:04:32 +0200 Subject: [PATCH 5/8] improve type checks for getExtensionField --- packages/core/src/helpers/getExtensionField.ts | 4 ++-- packages/core/src/types.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) 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/types.ts b/packages/core/src/types.ts index deeb3a9723e..9691c014a30 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -31,6 +31,15 @@ export type ParentConfig = Partial<{ : T[P] }> +export type Primitive = + | null + | undefined + | string + | number + | boolean + | symbol + | bigint + export type RemoveThis = T extends (...args: any) => any ? (...args: Parameters) => ReturnType : T @@ -39,6 +48,10 @@ export type MaybeReturnType = T extends (...args: any) => any ? ReturnType : T +export type MaybeThisParameterType = Exclude extends (...args: any) => any + ? ThisParameterType> + : any + export interface EditorEvents { beforeCreate: { editor: Editor }, create: { editor: Editor }, From 65e05e9bbea0e4228656bf97d3114b711ec98ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 21 Oct 2021 21:32:27 +0200 Subject: [PATCH 6/8] add storage to docs --- docs/guide/custom-extensions.md | 33 +++++++++++++++++++++++++++++++++ docs/guide/typescript.md | 19 +++++++++++++++++++ 2 files changed, 52 insertions(+) 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: From f30a34f66859b486982ed2e053d168593bdb1485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 21 Oct 2021 23:42:38 +0200 Subject: [PATCH 7/8] throw warning when using duplicate extension names --- packages/core/src/ExtensionManager.ts | 10 +++++++++- packages/core/src/utilities/findDuplicates.ts | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/utilities/findDuplicates.ts diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index 6aaa900903a..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 { @@ -134,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 { diff --git a/packages/core/src/utilities/findDuplicates.ts b/packages/core/src/utilities/findDuplicates.ts new file mode 100644 index 00000000000..541c062fcee --- /dev/null +++ b/packages/core/src/utilities/findDuplicates.ts @@ -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)] +} From 9eaf0f28817a28c87745ac635c41c25296b335a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Fri, 22 Oct 2021 00:11:56 +0200 Subject: [PATCH 8/8] fix linting --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: {