@supernova-studio/client 0.3.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supernova-studio/client",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Supernova Data Models",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,8 @@ import {
8
8
  PageBlockItemRichTextValue,
9
9
  PageBlockCalloutType,
10
10
  PageBlockItemMultiRichTextValue,
11
+ PageBlockTableCellAlignment,
12
+ PageBlockItemTableNode,
11
13
  } from "@supernova-studio/model";
12
14
  import { PageBlockEditorModel } from "./model/block";
13
15
  import { ProsemirrorNode, ProsemirrorMark } from "./prosemirror/types";
@@ -96,6 +98,15 @@ export function blockToProsemirrorNode(
96
98
  });
97
99
  }
98
100
 
101
+ const tableProperty = BlockDefinitionUtils.firstTableProperty(definition);
102
+ if (tableProperty) {
103
+ return serializeAsTable({
104
+ block: block,
105
+ definition: definition,
106
+ property: tableProperty,
107
+ });
108
+ }
109
+
99
110
  // Embed
100
111
  const embedProperty = BlockDefinitionUtils.firstEmbedProperty(definition);
101
112
  const embedType = serializeEmbedType(block.data.packageId);
@@ -157,7 +168,7 @@ function serializeAsParagraph(input: RichTextInputWithValue): ProsemirrorNode {
157
168
  return {
158
169
  type: "paragraph",
159
170
  attrs: serializeBlockNodeAttributes(input),
160
- content: serializeRichText(input.richTextPropertyValue.value),
171
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
161
172
  };
162
173
  }
163
174
 
@@ -168,7 +179,7 @@ function serializeAsHeading(input: RichTextInputWithValue): ProsemirrorNode {
168
179
  ...serializeBlockNodeAttributes(input),
169
180
  level: richTextHeadingLevel(input.property),
170
181
  },
171
- content: serializeRichText(input.richTextPropertyValue.value),
182
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
172
183
  };
173
184
  }
174
185
 
@@ -176,7 +187,7 @@ function serializeAsBlockquote(input: RichTextInputWithValue): ProsemirrorNode {
176
187
  return {
177
188
  type: "blockquote",
178
189
  attrs: serializeBlockNodeAttributes(input),
179
- content: serializeRichText(input.richTextPropertyValue.value),
190
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
180
191
  };
181
192
  }
182
193
 
@@ -189,7 +200,7 @@ function serializeAsCallout(input: RichTextInputWithValue): ProsemirrorNode {
189
200
  ...serializeBlockNodeAttributes(input),
190
201
  type: serializeCalloutType(calloutType),
191
202
  },
192
- content: serializeRichText(input.richTextPropertyValue.value),
203
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
193
204
  };
194
205
  }
195
206
 
@@ -262,6 +273,91 @@ function serializeAsListItem(text: PageBlockText): ProsemirrorNode {
262
273
  };
263
274
  }
264
275
 
