@portabletext/editor 1.16.3 → 1.16.4

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 CHANGED
@@ -9,20 +9,8 @@
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
- > [!NOTE]
13
- > We are currently working hard on the general release of this component. Better docs and refined APIs are coming.
14
-
15
- ## End-User Experience
16
-
17
- In order to provide a robust and consistent end-user experience, the editor is backed by an elaborate E2E test suite generated from a [human-readable Gherkin spec](/packages/editor/gherkin-spec/).
18
-
19
12
  ## Build Your Own Portable Text Editor
20
13
 
21
- > [!WARNING]
22
- > The `@portabletext/editor` is currently on the path to deprecate legacy APIs and introduce new ones. The end goals are to make the editor easier to use outside of `Sanity` (and without `@sanity/*` libraries) as well as providing a brand new API to configure the behavior of the editor.
23
- >
24
- > This means that the `defineSchema` and `EditorProvider` APIs showcased here are still experimental APIs tagged with `@alpha` and cannot be considered stable yet. At the same time, the examples below showcase usages of legacy static methods on the `PortableTextEditor` (for example, `PortableTextEditor.isMarkActive(...)` and `PortableTextEditor.toggleMark(...)`) that will soon be discouraged and deprecrated.
25
-
26
14
  Check [/examples/basic/src/App.tsx](/examples/basic/src/App.tsx) for a basic example of how to set up the edior. Most of the source code from this example app can also be found in the instructions below.
27
15
 
28
16
  ### Define the Schema
