@supernova-studio/client 0.3.1 → 0.4.2

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.1",
3
+ "version": "0.4.2",
4
4
  "description": "Supernova Data Models",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,11 @@ import {
8
8
  PageBlockItemRichTextValue,
9
9
  PageBlockCalloutType,
10
10
  PageBlockItemMultiRichTextValue,
11
+ PageBlockTableCellAlignment,
12
+ PageBlockItemTableNode,
13
+ PageBlockItemTableCell,
14
+ PageBlockItemTableValue,
15
+ PageBlockItemTableRow,
11
16
  } from "@supernova-studio/model";
12
17
  import { PageBlockEditorModel } from "./model/block";
13
18
  import { ProsemirrorNode, ProsemirrorMark } from "./prosemirror/types";
@@ -96,6 +101,15 @@ export function blockToProsemirrorNode(
96
101
  });
97
102
  }
98
103
 
104
+ const tableProperty = BlockDefinitionUtils.firstTableProperty(definition);
105
+ if (tableProperty) {
106
+ return serializeAsTable({
107
+ block: block,
108
+ definition: definition,
109
+ property: tableProperty,
110
+ });
111
+ }
112
+
99
113
  // Embed
100
114
  const embedProperty = BlockDefinitionUtils.firstEmbedProperty(definition);
101
115
  const embedType = serializeEmbedType(block.data.packageId);
@@ -157,7 +171,7 @@ function serializeAsParagraph(input: RichTextInputWithValue): ProsemirrorNode {
157
171
  return {
158
172
  type: "paragraph",
159
173
  attrs: serializeBlockNodeAttributes(input),
160
- content: serializeRichText(input.richTextPropertyValue.value),
174
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
161
175
  };
162
176
  }
163
177
 
@@ -168,7 +182,7 @@ function serializeAsHeading(input: RichTextInputWithValue): ProsemirrorNode {
168
182
  ...serializeBlockNodeAttributes(input),
169
183
  level: richTextHeadingLevel(input.property),
170
184
  },
171
- content: serializeRichText(input.richTextPropertyValue.value),
185
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
172
186
  };
173
187
  }
174
188
 
@@ -176,7 +190,7 @@ function serializeAsBlockquote(input: RichTextInputWithValue): ProsemirrorNode {
176
190
  return {
177
191
  type: "blockquote",
178
192
  attrs: serializeBlockNodeAttributes(input),
179
- content: serializeRichText(input.richTextPropertyValue.value),
193
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
180
194
  };
181
195
  }
182
196
 
@@ -189,7 +203,7 @@ function serializeAsCallout(input: RichTextInputWithValue): ProsemirrorNode {
189
203
  ...serializeBlockNodeAttributes(input),
190
204
  type: serializeCalloutType(calloutType),
191
205
  },
192
- content: serializeRichText(input.richTextPropertyValue.value),
206
+ ...serializeRichTextNodePart(input.richTextPropertyValue.value),
193
207
  };
194
208
  }
195
209
 
@@ -262,6 +276,111 @@ function serializeAsListItem(text: PageBlockText): ProsemirrorNode {
262
276
  };
263
277
  }
264
278
 
