@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supernova-studio/client",
3
- "version": "0.0.15",
3
+ "version": "0.2.0",
4
4
  "description": "Supernova Data Models",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -24,7 +24,8 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "build": "tsc",
27
- "typecheck": "tsc --noEmit"
27
+ "typecheck": "tsc --noEmit",
28
+ "test:unit": "vitest run"
28
29
  },
29
30
  "author": "",
30
31
  "license": "ISC",
@@ -2,34 +2,45 @@ import * as Y from "yjs";
2
2
  import {
3
3
  PageBlockDefinition,
4
4
  PageBlockDefinitionProperty,
5
- PageBlockItemV2,
6
5
  PageBlockText,
7
6
  PageBlockTextSpan,
8
7
  PageBlockTextSpanAttribute,
9
- PageBlockDefinitionRichTextPropertyStyle,
8
+ PageBlockItemRichTextPropertyValue,
9
+ PageBlockCalloutType,
10
+ PageBlockItemMultiRichTextPropertyValue,
10
11
  } from "@supernova-studio/model";
11
12
  import { PageBlockEditorModel } from "./model/block";
12
- import {
13
- ProsemirrorBlockItem,
14
- ProsemirrorBlockItemPropertyValue,
15
- ProsemirrorNode,
16
- ProsemirrorMark,
17
- } from "./prosemirror/types";
13
+ import { ProsemirrorNode, ProsemirrorMark } from "./prosemirror/types";
18
14
  import { BlockDefinitionUtils, BlockParsingUtils } from "./utils";
19
15
  import { DocumentationPageEditorModel } from "./model";
20
16
  import { prosemirrorJSONToYXmlFragment } from "y-prosemirror";
21
17
  import { pmSchema } from "./prosemirror";
22
18
 