276
+ //
277
+ // Tables
278
+ //
279
+
280
+ function serializeAsTable(input: InputWithProperty): ProsemirrorNode {
281
+ const { block, definition, property: tableProperty } = input;
282
+
283
+ const blockItem = BlockParsingUtils.singleBlockItem(block);
284
+ const table = BlockParsingUtils.tablePropertyValue(blockItem, tableProperty.id);
285
+
286
+ return {
287
+ type: "tableContainer",
288
+ attrs: {
289
+ ...serializeBlockNodeAttributes(input),
290
+ hasBorder: table.showBorder ?? true,
291
+ },
292
+
293
+ content: [
294
+ {
295
+ type: "table",
296
+ content: table.value.map<ProsemirrorNode>((row, rowIndex) => {
297
+ const isHeaderRow = rowIndex === 0 && table.highlightHeaderRow === true;
298
+
299
+ return {
300
+ type: "tableRow",
301
+ content: row.cells.map<ProsemirrorNode>((cell, cellIndex) => {
302
+ const isHeaderColumn = cellIndex === 0 && table.highlightHeaderColumn === true;
303
+
304
+ return {
305
+ type: isHeaderRow || isHeaderColumn ? "tableHeader" : "tableCell",
306
+ attrs: {
307
+ id: cell.id,
308
+ textAlign: serializeTableCellAlignment(cell.alignment),
309
+ colspan: 1,
310
+ rowspan: 1,
311
+ ...(cell.columnWidth && { colwidth: [cell.columnWidth] }),
312
+ },
313
+ content: cell.nodes.map<ProsemirrorNode>(serializeTableNode),
314
+ };
315
+ }),
316
+ };
317
+ }),
318
+ },
319
+ ],
320
+ };
321
+ }
322
+
323
+ function serializeTableCellAlignment(alignment: PageBlockTableCellAlignment) {
324
+ switch (alignment) {
325
+ case "Left":
326
+ return "left";
327
+ case "Center":
328
+ return "center";
329
+ case "Right":
330
+ return "right";
331
+
332
+ default:
333
+ return "left";
334
+ }
335
+ }
336
+
337
+ function serializeTableNode(node: PageBlockItemTableNode): ProsemirrorNode {
338
+ switch (node.type) {
339
+ case "RichText":
340
+ return {
341
+ type: "paragraph",
342
+ ...serializeRichTextNodePart(node.value),
343
+ };
344
+
345
+ case "Image":
346
+ return {
347
+ type: "image",
348
+ attrs: {
349
+ src: node.value?.url,
350
+ },
351
+ };
352
+
353
+ case "MultiRichText":
354
+ // TODO
355
+ return {
356
+ type: "paragraph",
357
+ };
358
+ }
359
+ }
360
+
265
361
  //
266
362
  // Embeds
267
363
  //
@@ -361,6 +457,12 @@ function serializeCalloutType(calloutType: PageBlockCalloutType) {
361
457
  // Rich text
362
458
  //
363
459
 
460
+ function serializeRichTextNodePart(richText: PageBlockText): Pick<ProsemirrorNode, "content"> {
461
+ const spans = serializeRichText(richText);
462
+ if (spans.length) return { content: spans };
463
+ return {};
464
+ }
465
+
364
466
  function serializeRichText(richText: PageBlockText) {
365
467
  return richText.spans.map(serializeTextSpan).flat();
366
468
  }
@@ -373,7 +475,8 @@ function serializeTextSpan(span: PageBlockTextSpan): ProsemirrorNode[] {
373
475
  {
374
476
  type: "text",
375
477
  text: textParts[0],
376
- marks: marks,
478
+
479
+ ...(marks.length && { marks: marks }),
377
480
  },
378
481
  ];
379
482
 
@@ -385,7 +488,8 @@ function serializeTextSpan(span: PageBlockTextSpan): ProsemirrorNode[] {
385
488
  {
386
489
  type: "text",
387
490
  text: textParts[i],
388
- marks: marks,
491
+
492
+ ...(marks.length && { marks: marks }),
389
493
  }
390
494
  );
391
495
  }
@@ -34,6 +34,9 @@ import {
34
34
  PageBlockItemStorybookValue,
35
35
  PageBlockItemColorValue,
36
36
  PageBlockItemUntypedValue,
37
+ PageBlockItemTableNode,
38
+ PageBlockItemTableCell,
39
+ PageBlockTableCellAlignment,
37
40
  } from "@supernova-studio/model";
38
41
  import { PageBlockEditorModel } from "./model/block";
39
42
  import { DocumentationPageEditorModel } from "./model/page";
@@ -90,6 +93,11 @@ export function prosemirrorNodeToBlock(
90
93
  return parseAsEmbed(prosemirrorNode, definition, embedProperty);
91
94
  }
92
95
 
96
+ const tableProperty = BlockDefinitionUtils.firstTableProperty(definition);
97
+ if (tableProperty) {
98
+ return parseAsTable(prosemirrorNode, definition, tableProperty);
99
+ }
100
+
93
101
  if (definition.id === "io.supernova.block.divider") {
94
102
  return parseAsDivider(prosemirrorNode, definition);
95
103
  }
@@ -248,6 +256,180 @@ function parseRichTextAttribute(mark: ProsemirrorMark): PageBlockTextSpanAttribu
248
256
  // Embed
249
257
  //
250
258
 
