@portabletext/editor 1.17.1 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -261
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
- package/lib/_chunks-es/selector.is-selection-collapsed.js.map +1 -1
- package/lib/behaviors/index.cjs +6 -1
- package/lib/behaviors/index.cjs.map +1 -1
- package/lib/behaviors/index.d.cts +23 -23
- package/lib/behaviors/index.d.ts +23 -23
- package/lib/behaviors/index.js +6 -1
- package/lib/behaviors/index.js.map +1 -1
- package/lib/index.cjs +78 -69
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +123 -753
- package/lib/index.d.ts +123 -753
- package/lib/index.js +78 -69
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +30 -30
- package/lib/selectors/index.d.ts +30 -30
- package/lib/selectors/index.js.map +1 -1
- package/package.json +10 -10
- package/src/behaviors/behavior.code-editor.ts +2 -2
- package/src/behaviors/behavior.core.ts +2 -2
- package/src/behaviors/behavior.emoji-picker.ts +12 -3
- package/src/behaviors/behavior.links.ts +2 -2
- package/src/behaviors/behavior.markdown.ts +2 -2
- package/src/behaviors/behavior.types.ts +10 -10
- package/src/editor/PortableTextEditor.tsx +2 -0
- package/src/editor/__tests__/self-solving.test.tsx +14 -0
- package/src/editor/components/Synchronizer.tsx +6 -1
- package/src/editor/create-editor.ts +14 -3
- package/src/editor/define-schema.ts +4 -4
- package/src/editor/editor-event-listener.tsx +1 -1
- package/src/editor/editor-machine.ts +42 -52
- package/src/editor/editor-provider.tsx +3 -3
- package/src/editor/editor-selector.ts +31 -14
- package/src/editor/editor-snapshot.ts +2 -2
- package/src/editor/get-value.ts +12 -5
- package/src/editor/hooks/usePortableTextEditor.ts +1 -0
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +1 -0
- package/src/selectors/selector.get-active-list-item.ts +1 -1
- package/src/selectors/selector.get-active-style.ts +1 -1
- package/src/selectors/selector.get-selected-spans.ts +1 -1
- package/src/selectors/selector.get-selection-text.ts +1 -1
- package/src/selectors/selector.get-text-before.ts +1 -1
- package/src/selectors/selector.is-active-annotation.ts +1 -1
- package/src/selectors/selector.is-active-decorator.ts +1 -1
- package/src/selectors/selector.is-active-list-item.ts +1 -1
- package/src/selectors/selector.is-active-style.ts +1 -1
- package/src/selectors/selector.is-selection-collapsed.ts +1 -1
- package/src/selectors/selector.is-selection-expanded.ts +1 -1
- package/src/selectors/selectors.ts +13 -13
- package/src/types/editor.ts +2 -2
package/README.md
CHANGED
|
@@ -9,23 +9,65 @@
|
|
|
9
9
|
|
|
10
10
|
> The official editor for editing [Portable Text](https://github.com/portabletext/portabletext) – the JSON based rich text specification for modern content editing platforms.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Get started with the Portable Text Editor
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
This library provides you with the building blocks to create a completely custom editor experience built on top of Portable Text. We recommend [checking out the official documentation](https://www.portabletext.org/). The following guide includes the basics to get your started.
|
|
15
|
+
|
|
16
|
+
In order to set up an editor you'll need to:
|
|
17
|
+
|
|
18
|
+
- Create a schema that defines the rich text and block content elements.
|
|
19
|
+
- Create a toolbar to toggle and insert these elements.
|
|
20
|
+
- Write render functions to style and display each element type in the editor.
|
|
21
|
+
- Render the editor.
|
|
22
|
+
|
|
23
|
+
Check out [example application](/examples/basic/src/App.tsx) in this repo for a basic implementation of the editor. Most of the source code from this example app can also be found in the instructions below.
|
|
24
|
+
|
|
25
|
+
### Add the library to your project
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
# npm
|
|
29
|
+
npm i @portabletext/editor
|
|
30
|
+
|
|
31
|
+
# pnpm
|
|
32
|
+
pnpm add @portabletext/editor
|
|
33
|
+
|
|
34
|
+
# yarn
|
|
35
|
+
yarn add @portabletext/editor
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Next, in your app or the component you're building, import `EditorProvider`, `EditorEventListener`, `PortableTextEditable`, `defineSchema`, and the types in the code below.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// App.tsx
|
|
43
|
+
import {
|
|
44
|
+
defineSchema,
|
|
45
|
+
EditorEventListener,
|
|
46
|
+
EditorProvider,
|
|
47
|
+
PortableTextEditable,
|
|
48
|
+
} from '@portabletext/editor'
|
|
49
|
+
import type {
|
|
50
|
+
PortableTextBlock,
|
|
51
|
+
RenderDecoratorFunction,
|
|
52
|
+
RenderStyleFunction,
|
|
53
|
+
} from '@portabletext/editor'
|
|
54
|
+
```
|
|
15
55
|
|
|
16
56
|
### Define the Schema
|
|
17
57
|
|
|
18
|
-
|
|
58
|
+
Before you can render the editor, you need a schema. The editor schema configures the types of content rendered by the editor.
|
|
59
|
+
|
|
60
|
+
We'll start with a schema that includes some common rich text elements.
|
|
61
|
+
|
|
62
|
+
> [!NOTE]
|
|
63
|
+
> This guide includes a limited set of schema types, or rich text elements, to get you started. See the [rendering guide](https://www.portabletext.org/guides/custom-rendering/) for additional examples.
|
|
19
64
|
|
|
20
|
-
```
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// You can use this schema definition later to build your toolbar
|
|
65
|
+
```tsx
|
|
66
|
+
// App.tsx
|
|
67
|
+
// ...
|
|
24
68
|
const schemaDefinition = defineSchema({
|
|
25
69
|
// Decorators are simple marks that don't hold any data
|
|
26
70
|
decorators: [{name: 'strong'}, {name: 'em'}, {name: 'underline'}],
|
|
27
|
-
// Annotations are more complex marks that can hold data
|
|
28
|
-
annotations: [{name: 'link'}],
|
|
29
71
|
// Styles apply to entire text blocks
|
|
30
72
|
// There's always a 'normal' style that can be considered the paragraph style
|
|
31
73
|
styles: [
|
|
@@ -35,136 +77,90 @@ const schemaDefinition = defineSchema({
|
|
|
35
77
|
{name: 'h3'},
|
|
36
78
|
{name: 'blockquote'},
|
|
37
79
|
],
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
80
|
+
|
|
81
|
+
// The types below are left empty for this example.
|
|
82
|
+
// See the rendering guide to learn more about each type.
|
|
83
|
+
|
|
84
|
+
// Annotations are more complex marks that can hold data (for example, hyperlinks).
|
|
85
|
+
annotations: [],
|
|
86
|
+
// Lists apply to entire text blocks as well (for example, bullet, numbered).
|
|
87
|
+
lists: [],
|
|
88
|
+
// Inline objects hold arbitrary data that can be inserted into the text (for example, custom emoji).
|
|
89
|
+
inlineObjects: [],
|
|
90
|
+
// Block objects hold arbitrary data that live side-by-side with text blocks (for example, images, code blocks, and tables).
|
|
91
|
+
blockObjects: [],
|
|
44
92
|
})
|
|
45
93
|
```
|
|
46
94
|
|
|
47
|
-
|
|
95
|
+
Learn more about the different types that exist in schema in the [Portable Text Overview](https://www.portabletext.org/concepts/portabletext/).
|
|
96
|
+
|
|
97
|
+
### Render the editor
|
|
48
98
|
|
|
49
|
-
|
|
99
|
+
With a schema defined, you have enough to render the editor. It won't do much yet, but you can confirm your progress.
|
|
100
|
+
|
|
101
|
+
Add `useState` from React, then scaffold out a basic application component. For example:
|
|
50
102
|
|
|
51
103
|
```tsx
|
|
104
|
+
// app.tsx
|
|
105
|
+
import {
|
|
106
|
+
defineSchema,
|
|
107
|
+
EditorEventListener,
|
|
108
|
+
EditorProvider,
|
|
109
|
+
PortableTextEditable,
|
|
110
|
+
} from '@portabletext/editor'
|
|
111
|
+
import type {
|
|
112
|
+
PortableTextBlock,
|
|
113
|
+
RenderDecoratorFunction,
|
|
114
|
+
RenderStyleFunction,
|
|
115
|
+
} from '@portabletext/editor'
|
|
116
|
+
import {useState} from 'react'
|
|
117
|
+
|
|
118
|
+
const schemaDefinition = defineSchema({
|
|
119
|
+
/* your schema from the previous step */
|
|
120
|
+
})
|
|
121
|
+
|
|
52
122
|
function App() {
|
|
123
|
+
// Set up the initial state getter and setter. Leave the starting value as undefined for now.
|
|
53
124
|
const [value, setValue] = useState<Array<PortableTextBlock> | undefined>(
|
|
54
|
-
|
|
55
|
-
() => [
|
|
56
|
-
{
|
|
57
|
-
_type: 'block',
|
|
58
|
-
_key: keyGenerator(),
|
|
59
|
-
children: [
|
|
60
|
-
{_type: 'span', _key: keyGenerator(), text: 'Hello, '},
|
|
61
|
-
{
|
|
62
|
-
_type: 'span',
|
|
63
|
-
_key: keyGenerator(),
|
|
64
|
-
text: 'world!',
|
|
65
|
-
marks: ['strong'],
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
],
|
|
125
|
+
undefined,
|
|
70
126
|
)
|
|
71
127
|
|
|
72
128
|
return (
|
|
73
129
|
<>
|
|
74
|
-
{/* Create an editor */}
|
|
75
130
|
<EditorProvider
|
|
76
131
|
initialConfig={{
|
|
77
132
|
schemaDefinition,
|
|
78
133
|
initialValue: value,
|
|
79
134
|
}}
|
|
80
135
|
>
|
|
81
|
-
{/* Subscribe to editor changes */}
|
|
82
136
|
<EditorEventListener
|
|
83
137
|
on={(event) => {
|
|
84
138
|
if (event.type === 'mutation') {
|
|
85
|
-
setValue(event.
|
|
139
|
+
setValue(event.value)
|
|
86
140
|
}
|
|
87
141
|
}}
|
|
88
142
|
/>
|
|
89
|
-
{/* Toolbar needs to be rendered inside the `EditorProvider` component */}
|
|
90
|
-
<Toolbar />
|
|
91
|
-
{/* Component that controls the actual rendering of the editor */}
|
|
92
143
|
<PortableTextEditable
|
|
144
|
+
// Add an optional style to see it more easily on the page
|
|
93
145
|
style={{border: '1px solid black', padding: '0.5em'}}
|
|
94
|
-
// Control how decorators are rendered
|
|
95
|
-
renderDecorator={renderDecorator}
|
|
96
|
-
// Control how annotations are rendered
|
|
97
|
-
renderAnnotation={renderAnnotation}
|
|
98
|
-
// Required to render block objects but also to make `renderStyle` take effect
|
|
99
|
-
renderBlock={renderBlock}
|
|
100
|
-
// Control how styles are rendered
|
|
101
|
-
renderStyle={renderStyle}
|
|
102
|
-
// Control how inline objects are rendered
|
|
103
|
-
renderChild={renderChild}
|
|
104
|
-
// Rendering lists is harder and most likely requires a fair amount of CSS
|
|
105
|
-
// First, return the children like here
|
|
106
|
-
// Next, look in the imported `editor.css` file to see how list styles are implemented
|
|
107
|
-
renderListItem={(props) => <>{props.children}</>}
|
|
108
146
|
/>
|
|
109
147
|
</EditorProvider>
|
|
110
|
-
<pre style={{border: '1px dashed black', padding: '0.5em'}}>
|
|
111
|
-
{JSON.stringify(value, null, 2)}
|
|
112
|
-
</pre>
|
|
113
148
|
</>
|
|
114
149
|
)
|
|
115
150
|
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Render Marks, Blocks and Objects
|
|
119
|
-
|
|
120
|
-
All the different render functions passed to `PortableTextEditable` can be defined as stand-alone React components. Most of these are fairly straightforward to render because everything you need is provided via `props`. However, lists are a little special. Since Portable Text has no concept of block nesting, the easiest way get something looking like lists is with pure CSS. Head over to [/examples/basic/src/editor.css](/examples/basic/src/editor.css) for a full example.
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
const renderDecorator: RenderDecoratorFunction = (props) => {
|
|
124
|
-
if (props.value === 'strong') {
|
|
125
|
-
return <strong>{props.children}</strong>
|
|
126
|
-
}
|
|
127
|
-
if (props.value === 'em') {
|
|
128
|
-
return <em>{props.children}</em>
|
|
129
|
-
}
|
|
130
|
-
if (props.value === 'underline') {
|
|
131
|
-
return <u>{props.children}</u>
|
|
132
|
-
}
|
|
133
|
-
return <>{props.children}</>
|
|
134
|
-
}
|
|
135
151
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return <span style={{textDecoration: 'underline'}}>{props.children}</span>
|
|
139
|
-
}
|
|
152
|
+
export default App
|
|
153
|
+
```
|
|
140
154
|
|
|
141
|
-
|
|
142
|
-
}
|
|
155
|
+
Include the `App` component in your application and run it. You should see an outlined editor that accepts text, but doesn't do much else.
|
|
143
156
|
|
|
144
|
-
|
|
145
|
-
if (props.schemaType.name === 'image' && isImage(props.value)) {
|
|
146
|
-
return (
|
|
147
|
-
<div
|
|
148
|
-
style={{
|
|
149
|
-
border: '1px dotted grey',
|
|
150
|
-
padding: '0.25em',
|
|
151
|
-
marginBlockEnd: '0.25em',
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
IMG: {props.value.src}
|
|
155
|
-
</div>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
157
|
+
### Create render functions for schema elements
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
}
|
|
159
|
+
At this point the PTE only has a schema, but it doesn't know how to render anything. Fix that by creating render functions for each property in the schema.
|
|
161
160
|
|
|
162
|
-
function
|
|
163
|
-
props: PortableTextBlock,
|
|
164
|
-
): props is PortableTextBlock & {src: string} {
|
|
165
|
-
return 'src' in props
|
|
166
|
-
}
|
|
161
|
+
Start by creating a render function for styles.
|
|
167
162
|
|
|
163
|
+
```tsx
|
|
168
164
|
const renderStyle: RenderStyleFunction = (props) => {
|
|
169
165
|
if (props.schemaType.value === 'h1') {
|
|
170
166
|
return <h1>{props.children}</h1>
|
|
@@ -180,216 +176,187 @@ const renderStyle: RenderStyleFunction = (props) => {
|
|
|
180
176
|
}
|
|
181
177
|
return <>{props.children}</>
|
|
182
178
|
}
|
|
179
|
+
```
|
|
183
180
|
|
|
184
|
-
|
|
185
|
-
if (props.schemaType.name === 'stock-ticker' && isStockTicker(props.value)) {
|
|
186
|
-
return (
|
|
187
|
-
<span
|
|
188
|
-
style={{
|
|
189
|
-
border: '1px dotted grey',
|
|
190
|
-
padding: '0.15em',
|
|
191
|
-
}}
|
|
192
|
-
>
|
|
193
|
-
{props.value.symbol}
|
|
194
|
-
</span>
|
|
195
|
-
)
|
|
196
|
-
}
|
|
181
|
+
Render functions all follow the same format.
|
|
197
182
|
|
|
198
|
-
|
|
199
|
-
|
|
183
|
+
- They take in props and return JSX elements.
|
|
184
|
+
- They use the schema to make decisions.
|
|
185
|
+
- They return JSX and pass `children` as a fallback.
|
|
200
186
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
187
|
+
With this in mind, continue for the remaining schema types.
|
|
188
|
+
|
|
189
|
+
Create a render function for decorators.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
const renderDecorator: RenderDecoratorFunction = (props) => {
|
|
193
|
+
if (props.value === 'strong') {
|
|
194
|
+
return <strong>{props.children}</strong>
|
|
195
|
+
}
|
|
196
|
+
if (props.value === 'em') {
|
|
197
|
+
return <em>{props.children}</em>
|
|
198
|
+
}
|
|
199
|
+
if (props.value === 'underline') {
|
|
200
|
+
return <u>{props.children}</u>
|
|
201
|
+
}
|
|
202
|
+
return <>{props.children}</>
|
|
205
203
|
}
|
|
206
204
|
```
|
|
207
205
|
|
|
208
|
-
|
|
206
|
+
> [!NOTE]
|
|
207
|
+
> By default, text is rendered as an inline `span` element in the editor. While most render functions return a fragment (`<>`) as the fallback, make sure block level elements return blocks, like `<div>` elements.
|
|
209
208
|
|
|
210
|
-
|
|
209
|
+
Update the `PortableTextEditable` with each corresponding function to attach them to the editor.
|
|
210
|
+
|
|
211
|
+
You may notice that we skipped a few types from the schema. Declare these inline in the configuration, like in the code below. You can learn more about [customizing the render functions](https://www.portabletext.org/guides/custom-rendering/) in the documentation.
|
|
211
212
|
|
|
212
213
|
```tsx
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
<PortableTextEditable
|
|
215
|
+
style={{border: '1px solid black', padding: '0.5em'}}
|
|
216
|
+
renderStyle={renderStyle}
|
|
217
|
+
renderDecorator={renderDecorator}
|
|
218
|
+
renderBlock={(props) => <div>{props.children}</div>}
|
|
219
|
+
renderListItem={(props) => <>{props.children}</>}
|
|
220
|
+
/>
|
|
221
|
+
```
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
<DecoratorButton key={decorator.name} decorator={decorator.name} />
|
|
219
|
-
))
|
|
223
|
+
Before you can see if anything changed, you need a way to interact with the editor.
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
<AnnotationButton key={annotation.name} annotation={annotation} />
|
|
223
|
-
))
|
|
225
|
+
### Create a toolbar
|
|
224
226
|
|
|
225
|
-
|
|
226
|
-
<StyleButton key={style.name} style={style.name} />
|
|
227
|
-
))
|
|
227
|
+
A toolbar is a collection of UI elements for interacting with the editor. The PTE library gives you the necessary hooks to create a toolbar however you like. Learn more about [creating your own toolbar](https://www.portabletext.org/guides/customize-toolbar/) in the documentation.
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
1. Create a `Toolbar` component in the same file.
|
|
230
|
+
2. Import the `useEditor` hook, and declare an `editor` constant in the component.
|
|
231
|
+
3. Iterate over the schema types to create toggle buttons for each style and decorator.
|
|
232
|
+
4. Send events to the editor to toggle the styles and decorators whenever the buttons are clicked.
|
|
233
|
+
5. Render the toolbar buttons.
|
|
232
234
|
|
|
233
|
-
|
|
235
|
+
```tsx
|
|
236
|
+
// App.tsx
|
|
237
|
+
// ...
|
|
238
|
+
import {useEditor} from '@portabletext/editor'
|
|
239
|
+
|
|
240
|
+
function Toolbar() {
|
|
241
|
+
// useEditor provides access to the PTE
|
|
242
|
+
const editor = useEditor()
|
|
243
|
+
|
|
244
|
+
// Iterate over the schema (defined earlier), or manually create buttons.
|
|
245
|
+
const styleButtons = schemaDefinition.styles.map((style) => (
|
|
234
246
|
<button
|
|
247
|
+
key={style.name}
|
|
235
248
|
onClick={() => {
|
|
249
|
+
// Send style toggle event
|
|
236
250
|
editor.send({
|
|
237
|
-
type: '
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
placement: 'auto',
|
|
251
|
+
type: 'style.toggle',
|
|
252
|
+
style: style.name,
|
|
253
|
+
})
|
|
254
|
+
editor.send({
|
|
255
|
+
type: 'focus',
|
|
243
256
|
})
|
|
244
|
-
editor.send({type: 'focus'})
|
|
245
257
|
}}
|
|
246
258
|
>
|
|
247
|
-
{
|
|
259
|
+
{style.name}
|
|
248
260
|
</button>
|
|
249
|
-
)
|
|
261
|
+
))
|
|
250
262
|
|
|
251
|
-
const
|
|
263
|
+
const decoratorButtons = schemaDefinition.decorators.map((decorator) => (
|
|
252
264
|
<button
|
|
265
|
+
key={decorator.name}
|
|
253
266
|
onClick={() => {
|
|
267
|
+
// Send decorator toggle event
|
|
268
|
+
editor.send({
|
|
269
|
+
type: 'decorator.toggle',
|
|
270
|
+
decorator: decorator.name,
|
|
271
|
+
})
|
|
254
272
|
editor.send({
|
|
255
|
-
type: '
|
|
256
|
-
inlineObject: {
|
|
257
|
-
name: 'stock-ticker',
|
|
258
|
-
value: {symbol: 'AAPL'},
|
|
259
|
-
},
|
|
273
|
+
type: 'focus',
|
|
260
274
|
})
|
|
261
|
-
editor.send({type: 'focus'})
|
|
262
275
|
}}
|
|
263
276
|
>
|
|
264
|
-
{
|
|
277
|
+
{decorator.name}
|
|
265
278
|
</button>
|
|
266
|
-
)
|
|
267
|
-
|
|
279
|
+
))
|
|
268
280
|
return (
|
|
269
281
|
<>
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
<div>{styleButtons}</div>
|
|
273
|
-
<div>{listButtons}</div>
|
|
274
|
-
<div>{imageButton}</div>
|
|
275
|
-
<div>{stockTickerButton}</div>
|
|
282
|
+
{styleButtons}
|
|
283
|
+
{decoratorButtons}
|
|
276
284
|
</>
|
|
277
285
|
)
|
|
278
286
|
}
|
|
287
|
+
```
|
|
279
288
|
|
|
280
|
-
|
|
281
|
-
// Obtain the editor instance
|
|
282
|
-
const editor = useEditor()
|
|
283
|
-
// Check if the decorator is active using a selector
|
|
284
|
-
const active = useEditorSelector(
|
|
285
|
-
editor,
|
|
286
|
-
selectors.isActiveDecorator(props.decorator),
|
|
287
|
-
)
|
|
289
|
+
The `useEditor` hook gives you access to the active editor. `send` lets you send events to the editor. You can view the full list of events in the [Behavior API reference](https://www.portabletext.org/reference/behavior-api/).
|
|
288
290
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
style={{
|
|
292
|
-
textDecoration: active ? 'underline' : 'unset',
|
|
293
|
-
}}
|
|
294
|
-
onClick={() => {
|
|
295
|
-
// Toggle the decorator
|
|
296
|
-
editor.send({
|
|
297
|
-
type: 'decorator.toggle',
|
|
298
|
-
decorator: props.decorator,
|
|
299
|
-
})
|
|
300
|
-
// Pressing this button steals focus so let's focus the editor again
|
|
301
|
-
editor.send({type: 'focus'})
|
|
302
|
-
}}
|
|
303
|
-
>
|
|
304
|
-
{props.decorator}
|
|
305
|
-
</button>
|
|
306
|
-
)
|
|
307
|
-
}
|
|
291
|
+
> [!NOTE]
|
|
292
|
+
> The example above sends a `focus` event after each action. Normally when interacting with a button, the browser removes focus from the text editing area. This event returns focus to the field to prevent interrupting the user.
|
|
308
293
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
294
|
+
### Bring it all together
|
|
295
|
+
|
|
296
|
+
With render functions created and a toolbar in place, you can fully render the editor. Add the `Toolbar` inside the `EditorProvider`.
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
// App.tsx
|
|
300
|
+
// ...
|
|
301
|
+
function App() {
|
|
302
|
+
const [value, setValue] = useState<Array<PortableTextBlock> | undefined>(
|
|
303
|
+
undefined,
|
|
314
304
|
)
|
|
315
305
|
|
|
316
306
|
return (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
307
|
+
<>
|
|
308
|
+
<EditorProvider
|
|
309
|
+
initialConfig={{
|
|
310
|
+
schemaDefinition,
|
|
311
|
+
initialValue: value,
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<EditorEventListener
|
|
315
|
+
on={(event) => {
|
|
316
|
+
if (event.type === 'mutation') {
|
|
317
|
+
setValue(event.value)
|
|
318
|
+
}
|
|
319
|
+
}}
|
|
320
|
+
/>
|
|
321
|
+
<Toolbar />
|
|
322
|
+
<PortableTextEditable
|
|
323
|
+
style={{border: '1px solid black', padding: '0.5em'}}
|
|
324
|
+
renderStyle={renderStyle}
|
|
325
|
+
renderDecorator={renderDecorator}
|
|
326
|
+
renderBlock={(props) => <div>{props.children}</div>}
|
|
327
|
+
renderListItem={(props) => <>{props.children}</>}
|
|
328
|
+
/>
|
|
329
|
+
</EditorProvider>
|
|
330
|
+
</>
|
|
337
331
|
)
|
|
338
332
|
}
|
|
333
|
+
// ...
|
|
334
|
+
```
|
|
339
335
|
|
|
340
|
-
|
|
341
|
-
const editor = useEditor()
|
|
342
|
-
const active = useEditorSelector(editor, selectors.isActiveStyle(props.style))
|
|
336
|
+
You can now enter text and interact with the toolbar buttons to toggle the styles and decorators. These are only a small portion of the types of things you can do. Check out the [custom rendering guide](https://www.portabletext.org/guides/custom-rendering/) and the [toolbar customization guide](https://www.portabletext.org/guides/customize-toolbar/) for options.
|
|
343
337
|
|
|
344
|
-
|
|
345
|
-
<button
|
|
346
|
-
style={{
|
|
347
|
-
textDecoration: active ? 'underline' : 'unset',
|
|
348
|
-
}}
|
|
349
|
-
onClick={() => {
|
|
350
|
-
editor.send({type: 'style.toggle', style: props.style})
|
|
351
|
-
editor.send({type: 'focus'})
|
|
352
|
-
}}
|
|
353
|
-
>
|
|
354
|
-
{props.style}
|
|
355
|
-
</button>
|
|
356
|
-
)
|
|
357
|
-
}
|
|
338
|
+
### View the Portable Text data
|
|
358
339
|
|
|
359
|
-
|
|
360
|
-
const editor = useEditor()
|
|
361
|
-
const active = useEditorSelector(
|
|
362
|
-
editor,
|
|
363
|
-
selectors.isActiveListItem(props.list),
|
|
364
|
-
)
|
|
340
|
+
You can preview the Portable Text from the editor by reading the state. Add the following after the `EditorProvider`.
|
|
365
341
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}}
|
|
371
|
-
onClick={() => {
|
|
372
|
-
editor.send({
|
|
373
|
-
type: 'list item.toggle',
|
|
374
|
-
listItem: props.list,
|
|
375
|
-
})
|
|
376
|
-
editor.send({type: 'focus'})
|
|
377
|
-
}}
|
|
378
|
-
>
|
|
379
|
-
{props.list}
|
|
380
|
-
</button>
|
|
381
|
-
)
|
|
382
|
-
}
|
|
342
|
+
```tsx
|
|
343
|
+
<pre style={{border: '1px dashed black', padding: '0.5em'}}>
|
|
344
|
+
{JSON.stringify(value, null, 2)}
|
|
345
|
+
</pre>
|
|
383
346
|
```
|
|
384
347
|
|
|
385
|
-
|
|
348
|
+
This displays the raw Portable Text. To customize how Portable Text renders in your apps, explore the [collection of serializers](https://www.portabletext.org/integrations/serializers/).
|
|
349
|
+
|
|
350
|
+
## Behavior API
|
|
386
351
|
|
|
387
352
|
The Behavior API is a new way of interfacing with the Portable Text Editor. It allows you to think of and treat the editor as a state machine by:
|
|
388
353
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
354
|
+
- Declaratively hooking into editor **events** and defining new behaviors.
|
|
355
|
+
- Imperatively triggering **events**.
|
|
356
|
+
- Deriving editor **state** using **pure functions**.
|
|
357
|
+
- Subscribing to **emitted** editor **events**.
|
|
358
|
+
|
|
359
|
+
Learn more about the [Behaviors](https://www.portabletext.org/concepts/behavior/) and how to [create your own behaviors](https://www.portabletext.org/guides/create-behavior/) in the documentation.
|
|
393
360
|
|
|
394
361
|
## End-User Experience
|
|
395
362
|
|