@portabletext/editor 1.4.1 → 1.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -47,6 +47,7 @@
47
47
  "debug": "^4.3.4",
48
48
  "is-hotkey-esm": "^1.0.0",
49
49
  "lodash": "^4.17.21",
50
+ "lodash.startcase": "^4.4.0",
50
51
  "react-compiler-runtime": "19.0.0-beta-6fc168f-20241025",
51
52
  "slate": "0.110.2",
52
53
  "slate-dom": "^0.111.0",
@@ -60,7 +61,7 @@
60
61
  "@jest/globals": "^29.7.0",
61
62
  "@jest/types": "^29.6.3",
62
63
  "@playwright/test": "1.48.2",
63
- "@portabletext/toolkit": "^2.0.15",
64
+ "@portabletext/toolkit": "^2.0.16",
64
65
  "@sanity/block-tools": "^3.62.3",
65
66
  "@sanity/diff-match-patch": "^3.1.1",
66
67
  "@sanity/pkg-utils": "^6.11.8",
@@ -76,6 +77,7 @@
76
77
  "@types/express": "^4.17.21",
77
78
  "@types/express-ws": "^3.0.5",
78
79
  "@types/lodash": "^4.17.13",
80
+ "@types/lodash.startcase": "^4.4.9",
79
81
  "@types/node": "^18.19.8",
80
82
  "@types/node-ipc": "^9.2.3",
81
83
  "@types/react": "^18.3.12",
@@ -1,13 +1,10 @@
1
1
  import type {
2
2
  ArrayDefinition,
3
3
  ArraySchemaType,
4
- BlockSchemaType,
5
- ObjectSchemaType,
6
4
  Path,
7
5
  PortableTextBlock,
8
6
  PortableTextChild,
9
7
  PortableTextObject,
10
- SpanSchemaType,
11
8
  } from '@sanity/types'
12
9
  import {
13
10
  Component,
@@ -271,9 +268,9 @@ export class PortableTextEditor extends Component<
271
268
  ? editor.editable.isAnnotationActive(annotationType)
272
269
  : false
273
270
  }