@@ -219,153 +207,177 @@ function isStockTicker(
219
207
 
220
208
  ### Render the Toolbar
221
209
 
222
- Your toolbar needs to be rendered within `EditorProvider` because it requires a reference to the `editorInstance` that it produces. To toggle marks and styles and to insert objects, you'll have to use this `editorInstance` together with static methods on the `PortableTextEditor` class.
210
+ Your toolbar needs to be rendered within `EditorProvider` because it requires a reference to the `editor` that it produces. To toggle marks and styles and to insert objects, you'll have to use the `.send` method on this `editor` instance.
223
211
 
224
212
  ```tsx
225
213
  function Toolbar() {
226
214
  // Obtain the editor instance
227
- const editorInstance = usePortableTextEditor()
228
- // Rerender the toolbar whenever the selection changes
229
- usePortableTextEditorSelection()
215
+ const editor = useEditor()
230
216
 
231
- const decoratorButtons = schemaDefinition.decorators.map((decorator) => {
232
- return (
233
- <button
234
- key={decorator.name}
235
- style={{
236
- textDecoration: PortableTextEditor.isMarkActive(
237
- editorInstance,
238
- decorator.name,
239
- )
240
- ? 'underline'
241
- : 'unset',
242
- }}
243
- onClick={() => {
244
- // Toggle the decorator by name
245
- PortableTextEditor.toggleMark(editorInstance, decorator.name)
246
- // Pressing this button steals focus so let's focus the editor again
247
- PortableTextEditor.focus(editorInstance)
248
- }}
249
- >
250
- {decorator.name}
251
- </button>
252
- )
253
- })
217
+ const decoratorButtons = schemaDefinition.decorators.map((decorator) => (
218
+ <DecoratorButton key={decorator.name} decorator={decorator.name} />
219
+ ))
220
+
221
+ const annotationButtons = schemaDefinition.annotations.map((annotation) => (
222
+ <AnnotationButton key={annotation.name} annotation={annotation} />
223
+ ))
224
+
225
+ const styleButtons = schemaDefinition.styles.map((style) => (
226
+ <StyleButton key={style.name} style={style.name} />
227
+ ))
254
228
 
255
- const linkButton = (
229
+ const listButtons = schemaDefinition.lists.map((list) => (
230
+ <ListButton key={list.name} list={list.name} />
231
+ ))
232
+
233
+ const imageButton = (
256
234
  <button
257
- style={{
258
- textDecoration: PortableTextEditor.isAnnotationActive(
259
- editorInstance,
260
- schemaDefinition.annotations[0].name,
261
- )
262
- ? 'underline'
263
- : 'unset',
264
- }}
265
235
  onClick={() => {
266
- if (
267
- PortableTextEditor.isAnnotationActive(
268
- editorInstance,
269
- schemaDefinition.annotations[0].name,
270
- )
271
- ) {
272
- PortableTextEditor.removeAnnotation(
273
- editorInstance,
274
- schemaDefinition.annotations[0],
275
- )
276
- } else {
277
- PortableTextEditor.addAnnotation(
278
- editorInstance,
279
- schemaDefinition.annotations[0],
280
- {href: 'https://example.com'},
281
- )
282
- }
283
- PortableTextEditor.focus(editorInstance)
236
+ editor.send({
237
+ type: 'insert.block object',
238
+ blockObject: {
239
+ name: 'image',
240
+ value: {src: 'https://example.com/image.jpg'},
241
+ },
242
+ placement: 'auto',
243
+ })
244
+ editor.send({type: 'focus'})
284
245
  }}
285
246
  >
286
- link
247
+ {schemaDefinition.blockObjects[0].name}
287
248
  </button>
288
249
  )
289
250
 
290
- const styleButtons = schemaDefinition.styles.map((style) => (
251
+ const stockTickerButton = (
291
252
  <button
292
- key={style.name}
293
- style={{
294
- textDecoration: PortableTextEditor.hasBlockStyle(
295
- editorInstance,
296
- style.name,
297
- )
298
- ? 'underline'
299
- : 'unset',
300
- }}
301
253
  onClick={() => {
302
- PortableTextEditor.toggleBlockStyle(editorInstance, style.name)
303
- PortableTextEditor.focus(editorInstance)
254
+ editor.send({
255
+ type: 'insert.inline object',
256
+ inlineObject: {
257
+ name: 'stock-ticker',
258
+ value: {symbol: 'AAPL'},
259
+ },
260
+ })
261
+ editor.send({type: 'focus'})
304
262
  }}
305
263
  >
306
- {style.name}
264
+ {schemaDefinition.inlineObjects[0].name}
307
265
  </button>
308
- ))
266
+ )
309
267
 
310
- const listButtons = schemaDefinition.lists.map((list) => (
268
+ return (
269
+ <>
270
+ <div>{decoratorButtons}</div>
271
+ <div>{annotationButtons}</div>
272
+ <div>{styleButtons}</div>
273
+ <div>{listButtons}</div>
274
+ <div>{imageButton}</div>
275
+ <div>{stockTickerButton}</div>
276
+ </>
277
+ )
278
+ }
279
+
280
+ function DecoratorButton(props: {decorator: string}) {
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
+ )
288
+
289
+ return (
311
290
  <button
312
- key={list.name}
313
291
  style={{
314
- textDecoration: PortableTextEditor.hasListStyle(
315
- editorInstance,
316
- list.name,
317
- )
318
- ? 'underline'
319
- : 'unset',
292
+ textDecoration: active ? 'underline' : 'unset',
320
293
  }}
321
294
  onClick={() => {
322
- PortableTextEditor.toggleList(editorInstance, list.name)
323
- PortableTextEditor.focus(editorInstance)
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'})
324
302
  }}
325
303
  >
326
- {list.name}
304
+ {props.decorator}
327
305
  </button>
328
- ))
306
+ )
307
+ }
329
308
 
330
- const imageButton = (
309
+ function AnnotationButton(props: {annotation: {name: string}}) {
310
+ const editor = useEditor()
311
+ const active = useEditorSelector(
312
+ editor,
313
+ selectors.isActiveAnnotation(props.annotation.name),
314
+ )
315
+
316
+ return (
331
317
  <button
318
+ style={{
319
+ textDecoration: active ? 'underline' : 'unset',
320
+ }}
332
321
  onClick={() => {
333
- PortableTextEditor.insertBlock(
334
- editorInstance,
335
- schemaDefinition.blockObjects[0],
336
- {src: 'https://example.com/image.jpg'},
337
- )
338
- PortableTextEditor.focus(editorInstance)
322
+ editor.send({
323
+ type: 'annotation.toggle',
324
+ annotation: {
325
+ name: props.annotation.name,
326
+ value:
327
+ props.annotation.name === 'link'
328
+ ? {href: 'https://example.com'}
329
+ : {},
330
+ },
331
+ })
332
+ editor.send({type: 'focus'})
339
333
  }}
340
334
  >
341
- {schemaDefinition.blockObjects[0].name}
335
+ {props.annotation.name}
342
336
  </button>
343
337
  )
338
+ }
344
339
 
345
- const stockTickerButton = (
340
+ function StyleButton(props: {style: string}) {
341
+ const editor = useEditor()
342
+ const active = useEditorSelector(editor, selectors.isActiveStyle(props.style))
343
+
344
+ return (
346
345
  <button
346
+ style={{
347
+ textDecoration: active ? 'underline' : 'unset',
348
+ }}
347
349
  onClick={() => {
348
- PortableTextEditor.insertChild(
349
- editorInstance,
350
- schemaDefinition.inlineObjects[0],
351
- {symbol: 'AAPL'},
352
- )
353
- PortableTextEditor.focus(editorInstance)
350
+ editor.send({type: 'style.toggle', style: props.style})
351
+ editor.send({type: 'focus'})
354
352
  }}
355
353
  >
356
- {schemaDefinition.inlineObjects[0].name}
354
+ {props.style}
357
355
  </button>
358
356
  )
357
+ }
358
+
359
+ function ListButton(props: {list: string}) {
360
+ const editor = useEditor()
361
+ const active = useEditorSelector(
362
+ editor,
363
+ selectors.isActiveListItem(props.list),
364
+ )
359
365
 
360
366
  return (
361
- <>
362
- <div>{decoratorButtons}</div>
363
- <div>{linkButton}</div>
364
- <div>{styleButtons}</div>
365
- <div>{listButtons}</div>
366
- <div>{imageButton}</div>
367
- <div>{stockTickerButton}</div>
368
- </>
367
+ <button
368
+ style={{
369
+ textDecoration: active ? 'underline' : 'unset',
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>
369
381
  )
370
382
  }
371
383
  ```
@@ -379,6 +391,10 @@ The Behavior API is a new way of interfacing with the Portable Text Editor. It a
379
391
  3. Deriving editor **state** using **pure functions**.
380
392
  4. Subscribe to **emitted** editor **events** using `editor.on(…)`.
381
393
 
394
+ ## End-User Experience
395
+
396
+ In order to provide a robust and consistent end-user experience, the editor is backed by an elaborate E2E test suite generated from a [human-readable Gherkin spec](/packages/editor/gherkin-spec/).
397
+
382
398
  ## Development
383
399
 
384
400
  ### Develop Together with Sanity Studio