Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extension storage #2069

Merged
merged 9 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
19 changes: 19 additions & 0 deletions demos/src/Experiments/ExtensionStorage/React/CustomExtension.ts
Original file line number Diff line number Diff line change
@@ -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
},
})
15 changes: 15 additions & 0 deletions demos/src/Experiments/ExtensionStorage/React/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from '../../../../setup/react.ts'
import source from '@source'
setup('Experiments/ExtensionStorage', source)
</script>
</body>
</html>
33 changes: 33 additions & 0 deletions demos/src/Experiments/ExtensionStorage/React/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { useEditor, EditorContent } from '@tiptap/react'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { CustomExtension } from './CustomExtension'
import './styles.scss'

export default () => {
const editor = useEditor({
extensions: [
Document,
Paragraph,
Text,
CustomExtension,
],
content: `
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
</p>
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`,
})

return (
<>
reactive storage: {editor?.storage.custom.foo}
<EditorContent editor={editor} />
</>
)
}
6 changes: 6 additions & 0 deletions demos/src/Experiments/ExtensionStorage/React/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
19 changes: 19 additions & 0 deletions demos/src/Experiments/ExtensionStorage/Vue/CustomExtension.ts
Original file line number Diff line number Diff line change
@@ -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
},
})
15 changes: 15 additions & 0 deletions demos/src/Experiments/ExtensionStorage/Vue/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from '../../../../setup/vue.ts'
import source from '@source'
setup('Experiments/ExtensionStorage', source)
</script>
</body>
</html>
56 changes: 56 additions & 0 deletions demos/src/Experiments/ExtensionStorage/Vue/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
reactive storage: {{ editor?.storage.custom.foo }}
<editor-content :editor="editor" />
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { CustomExtension } from './CustomExtension'

export default {
components: {
EditorContent,
},

data() {
return {
editor: null,
}
},

mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
CustomExtension,
],
content: `
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
</p>
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`,
})
},

beforeUnmount() {
this.editor.destroy()
},
}
</script>

<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
</style>
33 changes: 33 additions & 0 deletions docs/guide/custom-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
19 changes: 19 additions & 0 deletions docs/guide/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ const CustomExtension = Extension.create<CustomExtensionOptions>({
})
```

### 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:

Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export class Editor extends EventEmitter<EditorEvents> {

public isFocused = false

public extensionStorage: Record<string, any> = {}

public options: EditorOptions = {
element: document.createElement('div'),
content: '',
Expand Down Expand Up @@ -100,6 +102,13 @@ export class Editor extends EventEmitter<EditorEvents> {
}, 0)
}

/**
* Returns the editor storage.
*/
public get storage(): Record<string, any> {
return this.extensionStorage
}

/**
* An object of all registered commands.
*/
Expand Down
Loading