259
+ function parseAsTable(
260
+ prosemirrorNode: ProsemirrorNode,
261
+ definition: PageBlockDefinition,
262
+ property: PageBlockDefinitionProperty
263
+ ): PageBlockEditorModel {
264
+ const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
265
+ const variantId = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "variantId");
266
+
267
+ const hasBorder = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "hasBorder") !== false;
268
+
269
+ const tableChild = prosemirrorNode.content?.find(c => c.type === "table");
270
+ if (!tableChild) {
271
+ return emptyTable(id, variantId, 0);
272
+ }
273
+
274
+ // Nodes can are considered rows if they have correct node type + their content is non-empty
275
+ const rows = tableChild.content?.filter(c => c.type === "tableRow" && !!c.content?.length) ?? [];
276
+ if (!rows.length) {
277
+ return emptyTable(id, variantId, 0);
278
+ }
279
+
280
+ const rowHeaderCells = rows[0].content?.filter(c => c.type === "tableHeader").length ?? 0;
281
+ const columnHeaderCells = rows.filter(r => r.content?.[0]?.type === "tableHeader").length;
282
+
283
+ const hasHeaderRow = rows[0].content?.length === rowHeaderCells;
284
+ const hasHeaderColumn = rows.length === columnHeaderCells;
285
+
286
+ const tableValue: PageBlockItemTableValue = {
287
+ showBorder: hasBorder,
288
+ highlightHeaderRow: hasHeaderRow,
289
+ highlightHeaderColumn: hasHeaderColumn,
290
+ value: [],
291
+ };
292
+
293
+ tableValue.value = rows.map(row => {
294
+ return {
295
+ cells: (row.content ?? []).map(parseAsTableCell),
296
+ };
297
+ });
298
+
299
+ return {
300
+ id: id,
301
+ data: {
302
+ packageId: "io.supernova.block.table",
303
+ indentLevel: 0,
304
+
305
+ ...(variantId && { variantId: variantId }),
306
+
307
+ items: [
308
+ {
309
+ id: id,
310
+ props: {
311
+ table: tableValue,
312
+ },
313
+ },
314
+ ],
315
+ },
316
+ };
317
+ }
318
+
319
+ function parseAsTableCell(prosemirrorNode: ProsemirrorNode): PageBlockItemTableCell {
320
+ const id = parseProsemirrorBlockAttribute(prosemirrorNode, "id");
321
+ const textAlign = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "textAlign");
322
+
323
+ let columnWidth: number | undefined;
324
+ const columnWidthArray = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "colwidth");
325
+ if (Array.isArray(columnWidthArray) && typeof columnWidthArray[0] === "number") {
326
+ columnWidth = columnWidthArray[0];
327
+ }
328
+
329
+ const tableNodes = (prosemirrorNode.content ?? []).map(parseAsTableNode).filter(nonNullFilter);
330
+
331
+ return {
332
+ id: id,
333
+ alignment: parseTableCellAlignment(textAlign),
334
+ ...(columnWidth && { columnWidth }),
335
+ nodes: tableNodes.length ? tableNodes : emptyTableCellContent(),
336
+ };
337
+ }
338
+
339
+ function parseTableCellAlignment(alignment?: string): PageBlockTableCellAlignment {
340
+ if (!alignment) return "Left";
341
+
342
+ switch (alignment) {
343
+ case "left":
344
+ return "Left";
345
+ case "center":
346
+ return "Center";
347
+ case "right":
348
+ return "Right";
349
+
350
+ default:
351
+ return "Left";
352
+ }
353
+ }
354
+
355
+ function parseAsTableNode(prosemirrorNode: ProsemirrorNode): PageBlockItemTableNode | null {
356
+ switch (prosemirrorNode.type) {
357
+ case "paragraph":
358
+ return {
359
+ type: "RichText",
360
+ value: parseRichText(prosemirrorNode.content ?? []),
361
+ };
362
+
363
+ case "image":
364
+ const url = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, "src");
365
+ if (!url || typeof url !== "string") return null;
366
+
367
+ return {
368
+ type: "Image",
369
+ value: {
370
+ type: "Upload",
371
+ url: url,
372
+ },
373
+ };
374
+
375
+ default:
376
+ return null;
377
+ }
378
+ }
379
+
380
+ function emptyTable(id: string, variantId?: string, indentLevel?: number): PageBlockEditorModel {
381
+ const tableValue: PageBlockItemTableValue = {
382
+ showBorder: true,
383
+ highlightHeaderColumn: false,
384
+ highlightHeaderRow: false,
385
+ value: [],
386
+ };
387
+
388
+ for (let i = 0; i < 2; i++) {
389
+ tableValue.value.push({
390
+ cells: [],
391
+ });
392
+
393
+ for (let j = 0; j < 3; j++) {
394
+ tableValue.value[i].cells.push({
395
+ id: "",
396
+ alignment: "Left",
397
+ nodes: emptyTableCellContent(),
398
+ });
399
+ }
400
+ }
401
+
402
+ return {
403
+ id: id,
404
+ data: {
405
+ packageId: "io.supernova.block.table",
406
+ indentLevel: indentLevel ?? 0,
407
+ variantId: variantId,
408
+ items: [
409
+ {
410
+ id: id,
411
+ props: {
412
+ table: tableValue,
413
+ },
414
+ },
415
+ ],
416
+ },
417
+ };
418
+ }
419
+
420
+ function emptyTableCellContent(): PageBlockItemTableNode[] {
421
+ return [
422
+ {
423
+ type: "RichText",
424
+ value: { spans: [] },
425
+ },
426
+ ];
427
+ }
428
+
429
+ //
430
+ // Embed
431
+ //
432
+
251
433
  function parseAsEmbed(
252
434
  prosemirrorNode: ProsemirrorNode,
253
435
  definition: PageBlockDefinition,
@@ -357,6 +539,8 @@ function parseItem(rawItem: unknown, definition: PageBlockDefinition): PageBlock
357
539
 
358
540
  if (validationResult.success) {
359
541
  sanitizedProps[property.id] = validationResult.data;
542
+ } else {
543
+ console.log(validationResult.error.flatten());
360
544
  }
361
545
  }