279
+ //
280
+ // Tables
281
+ //
282
+
283
+ function serializeAsTable(input: InputWithProperty): ProsemirrorNode {
284
+ const { block, definition, property: tableProperty } = input;
285
+
286
+ const blockItem = BlockParsingUtils.singleBlockItem(block);
287
+ const table = BlockParsingUtils.tablePropertyValue(blockItem, tableProperty.id);
288
+
289
+ return {
290
+ type: "tableContainer",
291
+ attrs: {
292
+ ...serializeBlockNodeAttributes(input),
293
+ hasBorder: table.showBorder ?? true,
294
+ },
295
+
296
+ content: [
297
+ {
298
+ type: "table",
299
+ content: serializeTableRows(table),
300
+ },
301
+ ],
302
+ };
303
+ }
304
+
305
+ function serializeTableRows(table: PageBlockItemTableValue): ProsemirrorNode[] {
306
+ // If no rows are present, we have to generate a least one empty row
307
+ const rows: PageBlockItemTableRow[] = table.value.length ? table.value : [{ cells: [] }];
308
+
309
+ return rows.map<ProsemirrorNode>((row, rowIndex) => {
310
+ const isHeaderRow = rowIndex === 0 && table.highlightHeaderRow === true;
311
+
312
+ // If no cells are present, we have to generate at least one cell
313
+ const cells: PageBlockItemTableCell[] = row.cells.length ? row.cells : [{ id: "", alignment: "Left", nodes: [] }];
314
+
315
+ return {
316
+ type: "tableRow",
317
+ content: cells.map<ProsemirrorNode>((cell, cellIndex) => {
318
+ const isHeaderColumn = cellIndex === 0 && table.highlightHeaderColumn === true;
319
+
320
+ return {
321
+ type: isHeaderRow || isHeaderColumn ? "tableHeader" : "tableCell",
322
+ attrs: {
323
+ ...(cell.id && { id: cell.id }),
324
+ textAlign: serializeTableCellAlignment(cell.alignment),
325
+ colspan: 1,
326
+ rowspan: 1,
327
+ ...(cell.columnWidth && { colwidth: [cell.columnWidth] }),
328
+ },
329
+ content: serializeTableCellNodes(cell),
330
+ };
331
+ }),
332
+ };
333
+ });
334
+ }
335
+
336
+ function serializeTableCellAlignment(alignment: PageBlockTableCellAlignment) {
337
+ switch (alignment) {
338
+ case "Left":
339
+ return "left";
340
+ case "Center":
341
+ return "center";
342
+ case "Right":
343
+ return "right";
344
+
345
+ default:
346
+ return "left";
347
+ }
348
+ }
349
+
350
+ function serializeTableCellNodes(cell: PageBlockItemTableCell): ProsemirrorNode[] {
351
+ const result = cell.nodes.map(serializeTableNode);
352
+
353
+ if (result.length) {
354
+ return result;
355
+ } else {
356
+ return [{ type: "paragraph" }];
357
+ }
358
+ }
359
+
360
+ function serializeTableNode(node: PageBlockItemTableNode): ProsemirrorNode {
361
+ switch (node.type) {
362
+ case "RichText":
363
+ return {
364
+ type: "paragraph",
365
+ ...serializeRichTextNodePart(node.value),
366
+ };
367
+
368
+ case "Image":
369
+ return {
370
+ type: "image",
371
+ attrs: {
372
+ src: node.value?.url,
373
+ },
374
+ };
375
+
376
+ case "MultiRichText":
377
+ // TODO
378
+ return {
379
+ type: "paragraph",
380
+ };
381
+ }
382
+ }
383
+
265
384
  //
266
385
  // Embeds
267
386
  //
@@ -361,6 +480,12 @@ function serializeCalloutType(calloutType: PageBlockCalloutType) {
361
480
  // Rich text
362
481
  //
363
482
 
483
+ function serializeRichTextNodePart(richText: PageBlockText): Pick<ProsemirrorNode, "content"> {
484
+ const spans = serializeRichText(richText);
485
+ if (spans.length) return { content: spans };
486
+ return {};
487
+ }
488
+
364
489
  function serializeRichText(richText: PageBlockText) {
365
490
  return richText.spans.map(serializeTextSpan).flat();
366
491
  }
@@ -373,7 +498,8 @@ function serializeTextSpan(span: PageBlockTextSpan): ProsemirrorNode[] {
373
498
  {
374
499
  type: "text",
375
500
  text: textParts[0],
376
- marks: marks,
501
+
502
+ ...(marks.length && { marks: marks }),
377
503
  },
378
504
  ];
379
505
 
@@ -385,7 +511,8 @@ function serializeTextSpan(span: PageBlockTextSpan): ProsemirrorNode[] {
385
511
  {
386
512
  type: "text",
387
513
  text: textParts[i],
388
- marks: marks,
514
+
515
+ ...(marks.length && { marks: marks }),
389
516
  }
390
517
  );
391
518
  }
@@ -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,
@@ -429,7 +611,7 @@ function valueSchemaForPropertyType(type: PageBlockDefinitionPropertyType) {
429
611
  function parseProsemirrorBlockAttribute(prosemirrorNode: ProsemirrorNode, attributeName: string) {
430
612
  const attributeValue = parseProsemirrorOptionalBlockAttribute(prosemirrorNode, attributeName); //prosemirrorNode.attrs?.[attributeName];
431
613
  if (!attributeValue) {
432
- throw new Error(`Attribute ${attributeName} was not defined`);
614
+ throw new Error(`Attribute ${attributeName} was not defined on node ${prosemirrorNode.type}`);
433
615
  }
434
616
 
435
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
  },