@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/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +301 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +293 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/docs-editor/blocks-to-prosemirror.ts +133 -6
- package/src/docs-editor/prosemirror-to-blocks.ts +183 -1
- package/src/docs-editor/utils.ts +9 -1
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/src/docs-editor/utils.ts
CHANGED
|
@@ -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 === "
|
|
82
|
+
const property = definition.item.properties.find(p => p.type === "Table");
|
|
75
83
|
if (property) return property;
|
|
76
84
|
return undefined;
|
|
77
85
|
},
|