274
- static addAnnotation = (
271
+ static addAnnotation = <TSchemaType extends {name: string}>(
275
272
  editor: PortableTextEditor,
276
- type: ObjectSchemaType,
273
+ type: TSchemaType,
277
274
  value?: {[prop: string]: unknown},
278
275
  ):
279
276
  | {
@@ -339,17 +336,17 @@ export class PortableTextEditor extends Component<
339
336
  editor.editable?.isExpandedSelection()
340
337
  static isMarkActive = (editor: PortableTextEditor, mark: string) =>
341
338
  editor.editable?.isMarkActive(mark)
342
- static insertChild = (
339
+ static insertChild = <TSchemaType extends {name: string}>(
343
340
  editor: PortableTextEditor,
344
- type: SpanSchemaType | ObjectSchemaType,
341
+ type: TSchemaType,
345
342
  value?: {[prop: string]: unknown},
346
343
  ): Path | undefined => {
347
344
  debug(`Host inserting child`)
348
345
  return editor.editable?.insertChild(type, value)
349
346
  }
350
- static insertBlock = (
347
+ static insertBlock = <TSchemaType extends {name: string}>(
351
348
  editor: PortableTextEditor,
352
- type: BlockSchemaType | ObjectSchemaType,
349
+ type: TSchemaType,
353
350
  value?: {[prop: string]: unknown},
354
351
  ): Path | undefined => {
355
352
  return editor.editable?.insertBlock(type, value)
@@ -379,9 +376,9 @@ export class PortableTextEditor extends Component<
379
376
  debug(`Host setting selection`, selection)
380
377
  editor.editable?.select(selection)
381
378
  }
382
- static removeAnnotation = (
379
+ static removeAnnotation = <TSchemaType extends {name: string}>(
383
380
  editor: PortableTextEditor,
384
- type: ObjectSchemaType,
381
+ type: TSchemaType,
385
382
  ) => editor.editable?.removeAnnotation(type)
386
383
  static toggleBlockStyle = (
387
384
  editor: PortableTextEditor,
@@ -0,0 +1,111 @@
1
+ import {Schema as SanitySchema} from '@sanity/schema'
2
+ import {
3
+ defineField,
4
+ defineType,
5
+ type BlockDecoratorDefinition,
6
+ } from '@sanity/types'
7
+ import startCase from 'lodash.startcase'
8
+ import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
9
+
10
+ /**
11
+ * @alpha
12
+ */
13
+ export type BaseDefinition = {
14
+ name: string
15
+ title?: string
16
+ icon?: BlockDecoratorDefinition['icon']
17
+ }
18
+
19
+ /**
20
+ * @alpha
21
+ */
22
+ export type SchemaDefinition<
23
+ TBaseDefinition extends BaseDefinition = BaseDefinition,
24
+ > = {
25
+ decorators?: ReadonlyArray<TBaseDefinition>
26
+ blockObjects?: ReadonlyArray<TBaseDefinition>
27
+ inlineObjects?: ReadonlyArray<TBaseDefinition>
28
+ annotations?: ReadonlyArray<TBaseDefinition>
29
+ lists?: ReadonlyArray<TBaseDefinition>
30
+ styles?: ReadonlyArray<TBaseDefinition>
31
+ }
32
+
33
+ /**
34
+ * @alpha
35
+ */
36
+ export function defineSchema<const TSchemaDefinition extends SchemaDefinition>(
37
+ definition: TSchemaDefinition,
38
+ ): TSchemaDefinition {
39
+ return definition
40
+ }
41
+
42
+ export function compileSchemaDefinition<
43
+ TSchemaDefinition extends SchemaDefinition,
44
+ >(definition?: TSchemaDefinition) {
45
+ const blockObjects =
46
+ definition?.blockObjects?.map((blockObject) =>
47
+ defineType({
48
+ type: 'object',
49
+ name: blockObject.name,
50
+ title: blockObject.title,
51
+ icon: blockObject.icon,
52
+ fields: [],
53
+ }),
54
+ ) ?? []
55
+ const inlineObjects =
56
+ definition?.inlineObjects?.map((inlineObject) =>
57
+ defineType({
58
+ type: 'object',
59
+ name: inlineObject.name,
60
+ title: inlineObject.title,
61
+ icon: inlineObject.icon,
62
+ fields: [],
63
+ }),
64
+ ) ?? []
65
+
66
+ const portableTextSchema = defineField({
67
+ type: 'array',
68
+ name: 'portable-text',
69
+ of: [
70
+ ...blockObjects.map((blockObject) => ({type: blockObject.name})),
71
+ {
72
+ type: 'block',
73
+ name: 'block',
74
+ of: inlineObjects.map((inlineObject) => ({type: inlineObject.name})),
75
+ marks: {
76
+ decorators:
77
+ definition?.decorators?.map((decorator) => ({
78
+ title: decorator.title ?? startCase(decorator.name),
79
+ value: decorator.name,
80
+ icon: decorator.icon,
81
+ })) ?? [],
82
+ annotations:
83
+ definition?.annotations?.map((annotation) => ({
84
+ name: annotation.name,
85
+ type: 'object',
86
+ title: annotation.title,
87
+ icon: annotation.icon,
88
+ })) ?? [],
89
+ },
90
+ lists:
91
+ definition?.lists?.map((list) => ({
92
+ value: list.name,
93
+ title: list.title ?? startCase(list.name),
94
+ icon: list.icon,
95
+ })) ?? [],
96
+ styles:
97
+ definition?.styles?.map((style) => ({
98
+ value: style.name,
99
+ title: style.title ?? startCase(style.name),
100
+ icon: style.icon,
101
+ })) ?? [],
102
+ },
103
+ ],
104
+ })
105
+
106
+ const schema = SanitySchema.compile({
107
+ types: [portableTextSchema, ...blockObjects, ...inlineObjects],
108
+ }).get('portable-text')
109
+
110
+ return getPortableTextMemberSchemaTypes(schema)
111
+ }
@@ -1,13 +1,11 @@
1
1
  import {
2
2
  isPortableTextSpan,
3
- type ObjectSchemaType,
4
3
  type Path,
5
4
  type PortableTextBlock,
6
5
  type PortableTextChild,
7
6
  type PortableTextObject,
8
7
  type PortableTextSpan,
9
8
  type PortableTextTextBlock,
10
- type SchemaType,
11
9
  } from '@sanity/types'
12
10
  import {
13
11
  Editor,
@@ -128,7 +126,10 @@ export function createWithEditableAPI(
128
126
  }
129
127
  return undefined
130
128
  },
131
- insertChild: (type: SchemaType, value?: {[prop: string]: any}): Path => {
129
+ insertChild: <TSchemaType extends {name: string}>(
130
+ type: TSchemaType,
131
+ value?: {[prop: string]: any},
132
+ ): Path => {
132
133
  if (!editor.selection) {
133
134
  throw new Error('The editor has no selection')
134
135
  }
@@ -196,7 +197,10 @@ export function createWithEditableAPI(
196
197
  )?.focus.path || []
197
198
  )
198
199
  },
199
- insertBlock: (type: SchemaType, value?: {[prop: string]: any}): Path => {
200
+ insertBlock: <TSchemaType extends {name: string}>(
201
+ type: TSchemaType,
202
+ value?: {[prop: string]: any},
203
+ ): Path => {
200
204
  const block = toSlateValue(
201
205
  [
202
206
  {
@@ -622,7 +626,9 @@ export function createWithEditableAPI(
622
626
  }
623
627
  }
624
628
  },
625
- removeAnnotation: (type: ObjectSchemaType): void => {
629
+ removeAnnotation: <TSchemaType extends {name: string}>(
630
+ type: TSchemaType,
631
+ ): void => {
626
632
  debug('Removing annotation', type)
627
633
 
628
634
  Editor.withoutNormalizing(editor, () => {
@@ -7,6 +7,7 @@ import {useActorRef} from '@xstate/react'
7
7
  import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
8
8
  import {compileType} from '../utils/schema'
9
9
  import type {Behavior} from './behavior/behavior.types'
10
+ import {compileSchemaDefinition, type SchemaDefinition} from './define-schema'
10
11
  import {editorMachine} from './editor-machine'
11
12
  import {defaultKeyGenerator} from './key-generator'
12
13
 
@@ -16,8 +17,16 @@ import {defaultKeyGenerator} from './key-generator'
16
17
  export type EditorConfig = {
17
18
  behaviors?: Array<Behavior>
18
19
  keyGenerator?: () => string
19
- schema: ArraySchemaType<PortableTextBlock> | ArrayDefinition
20
- }
20
+ } & (
21
+ | {
22
+ schemaDefinition: SchemaDefinition
23
+ schema?: undefined
24
+ }
25
+ | {
26
+ schemaDefinition?: undefined
27
+ schema: ArraySchemaType<PortableTextBlock> | ArrayDefinition
28
+ }
29
+ )
21
30
 
22
31
  /**
23
32
  * @alpha
@@ -28,11 +37,14 @@ export type Editor = ReturnType<typeof useEditor>
28
37
  * @alpha
29
38
  */
30
39
  export function useEditor(config: EditorConfig) {
31
- const schema = getPortableTextMemberSchemaTypes(
32
- config.schema.hasOwnProperty('jsonType')
33
- ? config.schema
34
- : compileType(config.schema),
35
- )
40
+ const schema = config.schemaDefinition
41
+ ? compileSchemaDefinition(config.schemaDefinition)
42
+ : getPortableTextMemberSchemaTypes(
43
+ config.schema.hasOwnProperty('jsonType')
44
+ ? config.schema
45
+ : compileType(config.schema),
46
+ )
47
+
36
48
  const editorActor = useActorRef(editorMachine, {
37
49
  input: {
38
50
  behaviors: config.behaviors,
package/src/index.ts CHANGED
@@ -1,18 +1,24 @@
1
1
  export type {Patch} from '@portabletext/patches'
2
+ export type {PortableTextBlock} from '@sanity/types'
2
3
  export {
4
+ createMarkdownBehaviors,
5
+ type MarkdownBehaviorsConfig,
6
+ } from './editor/behavior/behavior.markdown'
7
+ export {
8
+ defineBehavior,
3
9
  type Behavior,
4
10
  type BehaviorActionIntend,
11
+ type BehaviorActionIntendSet,
5
12
  type BehaviorContext,
6
13
  type BehaviorEvent,
7
14
  type BehaviorGuard,
8
15
  type PickFromUnion,
9
- type BehaviorActionIntendSet,
10
- defineBehavior,
11
16
  } from './editor/behavior/behavior.types'
12
17
  export {
13
- createMarkdownBehaviors,
14
- type MarkdownBehaviorsConfig,
15
- } from './editor/behavior/behavior.markdown'
18
+ defineSchema,
19
+ type SchemaDefinition,
20
+ type BaseDefinition,
21
+ } from './editor/define-schema'
16
22
  export {PortableTextEditable} from './editor/Editable'
17
23
  export type {PortableTextEditableProps} from './editor/Editable'
18
24
  export {
@@ -3,7 +3,6 @@ import type {
3
3
  ArraySchemaType,
4
4
  BlockDecoratorDefinition,
5
5
  BlockListDefinition,
6
- BlockSchemaType,
7
6
  BlockStyleDefinition,
8
7
  ObjectSchemaType,
9
8
  Path,
@@ -13,7 +12,6 @@ import type {
13
12
  PortableTextObject,
14
13
  PortableTextSpan,
15
14
  PortableTextTextBlock,
16
- SpanSchemaType,
17
15
  TypedObject,
18
16
  } from '@sanity/types'
19
17
  import type {
@@ -44,8 +42,8 @@ export interface EditableAPIDeleteOptions {
44
42
  export interface EditableAPI {
45
43
  activeAnnotations: () => PortableTextObject[]
46
44
  isAnnotationActive: (annotationType: PortableTextObject['_type']) => boolean
47
- addAnnotation: (
48
- type: ObjectSchemaType,
45
+ addAnnotation: <TSchemaType extends {name: string}>(
46
+ type: TSchemaType,
49
47
  value?: {[prop: string]: unknown},
50
48
  ) =>
51
49
  | {markDefPath: Path; markDefPaths: Array<Path>; spanPath: Path}
@@ -69,12 +67,12 @@ export interface EditableAPI {
69
67
  getValue: () => PortableTextBlock[] | undefined
70
68
  hasBlockStyle: (style: string) => boolean
71
69
  hasListStyle: (listStyle: string) => boolean
72
- insertBlock: (
73
- type: BlockSchemaType | ObjectSchemaType,
70
+ insertBlock: <TSchemaType extends {name: string}>(
71
+ type: TSchemaType,
74
72
  value?: {[prop: string]: unknown},
75
73
  ) => Path
76
- insertChild: (
77
- type: SpanSchemaType | ObjectSchemaType,
74
+ insertChild: <TSchemaType extends {name: string}>(
75
+ type: TSchemaType,
78
76
  value?: {[prop: string]: unknown},
79
77
  ) => Path
80
78
  insertBreak: () => void
@@ -88,7 +86,9 @@ export interface EditableAPI {
88
86
  isVoid: (element: PortableTextBlock | PortableTextChild) => boolean
89
87
  marks: () => string[]
90
88
  redo: () => void
91
- removeAnnotation: (type: ObjectSchemaType) => void
89
+ removeAnnotation: <TSchemaType extends {name: string}>(
90
+ type: TSchemaType,
91
+ ) => void
92
92
  select: (selection: EditorSelection) => void
93
93
  toggleBlockStyle: (blockStyle: string) => void
94
94
  toggleList: (listStyle: string) => void