|
1 | 1 | # Change Log
|
2 | 2 |
|
| 3 | +## 3.0.0-next.5 |
| 4 | + |
| 5 | +### Major Changes |
| 6 | + |
| 7 | +- 32958d6: `Node`, `Mark` and `Extension` config options now are strongly typed and do not allow arbitrary keys on the options object. |
| 8 | + |
| 9 | + To add keys, like when using `extendNodeSchema` or `extendMarkSchema`, you can do this: |
| 10 | + |
| 11 | + ```ts |
| 12 | + declare module '@tiptap/core' { |
| 13 | + interface NodeConfig { |
| 14 | + /** |
| 15 | + * This key will be added to all NodeConfig objects in your project |
| 16 | + */ |
| 17 | + newKey?: string |
| 18 | + } |
| 19 | + interface MarkConfig { |
| 20 | + /** |
| 21 | + * This key will be added to all MarkConfig objects in your project |
| 22 | + */ |
| 23 | + newKey?: string |
| 24 | + } |
| 25 | + interface ExtensionConfig { |
| 26 | + /** |
| 27 | + * This key will be added to all ExtensionConfig objects in your project |
| 28 | + */ |
| 29 | + newKey?: string |
| 30 | + } |
| 31 | + } |
| 32 | + ``` |
| 33 | + |
| 34 | +- 062afaf: `clearContent` command defaults to emitting updates now |
| 35 | +- 062afaf: Change signature of `setContent` command to `(content, options)` and default to emitting updates |
| 36 | +- 32958d6: `editor.storage` is now strongly typed `Storage` instances, using a similar pattern as commands, where you can define the type of the storage value using namespaces like: |
| 37 | + |
| 38 | + ```ts |
| 39 | + declare module '@tiptap/core' { |
| 40 | + interface Storage { |
| 41 | + extensionName: StorageValue |
| 42 | + } |
| 43 | + } |
| 44 | + ``` |
| 45 | + |
| 46 | +- 32958d6: `editor.storage` is instantiated per editor rather than per extension. |
| 47 | + |
| 48 | + Previously, the storage value was a singleton per extension instance, this caused strange bugs when using multiple editor instances on a single page. |
| 49 | + |
| 50 | + Now, storage instances are _per editor instance_, so changing the value on one `editor.storage` instance will not affect another editor's value. |
| 51 | + |
| 52 | +### Minor Changes |
| 53 | + |
| 54 | +- 0e3207f: Add support for [markviews](https://prosemirror.net/docs/ref/#view.MarkView), which allow you to render custom views for marks within the editor. This is useful for rendering custom UI for marks, like a color picker for a text color mark or a link editor for a link mark. |
| 55 | + |
| 56 | + Here is a plain JS markview example: |
| 57 | + |
| 58 | + ```ts |
| 59 | + Mark.create({ |
| 60 | + // Other options... |
| 61 | + addMarkView() { |
| 62 | + return ({ mark, HTMLAttributes }) => { |
| 63 | + const dom = document.createElement('b') |
| 64 | + const contentDOM = document.createElement('span') |
| 65 | + |
| 66 | + dom.appendChild(contentDOM) |
| 67 | + |
| 68 | + return { |
| 69 | + dom, |
| 70 | + contentDOM, |
| 71 | + } |
| 72 | + } |
| 73 | + }, |
| 74 | + }) |
| 75 | + ``` |
| 76 | + |
| 77 | + ## React binding |
| 78 | + |
| 79 | + To use a React component for a markview, you can use the `@tiptap/react` package: |
| 80 | + |
| 81 | + ```ts |
| 82 | + import { Mark } from '@tiptap/core' |
| 83 | + import { ReactMarkViewRenderer } from '@tiptap/react' |
| 84 | + |
| 85 | + import Component from './Component.jsx' |
| 86 | + |
| 87 | + export default Mark.create({ |
| 88 | + name: 'reactComponent', |
| 89 | + |
| 90 | + parseHTML() { |
| 91 | + return [ |
| 92 | + { |
| 93 | + tag: 'react-component', |
| 94 | + }, |
| 95 | + ] |
| 96 | + }, |
| 97 | + |
| 98 | + renderHTML({ HTMLAttributes }) { |
| 99 | + return ['react-component', HTMLAttributes] |
| 100 | + }, |
| 101 | + |
| 102 | + addMarkView() { |
| 103 | + return ReactMarkViewRenderer(Component) |
| 104 | + }, |
| 105 | + }) |
| 106 | + ``` |
| 107 | + |
| 108 | + And here is an example of a React component: |
| 109 | + |
| 110 | + ```tsx |
| 111 | + import { MarkViewContent, MarkViewRendererProps } from '@tiptap/react' |
| 112 | + import React from 'react' |
| 113 | + |
| 114 | + // eslint-disable-next-line @typescript-eslint/no-unused-vars |
| 115 | + export default (props: MarkViewRendererProps) => { |
| 116 | + const [count, setCount] = React.useState(0) |
| 117 | + |
| 118 | + return ( |
| 119 | + <span className="content" data-test-id="mark-view"> |
| 120 | + <MarkViewContent /> |
| 121 | + <label contentEditable={false}> |
| 122 | + React component: |
| 123 | + <button |
| 124 | + onClick={() => { |
| 125 | + setCount(count + 1) |
| 126 | + }} |
| 127 | + > |
| 128 | + This button has been clicked {count} times. |
| 129 | + </button> |
| 130 | + </label> |
| 131 | + </span> |
| 132 | + ) |
| 133 | + } |
| 134 | + ``` |
| 135 | + |
| 136 | + ## Vue 3 binding |
| 137 | + |
| 138 | + To use a Vue 3 component for a markview, you can use the `@tiptap/vue-3` package: |
| 139 | + |
| 140 | + ```ts |
| 141 | + import { Mark } from '@tiptap/core' |
| 142 | + import { VueMarkViewRenderer } from '@tiptap/vue-3' |
| 143 | + |
| 144 | + import Component from './Component.vue' |
| 145 | + |
| 146 | + export default Mark.create({ |
| 147 | + name: 'vueComponent', |
| 148 | + |
| 149 | + parseHTML() { |
| 150 | + return [ |
| 151 | + { |
| 152 | + tag: 'vue-component', |
| 153 | + }, |
| 154 | + ] |
| 155 | + }, |
| 156 | + |
| 157 | + renderHTML({ HTMLAttributes }) { |
| 158 | + return ['vue-component', HTMLAttributes] |
| 159 | + }, |
| 160 | + |
| 161 | + addMarkView() { |
| 162 | + return VueMarkViewRenderer(Component) |
| 163 | + }, |
| 164 | + }) |
| 165 | + ``` |
| 166 | + |
| 167 | + And here is an example of a Vue 3 component: |
| 168 | + |
| 169 | + ```vue |
| 170 | + <template> |
| 171 | + <span className="content" data-test-id="mark-view"> |
| 172 | + <mark-view-content /> |
| 173 | + <label contenteditable="false" |
| 174 | + >Vue Component:: |
| 175 | + <button @click="increase" class="primary">This button has been clicked {{ count }} times.</button> |
| 176 | + </label> |
| 177 | + </span> |
| 178 | + </template> |
| 179 | +
|
| 180 | + <script> |
| 181 | + import { MarkViewContent, markViewProps } from '@tiptap/vue-3' |
| 182 | + export default { |
| 183 | + components: { |
| 184 | + MarkViewContent, |
| 185 | + }, |
| 186 | + data() { |
| 187 | + return { |
| 188 | + count: 0, |
| 189 | + } |
| 190 | + }, |
| 191 | + props: markViewProps, |
| 192 | + methods: { |
| 193 | + increase() { |
| 194 | + this.count += 1 |
| 195 | + }, |
| 196 | + }, |
| 197 | + } |
| 198 | + </script> |
| 199 | + ``` |
| 200 | + |
| 201 | +- 28c5418: Adds a new `delete` event which can detect content which has been deleted by the editor as a core extension |
| 202 | +- 704f462: This introduces a new behavior for the editor, the ability to be safely run on the server-side (without rendering). |
| 203 | + |
| 204 | + `prosemirror-view` encapsulates all view (& DOM) related code, and cannot safely be SSR'd, but, the majority of the editor instance itself is in plain JS that does not require DOM APIs (unless your content is specified in HTML). |
| 205 | + |
| 206 | + But, we have so many convenient methods available for manipulating content. So, it is a shame that they could not be used on the server side too. With this change, the editor can be rendered on the server-side and will use a stub for select prosemirror-view methods. If accessing unsupported methods or values on the `editor.view`, you will encounter runtime errors, so it is important for you to test to see if the methods you call actually work. |
| 207 | + |
| 208 | + This is a step towards being able to server-side render content, but, it is not completely supported yet. This does not mean that you can render an editor instance on the server and expect it to just output any HTML. |
| 209 | + |
| 210 | + ## Usage |
| 211 | + |
| 212 | + If you pass `element: null` to your editor options: |
| 213 | + |
| 214 | + - the `editor.view` will not be initialized |
| 215 | + - the editor will not emit it's `'create'` event |
| 216 | + - the focus will not be initialized to it's first position |
| 217 | + |
| 218 | + You can however, later use the new `mount` function on the instance, which will mount the editor view to a DOM element. This obviously will not be allowed on the server which has no document object. |
| 219 | + |
| 220 | + Therefore, this will work on the server: |
| 221 | + |
| 222 | + ```ts |
| 223 | + import { Editor } from '@tiptap/core' |
| 224 | + import StarterKit from '@tiptap/starter-kit' |
| 225 | + |
| 226 | + const editor = new Editor({ |
| 227 | + element: null, |
| 228 | + content: { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello, World!' }] }] }, |
| 229 | + extensions: [StarterKit], |
| 230 | + }) |
| 231 | + |
| 232 | + editor |
| 233 | + .chain() |
| 234 | + .selectAll() |
| 235 | + .setContent({ type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'XYZ' }] }] }) |
| 236 | + .run() |
| 237 | + |
| 238 | + console.log(editor.state.doc.toJSON()) |
| 239 | + // { type: 'doc', content: [ { type: 'paragraph', content: [ { type: 'text', text: 'XYZ' } ] } ] } |
| 240 | + ``` |
| 241 | + |
| 242 | + Any of these things will not work on the server, and result in a runtime error: |
| 243 | + |
| 244 | + ```ts |
| 245 | + import { Editor } from '@tiptap/core' |
| 246 | + import StarterKit from '@tiptap/starter-kit' |
| 247 | + |
| 248 | + const editor = new Editor({ |
| 249 | + // document will not be defined in a server environment |
| 250 | + element: document.createElement('div'), |
| 251 | + content: { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello, World!' }] }] }, |
| 252 | + extensions: [StarterKit], |
| 253 | + }) |
| 254 | + |
| 255 | + editor |
| 256 | + .chain() |
| 257 | + // focus is a command which depends on the editor-view, so it will not work in a server environment |
| 258 | + .focus() |
| 259 | + .run() |
| 260 | + |
| 261 | + console.log(editor.getHTML()) |
| 262 | + // getHTML relies on the editor-view, so it will not work in a server environment |
| 263 | + ``` |
| 264 | + |
| 265 | +- 32958d6: Extensions, Nodes and Marks no longer respect the deprecated `defaultOptions` config value |
| 266 | + |
3 | 267 | ## 3.0.0-next.4
|
4 | 268 |
|
5 | 269 | ### Minor Changes
|
|
0 commit comments