23
- type Input = {
19
+ //
20
+ // Types
21
+ //
22
+
23
+ type BaseInput = {
24
24
  block: PageBlockEditorModel;
25
25
  definition: PageBlockDefinition;
26
- richTextProperty: PageBlockDefinitionProperty;
27
26
  };
28
27
 
29
- type InputWithValue = Input & {
30
- richTextPropertyValue: PageBlockText;
28
+ type InputWithProperty = BaseInput & {
29
+ property: PageBlockDefinitionProperty;
31
30
  };
32
31
 
32
+ type RichTextInputWithValue = InputWithProperty & {
33
+ richTextPropertyValue: PageBlockItemRichTextPropertyValue;
34
+ };
35
+
36
+ type MultiRichTextInputWithValue = InputWithProperty & {
37
+ multiRichTextPropertyValue: PageBlockItemMultiRichTextPropertyValue;
38
+ };
39
+
40
+ //
41
+ // Implementation
42
+ //
43
+
33
44
  export function pageToYXmlFragment(
34
45
  page: DocumentationPageEditorModel,
35
46
  definitions: PageBlockDefinition[],
@@ -65,26 +76,62 @@ export function blockToProsemirrorNode(
65
76
  block: PageBlockEditorModel,
66
77
  definition: PageBlockDefinition
67
78
  ): ProsemirrorNode | null {
79
+ // Single rich text
68
80
  const richTextProperty = BlockDefinitionUtils.firstRichTextProperty(definition);
69
-
70
81
  if (richTextProperty) {
71
82
  return serializeAsRichTextBlock({
72
83
  block: block,
73
84
  definition: definition,
74
- richTextProperty: richTextProperty,
85
+ property: richTextProperty,
86
+ });
87
+ }
88
+
89
+ // Multi rich text
90
+ const multiRichTextProperty = BlockDefinitionUtils.firstMultiRichTextProperty(definition);
91
+ if (multiRichTextProperty) {
92
+ return serializeAsMultiRichTextBlock({
93
+ block: block,
94
+ definition: definition,
95
+ property: multiRichTextProperty,
96
+ });
97
+ }
98
+
99
+ // Embed
100
+ const embedProperty = BlockDefinitionUtils.firstEmbedProperty(definition);
101
+ const embedType = serializeEmbedType(block.data.packageId);
102
+ if (embedProperty && embedType) {
103
+ return serializeAsEmbed(
104
+ {
105
+ block: block,
106
+ definition: definition,
107
+ property: embedProperty,
108
+ },
109
+ embedType
110
+ );
111
+ }
112
+
113
+ // Divider
114
+ if (block.data.packageId === "io.supernova.block.divider") {
115
+ return serializeAsDivider({
116
+ block: block,
117
+ definition: definition,
75
118
  });
76
119
  }
77
120
 
78
121
  return serializeAsCustomBlock(block, definition);
79
122
  }
80
123
 
81
- export function serializeAsRichTextBlock(input: Input): ProsemirrorNode {
82
- const { block, definition, richTextProperty } = input;
124
+ //
125
+ // Single rich text blocks
126
+ //
127
+
128
+ function serializeAsRichTextBlock(input: InputWithProperty): ProsemirrorNode {
129
+ const { block, definition, property: richTextProperty } = input;
83
130
 
84
131
  const blockItem = BlockParsingUtils.singleBlockItem(block);
85
132
  const textPropertyValue = BlockParsingUtils.richTextPropertyValue(blockItem, richTextProperty.id);
86
133
 
87
- const enrichedInput: InputWithValue = { ...input, richTextPropertyValue: textPropertyValue };
134
+ const enrichedInput: RichTextInputWithValue = { ...input, richTextPropertyValue: textPropertyValue };
88
135
 
89
136
  const style = richTextProperty.options?.richTextStyle ?? "Default";
90
137
 
@@ -103,55 +150,161 @@ export function serializeAsRichTextBlock(input: Input): ProsemirrorNode {
103
150
  case "Title4":
104
151
  case "Title5":
105
152
  return serializeAsHeading(enrichedInput);
106
-
107
- // Todo: verify
108
- case "OL":
109
- case "UL":
110
- throw new Error(`Not allowed!`);
111
153
  }
112
154
  }
113
155
 
114
- //
115
- // Blocks
116
- //
117
-
118
- function serializeAsParagraph(input: InputWithValue): ProsemirrorNode {
156
+ function serializeAsParagraph(input: RichTextInputWithValue): ProsemirrorNode {
119
157
  return {
120
158
  type: "paragraph",
121
159
  attrs: serializeBlockNodeAttributes(input),
122
- content: serializeRichText(input.richTextPropertyValue),
160
+ content: serializeRichText(input.richTextPropertyValue.value),
123
161
  };
124
162
  }
125
163
 
126
- function serializeAsHeading(input: InputWithValue): ProsemirrorNode {
164
+ function serializeAsHeading(input: RichTextInputWithValue): ProsemirrorNode {
127
165
  return {
128
166
  type: "heading",
129
167
  attrs: {
130
168
  ...serializeBlockNodeAttributes(input),
131
- level: richTextHeadingLevel(input.richTextProperty),
169
+ level: richTextHeadingLevel(input.property),
132
170
  },
133
- content: serializeRichText(input.richTextPropertyValue),
171
+ content: serializeRichText(input.richTextPropertyValue.value),
134
172
  };
135
173
  }
136
174
 
137
- function serializeAsBlockquote(input: InputWithValue): ProsemirrorNode {
175
+ function serializeAsBlockquote(input: RichTextInputWithValue): ProsemirrorNode {
138
176
  return {
139
177
  type: "blockquote",
140
178
  attrs: serializeBlockNodeAttributes(input),
141
- content: serializeRichText(input.richTextPropertyValue),
179
+ content: serializeRichText(input.richTextPropertyValue.value),
142
180
  };
143
181
  }
144
182
 
145
- function serializeAsCallout(input: InputWithValue): ProsemirrorNode {
183
+ function serializeAsCallout(input: RichTextInputWithValue): ProsemirrorNode {
184
+ const calloutType = input.richTextPropertyValue.calloutType ?? "Info";
185
+
146
186
  return {
147
187
  type: "callout",
148
188
  attrs: {
149
189
  ...serializeBlockNodeAttributes(input),
190
+ type: serializeCalloutType(calloutType),
191
+ },
192
+ content: serializeRichText(input.richTextPropertyValue.value),
193
+ };
194
+ }
195
+
196
+ //
197
+ // Multi rich text blocks
198
+ //
199
+
200
+ function serializeAsMultiRichTextBlock(input: InputWithProperty): ProsemirrorNode {
201
+ const { block, definition, property: richTextProperty } = input;
202
+
203
+ const blockItem = BlockParsingUtils.singleBlockItem(block);
204
+ const textPropertyValue = BlockParsingUtils.multiRichTextPropertyValue(blockItem, richTextProperty.id);
205
+
206
+ const enrichedInput: MultiRichTextInputWithValue = { ...input, multiRichTextPropertyValue: textPropertyValue };
207
+
208
+ const style = richTextProperty.options?.multiRichTextStyle ?? "Default";
209
+
210
+ switch (style) {
211
+ case "Default":
212
+ return serializeAsMultiParagraph(enrichedInput);
213
+
214
+ case "OL":
215
+ return serializeAsOrderedList(enrichedInput);
216
+
217
+ case "UL":
218
+ return serializeAsUnorderedList(enrichedInput);
219
+ }
220
+ }
221
+
222
+ function serializeAsMultiParagraph(input: MultiRichTextInputWithValue): ProsemirrorNode {
223
+ return {
224
+ type: "bulletlist",
225
+ attrs: {
226
+ ...serializeBlockNodeAttributes(input),
227
+ },
228
+ content: input.multiRichTextPropertyValue.value.map(serializeAsListItem),
229
+ };
230
+ }
231
+
232
+ function serializeAsOrderedList(input: MultiRichTextInputWithValue): ProsemirrorNode {
233
+ return {
234
+ type: "orderedlist",
235
+ attrs: {
236
+ ...serializeBlockNodeAttributes(input),
237
+ start: "1",
238
+ },
239
+ content: input.multiRichTextPropertyValue.value.map(serializeAsListItem),
240
+ };
241
+ }
150
242
 
151
- // TODO Docs: type
152
- type: "neutral",
243
+ function serializeAsUnorderedList(input: MultiRichTextInputWithValue): ProsemirrorNode {
244
+ return {
245
+ type: "bulletlist",
246
+ attrs: {
247
+ ...serializeBlockNodeAttributes(input),
153
248
  },
154
- content: serializeRichText(input.richTextPropertyValue),
249
+ content: input.multiRichTextPropertyValue.value.map(serializeAsListItem),
250
+ };
251
+ }
252
+
253
+ function serializeAsListItem(text: PageBlockText): ProsemirrorNode {
254
+ return {
255
+ type: "listitem",
256
+ content: [
257
+ {
258
+ type: "paragraph",
259
+ content: serializeRichText(text),
260
+ },
261
+ ],
262
+ };
263
+ }
264
+
265
+ //
266
+ // Embeds
267
+ //
268
+
269
+ function serializeAsEmbed(input: InputWithProperty, embedType: string): ProsemirrorNode {
270
+ const { block, definition, property: embedProperty } = input;
271
+
272
+ const blockItem = BlockParsingUtils.singleBlockItem(block);
273
+ const embedValue = BlockParsingUtils.embedPropertyValue(blockItem, embedProperty.id);
274
+
275
+ return {
276
+ type: "embed",
277
+ attrs: {
278
+ ...serializeBlockNodeAttributes(input),
279
+ type: embedType,
280
+ url: embedValue.value,
281
+ height: embedValue.height,
282
+ caption: embedValue.caption,
283
+ },
284
+ };
285
+ }
286
+
287
+ function serializeEmbedType(blockDefinitionId: string): string | undefined {
288
+ switch (blockDefinitionId) {
289
+ case "io.supernova.block.embed":
290
+ return "generic";
291
+ case "io.supernova.block.embed-youtube":
292
+ return "youtube";
293
+ case "io.supernova.block.embed-figma":
294
+ return "figma";
295
+ }
296
+
297
+ return undefined;
298
+ }
299
+
300
+ //
301
+ // Divider
302
+ //
303
+
304
+ function serializeAsDivider(input: BaseInput): ProsemirrorNode {
305
+ return {
306
+ type: "horizontalrule",
307
+ attrs: serializeBlockNodeAttributes(input),
155
308
  };
156
309
  }
157
310
 
@@ -159,18 +312,18 @@ function serializeAsCallout(input: InputWithValue): ProsemirrorNode {
159
312
  // Attributes
160
313
  //
161
314
 
162
- function serializeBlockNodeAttributes(input: InputWithValue) {
315
+ function serializeBlockNodeAttributes(input: BaseInput) {
163
316
  const { block } = input;
164
317
 
165
318
  return {
166
319
  id: block.id,
167
320
  definitionId: block.data.packageId,
168
- ...(input.block.data.variantId && { variantId: input.block.data.variantId }),
321
+ ...(block.data.variantId && { variantId: block.data.variantId }),
169
322
  };
170
323
  }
171
324
 
172
325
  function richTextHeadingLevel(property: PageBlockDefinitionProperty): number | undefined {
173
- const style = property.options?.style;
326
+ const style = property.options?.richTextStyle;
174
327
  if (!style) return undefined;
175
328
 
176
329
  switch (style) {
@@ -184,9 +337,6 @@ function richTextHeadingLevel(property: PageBlockDefinitionProperty): number | u
184
337
  return 4;
185
338
  case "Title5":
186
339
  return 5;
187
-
188
- case "OL":
189
- case "UL":
190
340
  case "Callout":
191
341
  case "Default":
192
342
  case "Quote":
@@ -194,6 +344,19 @@ function richTextHeadingLevel(property: PageBlockDefinitionProperty): number | u
194
344
  }
195
345
  }
196
346
 
347
+ function serializeCalloutType(calloutType: PageBlockCalloutType) {
348
+ switch (calloutType) {
349
+ case "Error":
350
+ return "error";
351
+ case "Info":
352
+ return "neutral";
353
+ case "Success":
354
+ return "success";
355
+ case "Warning":
356
+ return "warning";
357
+ }
358
+ }
359
+
197
360
  //
198
361
  // Rich text
199
362
  //
@@ -255,118 +418,25 @@ function serializeTextSpanAttribute(spanAttribute: PageBlockTextSpanAttribute):
255
418
  }
256
419
 
257
420
  export function serializeAsCustomBlock(block: PageBlockEditorModel, definition: PageBlockDefinition): ProsemirrorNode {
258
- const items = block.data.items.map(i => serializeItem(i, block, definition));
421
+ const items = block.data.items.map(i => {
422
+ return {
423
+ id: i.id,
424
+ props: i.props,
425
+ linksTo: i.linksTo,
426
+ };
427
+ });
259
428
 
260
429
  return {
261
430
  type: "blockNode",
262
431
  attrs: {
263
432
  id: block.id,
264
433
  definitionId: block.data.packageId,
265
- variantId: "default",
266
- columns: 1,
434
+ variantId: block.data.variantId,
267
435
  items: JSON.stringify(items),
268
-
269
- // TODO Docs: variant, columns
270
436
  },
271
437
  };
272
438
  }
273
439
 
274
- function serializeItem(
275
- item: PageBlockItemV2,
276
- block: PageBlockEditorModel,
277
- definition: PageBlockDefinition
278
- ): ProsemirrorBlockItem {
279
- const result: ProsemirrorBlockItem = {
280
- properties: [],
281
- };
282
-
283
- for (const property of definition.item.properties) {
284
- const serializedValue = serializePropertyValue(item, property);
285
- serializedValue && result.properties.push(serializedValue);
286
- }
287
-
288
- return result;
289
- }
290
-
291
- function serializePropertyValue(
292
- item: PageBlockItemV2,
293
- property: PageBlockDefinitionProperty
294
- ): ProsemirrorBlockItemPropertyValue | undefined {
295
- const value = item.props[property.id] ?? undefined;
296
- if (!value) {
297
- return undefined;
298
- }
299
-
300
- // TODO Docs: actual property serialization
301
-
302
- const result: ProsemirrorBlockItemPropertyValue = {
303
- id: property.id,
304
- data: {},
305
- };
306
-
307
- if (typeof value === "string") {
308
- result.data.value = value;
309
- }
310
-
311
- return result;
312
- }
313
-
314
440
  function nonNullFilter<T>(item: T | null): item is T {
315
441
  return !!item;
316
442
  }
317
-
318
- // function serializePropertyValue(item: SDKBlockItem, property: SDKBlockDefinitionProperty) {
319
- // switch (property.type) {
320
- // case SDK.DocsBlockItemPropertyType.text: return value as string;
321
-
322
- // case SDK.DocsBlockItemPropertyType.richText: return {}
323
-
324
- // case SDK.DocsBlockItemPropertyType.boolean: return {}
325
- // case SDK.DocsBlockItemPropertyType.number: return {}
326
- // case SDK.DocsBlockItemPropertyType.singleSelect: return {}
327
- // case SDK.DocsBlockItemPropertyType.multiSelect: return {}
328
- // case SDK.DocsBlockItemPropertyType.image: return {}
329
- // case SDK.DocsBlockItemPropertyType.token: return {}
330
- // case SDK.DocsBlockItemPropertyType.tokenType: return {}
331
- // case SDK.DocsBlockItemPropertyType.tokenProperty: return {}
332
- // case SDK.DocsBlockItemPropertyType.component: return {}
333
- // case SDK.DocsBlockItemPropertyType.componentProperty: return {}
334
- // case SDK.DocsBlockItemPropertyType.asset: return {}
335
- // case SDK.DocsBlockItemPropertyType.assetProperty: return {}
336
- // case SDK.DocsBlockItemPropertyType.page: return {}
337
- // case SDK.DocsBlockItemPropertyType.pageProperty: return {}
338
- // case SDK.DocsBlockItemPropertyType.embedURL: return {}
339
- // case SDK.DocsBlockItemPropertyType.embedFrame: return {}
340
- // case SDK.DocsBlockItemPropertyType.markdown: return {}
341
- // case SDK.DocsBlockItemPropertyType.code: return {}
342
- // case SDK.DocsBlockItemPropertyType.codeSandbox: return {}
343
- // case SDK.DocsBlockItemPropertyType.table: return {}
344
- // case SDK.DocsBlockItemPropertyType.divider: return {}
345
- // case SDK.DocsBlockItemPropertyType.storybook: return {}
346
- // }
347
- // }
348
-
349
- // richText = "RichText",
350
- // text = "Text",
351
- // boolean = "Boolean",
352
- // number = "Number",
353
- // singleSelect = "SingleSelect",
354
- // multiSelect = "MultiSelect",
355
- // image = "Image",
356
- // token = "Token",
357
- // tokenType = "TokenType",
358
- // tokenProperty = "TokenProperty",
359
- // component = "Component",
360
- // componentProperty = "ComponentProperty",
361
- // asset = "Asset",
362
- // assetProperty = "AssetProperty",
363
- // page = "Page",
364
- // pageProperty = "PageProperty",
365
- // embedURL = "EmbedURL",
366
- // embedFrame = "EmbedFrame",
367
- // markdown = "Markdown",
368
- // code = "Code",
369
- // codeSandbox = "CodeSandbox",
370
- // table = "Table",
371
- // divider = "Divider",
372
- // storybook = "Storybook"
@@ -21,7 +21,7 @@ const blocks: PageBlockDefinition[] = [
21
21
  description: undefined,
22
22
  options: {
23
23
  placeholder: "Start writing with plain text",
24
- style: "Default",
24
+ richTextStyle: "Default",
25
25
  },
26
26
  variantOptions: undefined,
27
27
  },
@@ -67,7 +67,7 @@ const blocks: PageBlockDefinition[] = [
67
67
  id: "text",
68
68
  name: "Text",
69
69
  type: "RichText",
70
- options: { placeholder: "Title 1", style: "Title1" },
70
+ options: { placeholder: "Title 1", richTextStyle: "Title1" },
71
71
  },
72
72
  ],
73
73
  appearance: { isBordered: false, hasBackground: false },
@@ -112,7 +112,7 @@ const blocks: PageBlockDefinition[] = [
112
112
  name: "Text",
113
113
  type: "RichText",
114
114
  description: undefined,
115
- options: { placeholder: "Title 2", style: "Title2" },
115
+ options: { placeholder: "Title 2", richTextStyle: "Title2" },
116
116
  variantOptions: undefined,
117
117
  },
118
118
  ],
@@ -160,7 +160,7 @@ const blocks: PageBlockDefinition[] = [
160
160
  name: "Text",
161
161
  type: "RichText",
162
162
  description: undefined,
163
- options: { placeholder: "Title 3", style: "Title3" },
163
+ options: { placeholder: "Title 3", richTextStyle: "Title3" },
164
164
  variantOptions: undefined,
165
165
  },
166
166
  ],
@@ -208,7 +208,7 @@ const blocks: PageBlockDefinition[] = [
208
208
  name: "Text",
209
209
  type: "RichText",
210
210
  description: undefined,
211
- options: { placeholder: "Title 4", style: "Title4" },
211
+ options: { placeholder: "Title 4", richTextStyle: "Title4" },
212
212
  variantOptions: undefined,
213
213
  },
214
214
  ],
@@ -256,7 +256,7 @@ const blocks: PageBlockDefinition[] = [
256
256
  name: "Text",
257
257
  type: "RichText",
258
258
  description: undefined,
259
- options: { placeholder: "Title 5", style: "Title5" },
259
+ options: { placeholder: "Title 5", richTextStyle: "Title5" },
260
260
  variantOptions: undefined,
261
261
  },
262
262
  ],
@@ -302,9 +302,9 @@ const blocks: PageBlockDefinition[] = [
302
302
  {
303
303
  id: "text",
304
304
  name: "Text",
305
- type: "RichText",
305
+ type: "MultiRichText",
306
306
  description: undefined,
307
- options: { style: "OL" },
307
+ options: { multiRichTextStyle: "OL" },
308
308
  variantOptions: undefined,
309
309
  },
310
310
  ],
@@ -350,9 +350,9 @@ const blocks: PageBlockDefinition[] = [
350
350
  {
351
351
  id: "text",
352
352
  name: "Text",
353
- type: "RichText",
353
+ type: "MultiRichText",
354
354
  description: undefined,
355
- options: { style: "UL" },
355
+ options: { multiRichTextStyle: "UL" },
356
356
  variantOptions: undefined,
357
357
  },
358
358
  ],
@@ -448,7 +448,7 @@ const blocks: PageBlockDefinition[] = [
448
448
  name: "Text",
449
449
  type: "RichText",
450
450
  description: undefined,
451
- options: { placeholder: "Empty quote", style: "Quote" },
451
+ options: { placeholder: "Empty quote", richTextStyle: "Quote" },
452
452
  variantOptions: undefined,
453
453
  },
454
454
  ],
@@ -496,7 +496,7 @@ const blocks: PageBlockDefinition[] = [
496
496
  name: "Text",
497
497
  type: "RichText",
498
498
  description: undefined,
499
- options: { style: "Callout" },
499
+ options: { richTextStyle: "Callout" },
500
500
  variantOptions: undefined,
501
501
  },
502
502
  ],
@@ -591,17 +591,17 @@ const blocks: PageBlockDefinition[] = [
591
591
  {
592
592
  id: "block.links.property.title",
593
593
  name: "Title",
594
- type: "RichText",
594
+ type: "Text",
595
595
  description: undefined,
596
- options: { style: "Title5" },
596
+ options: { textStyle: "Title5" },
597
597
  variantOptions: undefined,
598
598
  },
599
599
  {
600
600
  id: "block.links.property.description",
601
601
  name: "Short description",
602
- type: "RichText",
602
+ type: "Text",
603
603
  description: undefined,
604
- options: { style: "Default", color: "NeutralFaded" },
604
+ options: { textStyle: "Default", color: "NeutralFaded" },
605
605
  variantOptions: undefined,
606
606
  },
607
607
  {
@@ -1,3 +1,5 @@
1
+ import { PageBlockItemUntypedPropertyValue } from "@supernova-studio/model";
2
+
1
3
  export type ProsemirrorNode = {
2
4
  type: string;
3
5
  text?: string;
@@ -12,12 +14,6 @@ export type ProsemirrorMark = {
12
14
  };
13
15
 
14
16
  export type ProsemirrorBlockItem = {
15
- properties: ProsemirrorBlockItemPropertyValue[];
16
- };
17
-
18
- export type ProsemirrorBlockItemPropertyValue = {
19
17
  id: string;
20
- data: {
21
- value?: string;
22
- };
18
+ props: Record<string, PageBlockItemUntypedPropertyValue>;
23
19
  };