@supernova-studio/client 0.0.15 → 0.2.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.
@@ -6,6 +6,14 @@ import {
6
6
  PageBlockTextSpan,
7
7
  PageBlockTextSpanAttribute,
8
8
  PageBlockDefinitionProperty,
9
+ PageBlockCalloutType,
10
+ PageBlockItemRichTextPropertyValue,
11
+ PageBlockItemMultiRichTextPropertyValue,
12
+ PageBlockItemEmbedPropertyValue,
13
+ PageBlockItemV2,
14
+ PageBlockItemTextPropertyValue,
15
+ PageBlockItemUntypedPropertyValue,
16
+ PageBlockItemImageValue,
9
17
  } from "@supernova-studio/model";
10
18
  import { PageBlockEditorModel } from "./model/block";
11
19
  import { DocumentationPageEditorModel } from "./model/page";
@@ -52,9 +60,27 @@ export function prosemirrorNodeToBlock(
52
60
  return parseAsRichText(prosemirrorNode, definition, richTextProperty);
53
61
  }
54
62
 
63
+ const multiRichTextProperty = BlockDefinitionUtils.firstMultiRichTextProperty(definition);
64
+ if (multiRichTextProperty) {
65
+ return parseAsMultiRichText(prosemirrorNode, definition, multiRichTextProperty);
66
+ }
67
+
68
+ const embedProperty = BlockDefinitionUtils.firstEmbedProperty(definition);
69
+ if (prosemirrorNode.type === "embed" && embedProperty) {
70
+ return parseAsEmbed(prosemirrorNode, definition, embedProperty);
71
+ }
72
+
73
+ if (definition.id === "io.supernova.block.divider") {
74
+ return parseAsDivider(prosemirrorNode, definition);
75
+ }
76
+
55
77
  return parseAsCustomBlock(prosemirrorNode, definition);
56
78
  }
57
79
 