362
546
 
@@ -427,7 +611,7 @@ function valueSchemaForPropertyType(type: PageBlockDefinitionPropertyType) {
427
611
  function parseProsemirrorBlockAttribute(prosemirrorNode: ProsemirrorNode, attributeName: string) {
428
612
  const attributeValue = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, attributeName); //prosemirrorNode.attrs?.[attributeName];
429
613
  if (!attributeValue) {
430
- throw new Error(`Attribute ${attributeName} was not defined`);
614
+ throw new Error(`Attribute ${attributeName} was not defined on node ${prosemirrorNode.type}`);
431
615
  }
432
616
 
433
617
  return attributeValue;
@@ -5,6 +5,7 @@ import {
5
5
  PageBlockItemEmbedValue,
6
6
  PageBlockItemMultiRichTextValue,
7
7
  PageBlockItemRichTextValue,
8
+ PageBlockItemTableValue,
8
9
  PageBlockItemUntypedValue,
9
10
  PageBlockItemV2,
10
11
  PageBlockText,
@@ -32,6 +33,13 @@ export const BlockParsingUtils = {
32
33
  return richText;
33
34
  },
34
35
 
36
+ tablePropertyValue(item: PageBlockItemV2, propertyKey: string): PageBlockItemTableValue {
37
+ const objectValue = this.objectPropertyValue(item, propertyKey);
38
+ const table = PageBlockItemTableValue.parse(objectValue);
39
+
40
+ return table;
41
+ },
42
+
35
43
  embedPropertyValue(item: PageBlockItemV2, propertyKey: string): PageBlockItemEmbedValue {
36
44
  const objectValue = this.objectPropertyValue(item, propertyKey);
37
45
  const embed = PageBlockItemEmbedValue.parse(objectValue);
@@ -71,7 +79,7 @@ export const BlockDefinitionUtils = {
71
79
  },
72
80
 
73
81
  firstTableProperty(definition: PageBlockDefinition) {
74
- const property = definition.item.properties.find(p => p.type === "RichText");
82
+ const property = definition.item.properties.find(p => p.type === "Table");
75
83
  if (property) return property;
76
84
  return undefined;
77
85
  },