80
+ //
81
+ // Single rich text
82
+ //
83
+
58
84
  function parseAsRichText(
59
85
  prosemirrorNode: ProsemirrorNode,
60
86
  definition: PageBlockDefinition,
@@ -62,21 +88,89 @@ function parseAsRichText(
62
88
  ): PageBlockEditorModel {
63
89
  const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
64
90
  const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
91
+ const calloutType = parseCalloutType(parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "type"));
65
92
 
66
93
  return {
67
94
  // TODO Artem: indent
68
95
  id: id,
96
+
97
+ ...(variantId && { variantId: variantId }),
98
+
69
99
  data: {
70
100
  packageId: definition.id,
71
101
  indentLevel: 0,
72
- variantId: variantId,
73
102
  items: [
74
103
  {
75
104
  id: id,
76
105
  props: {
77
106
  [property.id]: {
107
+ // Required
78
108
  value: parseRichText(prosemirrorNode.content ?? []),
79
- },
109
+
110
+ // Optional
111
+ ...(calloutType && { calloutType: calloutType }),
112
+ } satisfies PageBlockItemRichTextPropertyValue,
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ };
118
+ }
119
+
120
+ function parseCalloutType(prosemirrorCalloutType: string | undefined): PageBlockCalloutType | undefined {
121
+ if (!prosemirrorCalloutType) return undefined;
122
+
123
+ switch (prosemirrorCalloutType) {
124
+ case "error":
125
+ return "Error";
126
+ case "neutral":
127
+ return "Info";
128
+ case "success":
129
+ return "Success";
130
+ case "warning":
131
+ return "Warning";
132
+ }
133
+ }
134
+
135
+ //
136
+ // Multi rich text
137
+ //
138
+
139
+ function parseAsMultiRichText(
140
+ prosemirrorNode: ProsemirrorNode,
141
+ definition: PageBlockDefinition,
142
+ property: PageBlockDefinitionProperty
143
+ ) {
144
+ const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
145
+ const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
146
+
147
+ return {
148
+ // TODO Artem: indent
149
+ id: id,
150
+ data: {
151
+ packageId: definition.id,
152
+ indentLevel: 0,
153
+
154
+ ...(variantId && { variantId: variantId }),
155
+
156
+ items: [
157
+ {
158
+ id: id,
159
+ props: {
160
+ [property.id]: {
161
+ // Required
162
+ value: (prosemirrorNode.content ?? [])
163
+ .map(listItem => {
164
+ if (listItem.type !== "listitem") return null;
165
+ if (!listItem.content?.length) return parseRichText([]);
166
+
167
+ const paragraph = listItem.content[0];
168
+ if (paragraph.type !== "paragraph") return parseRichText([]);
169
+
170
+ return parseRichText(paragraph.content ?? []);
171
+ })
172
+ .filter(nonNullFilter),
173
+ } satisfies PageBlockItemMultiRichTextPropertyValue,
80
174
  },
81
175
  },
82
176
  ],
@@ -84,6 +178,10 @@ function parseAsRichText(
84
178
  };
85
179
  }
86
180
 
181
+ //
182
+ // Rich text
183
+ //
184
+
87
185
  function parseRichText(spans: ProsemirrorNode[]): PageBlockText {
88
186
  return {
89
187
  spans: spans.map(parseRichTextSpan).filter(nonNullFilter),
@@ -126,6 +224,67 @@ function parseRichTextAttribute(mark: ProsemirrorMark): PageBlockTextSpanAttribu
126
224
  return null;
127
225
  }
128
226
 
227
+ //
228
+ // Embed
229
+ //
230
+
231
+ function parseAsEmbed(
232
+ prosemirrorNode: ProsemirrorNode,
233
+ definition: PageBlockDefinition,
234
+ property: PageBlockDefinitionProperty
235
+ ): PageBlockEditorModel {
236
+ const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
237
+ const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
238
+
239
+ const url = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "url");
240
+ const caption = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "caption");
241
+ const height = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "height");
242
+
243
+ return {
244
+ id: id,
245
+ data: {
246
+ packageId: definition.id,
247
+ indentLevel: 0,
248
+
249
+ ...(variantId && { variantId: variantId }),
250
+
251
+ items: [
252
+ {
253
+ id: id,
254
+ props: {
255
+ [property.id]: {
256
+ value: url,
257
+ caption: caption,
258
+ height: height,
259
+ } as PageBlockItemEmbedPropertyValue,
260
+ },
261
+ },
262
+ ],
263
+ },
264
+ };
265
+ }
266
+
267
+ //
268
+ // Divider
269
+ //
270
+
271
+ function parseAsDivider(prosemirrorNode: ProsemirrorNode, definition: PageBlockDefinition): PageBlockEditorModel {
272
+ const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
273
+ const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
274
+
275
+ return {
276
+ id: id,
277
+ data: {
278
+ packageId: definition.id,
279
+ indentLevel: 0,
280
+
281
+ ...(variantId && { variantId: variantId }),
282
+
283
+ items: [{ id: id, props: {} }],
284
+ },
285
+ };
286
+ }
287
+
129
288
  //
130
289
  // Custom block
131
290
  //
@@ -134,7 +293,71 @@ function parseAsCustomBlock(
134
293
  prosemirrorNode: ProsemirrorNode,
135
294
  definition: PageBlockDefinition
136
295
  ): PageBlockEditorModel | null {
137
- return null;
296
+ const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
297
+ const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
298
+
299
+ const itemsString = parseProsemirrorBlockAttribute(prosemirrorNode, "items");
300
+ const itemsJson = JSON.parse(itemsString);
301
+ if (!Array.isArray(itemsJson)) {
302
+ console.error("Block `items` property must be a json array");
303
+ return null;
304
+ }
305
+
306
+ const parsedItems = itemsJson.map(i => parseItem(i, definition));
307
+
308
+ return {
309
+ id: id,
310
+ data: {
311
+ packageId: definition.id,
312
+ indentLevel: 0,
313
+
314
+ ...(variantId && { variantId: variantId }),
315
+
316
+ items: parsedItems,
317
+ },
318
+ };
319
+ }
320
+
321
+ function parseItem(rawItem: unknown, definition: PageBlockDefinition): PageBlockItemV2 | null {
322
+ const parsedItem = PageBlockItemV2.safeParse(rawItem);
323
+ if (!parsedItem.success) {
324
+ return null;
325
+ }
326
+
327
+ const sanitizedProps: Record<string, PageBlockItemUntypedPropertyValue> = {};
328
+
329
+ for (const property of definition.item.properties) {
330
+ const value = parsedItem.data.props[property.id];
331
+ if (!value) {
332
+ return null;
333
+ }
334
+
335
+ // Validate
336
+ switch (property.type) {
337
+ case "Text":
338
+ PageBlockItemTextPropertyValue.parse(value);
339
+ break;
340
+
341
+ case "EmbedURL":
342
+ PageBlockItemEmbedPropertyValue.parse(value);
343
+ break;
344
+
345
+ case "Image":
346
+ PageBlockItemImageValue.parse(value);
347
+ break;
348
+
349
+ default:
350
+ return null;
351
+ }
352
+
353
+ sanitizedProps[property.id] = value;
354
+ }
355
+
356
+ return {
357
+ id: parsedItem.data.id,
358
+ linksTo: parsedItem.data.linksTo,
359
+ props: sanitizedProps,
360
+ };
138
361
  }
139
362
 
140
363
  //
@@ -2,6 +2,10 @@ import { PageBlockEditorModel } from "./model/block";
2
2
  import {
3
3
  PageBlockDefinition,
4
4
  PageBlockDefinitionPropertyType,
5
+ PageBlockItemEmbedPropertyValue,
6
+ PageBlockItemMultiRichTextPropertyValue,
7
+ PageBlockItemRichTextPropertyValue,
8
+ PageBlockItemUntypedPropertyValue,
5
9
  PageBlockItemV2,
6
10
  PageBlockText,
7
11
  } from "@supernova-studio/model";
@@ -14,15 +18,29 @@ export const BlockParsingUtils = {
14
18
  return block.data.items[0]!;
15
19
  },
16
20
 
17
- richTextPropertyValue(item: PageBlockItemV2, propertyKey: string) {
21
+ richTextPropertyValue(item: PageBlockItemV2, propertyKey: string): PageBlockItemRichTextPropertyValue {
18
22
  const objectValue = this.objectPropertyValue(item, propertyKey);
19
- const richText = PageBlockText.parse(objectValue.value);
23
+ const richText = PageBlockItemRichTextPropertyValue.parse(objectValue);
20
24
 
21
25
  return richText;
22
26
  },
23
27
 
28
+ multiRichTextPropertyValue(item: PageBlockItemV2, propertyKey: string): PageBlockItemMultiRichTextPropertyValue {
29
+ const objectValue = this.objectPropertyValue(item, propertyKey);
30
+ const richText = PageBlockItemMultiRichTextPropertyValue.parse(objectValue);
31
+
32
+ return richText;
33
+ },
34
+
35
+ embedPropertyValue(item: PageBlockItemV2, propertyKey: string): PageBlockItemEmbedPropertyValue {
36
+ const objectValue = this.objectPropertyValue(item, propertyKey);
37
+ const embed = PageBlockItemEmbedPropertyValue.parse(objectValue);
38
+
39
+ return embed;
40
+ },
41
+
24
42
  objectPropertyValue(item: PageBlockItemV2, propertyKey: string) {
25
- const value = this.propertyValue(item, propertyKey);
43
+ const value = this.propertyValueOrThrow(item, propertyKey);
26
44
 
27
45
  if (typeof value !== "object") {
28
46
  throw new Error(`Value for property ${propertyKey} is expected to be an object, but instead is ${typeof value}`);
@@ -31,16 +49,12 @@ export const BlockParsingUtils = {
31
49
  return value;
32
50
  },
33
51
 
34
- propertyValue(item: PageBlockItemV2, propertyKey: string) {
35
- const value = this.safePropertyValue(item, propertyKey);
52
+ propertyValueOrThrow(item: PageBlockItemV2, propertyKey: string): PageBlockItemUntypedPropertyValue {
53
+ const value = item.props[propertyKey];
36
54
  if (!value) throw new Error(`Property ${propertyKey} is not defined on block item`);
37
55
 
38
56
  return value;
39
57
  },
40
-
41
- safePropertyValue(item: PageBlockItemV2, propertyKey: string) {
42
- return item.props[propertyKey] ?? undefined;
43
- },
44
58
  };
45
59
 
46
60
  export const BlockDefinitionUtils = {
@@ -50,12 +64,24 @@ export const BlockDefinitionUtils = {
50
64
  return undefined;
51
65
  },
52
66
 
67
+ firstMultiRichTextProperty(definition: PageBlockDefinition) {
68
+ const property = definition.item.properties.find(p => p.type === "MultiRichText");
69
+ if (property) return property;
70
+ return undefined;
71
+ },
72
+
53
73
  firstTableProperty(definition: PageBlockDefinition) {
54
74
  const property = definition.item.properties.find(p => p.type === "RichText");
55
75
  if (property) return property;
56
76
  return undefined;
57
77
  },
58
78
 
79
+ firstEmbedProperty(definition: PageBlockDefinition) {
80
+ const property = definition.item.properties.find(p => p.type === "EmbedURL");
81
+ if (property) return property;
82
+ return undefined;
83
+ },
84
+
59
85
  richTextProperty(definition: PageBlockDefinition, propertyKey: string) {
60
86
  return this.property(definition, propertyKey, "RichText");
61
87
  },