Skip to content

Commit 4cc8614

Browse files
committed
Add table generation support
1 parent b31a9f9 commit 4cc8614

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Get the field attributes based on the field type and value
3+
*
4+
* @param {Object} field The field node
5+
* @param {Object} value The value we want to annotate the field with
6+
* @returns
7+
*/
8+
export const getFieldAttrs = (field, value) => {
9+
const { type } = field.attrs;
10+
const annotatorHandlers = {
11+
html: annotateHtml,
12+
text: annotateText,
13+
checkbox: annotateCheckbox,
14+
image: annotateImage,
15+
}
16+
17+
const handler = annotatorHandlers[type];
18+
if (!handler) return {};
19+
20+
// Run the handler to get the annotated field attributes
21+
return handler(value);
22+
};
23+
24+
const annotateHtml = (value) => ({ rawHtml: value });
25+
const annotateText = (value) => ({ displayLabel: value });
26+
const annotateImage = (value) => ({ imageSrc: value });
27+
const annotateCheckbox = (value) => ({ displayLabel: value });

packages/super-editor/src/extensions/table/table.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Node, Attribute } from '@core/index.js';
22
import { callOrGet } from '@core/utilities/callOrGet.js';
33
import { getExtensionConfigField } from '@core/helpers/getExtensionConfigField.js';
44
import { /* TableView */ createTableView } from './TableView.js';
5+
import { findParentNodeClosestToPos } from '@helpers/index.js';
6+
import { Fragment } from "prosemirror-model";
57
import { createTable } from './tableHelpers/createTable.js';
68
import { createColGroup } from './tableHelpers/createColGroup.js';
79
import { deleteTableWhenSelected } from './tableHelpers/deleteTableWhenSelected.js';
@@ -10,6 +12,7 @@ import { createTableBorders } from './tableHelpers/createTableBorders.js';
1012
import { createCellBorders } from '../table-cell/helpers/createCellBorders.js';
1113
import { findParentNode } from '@helpers/findParentNode.js';
1214
import { TextSelection } from 'prosemirror-state';
15+
import { getFieldAttrs } from '@helpers/annotator.js';
1316
import {
1417
addColumnBefore,
1518
addColumnAfter,
@@ -295,6 +298,76 @@ export const Table = Node.create({
295298

296299
return true;
297300
},
301+
302+
generateTable: (annotation, matchingAnnotation, fieldData) => {
303+
return ({ tr, editor }) => {
304+
const seenTableRowAnnotationIds = [];
305+
const { state: { doc, schema } } = editor;
306+
const { tableRow: RowType, tableCell: CellType, fieldAnnotation: FieldType, paragraph: ParaType } = schema.nodes;
307+
308+
// Locate the parent row node containing the annotation
309+
const position = doc.resolve(annotation.pos);
310+
const rowInfo = findParentNodeClosestToPos(position, (node) => node.type === RowType);
311+
if (!rowInfo) return;
312+
313+
// Get all annotations in the row
314+
rowInfo.node.descendants((node, pos) => {
315+
if (node.type.name === 'fieldAnnotation') {
316+
seenTableRowAnnotationIds.push(node.attrs.fieldId);
317+
}
318+
});
319+
320+
if (!seenTableRowAnnotationIds.includes(annotation.node.attrs.fieldId)) return [];
321+
322+
// Figure out the position where we will start inserting new rows
323+
const { pos: rowStartPos, node: rowNode } = rowInfo;
324+
let insertPos = rowStartPos + rowNode.nodeSize;
325+
326+
// Helper: rebuild a single cell for a given row index
327+
const rebuildCell = (cellNode, rowIndex) => {
328+
const updatedBlocks = cellNode.content.content.map((blockNode) => {
329+
if (blockNode.type !== ParaType) return blockNode;
330+
331+
// Rebuild paragraphs by mapping inline nodes
332+
const updatedInlines = blockNode.content.content.map((inlineNode) => {
333+
if (inlineNode.type !== FieldType) return inlineNode;
334+
335+
// Find the matching field data and compute new attributes
336+
const fieldRecord = fieldData.find((f) => f.input_id === inlineNode.attrs.fieldId);
337+
const value = fieldRecord?.input_value[rowIndex];
338+
339+
// Different field types require different annotation handling
340+
// We use the helper here to get the correct attributes
341+
// Since generated tables contain annotated fields
342+
const extraAttrs = getFieldAttrs(inlineNode, value);
343+
return FieldType.create(
344+
{ ...inlineNode.attrs, ...extraAttrs },
345+
inlineNode.content,
346+
inlineNode.marks
347+
);
348+
});
349+
350+
return ParaType.create(blockNode.attrs, Fragment.from(updatedInlines), blockNode.marks);
351+
});
352+
353+
return CellType.create(cellNode.attrs, Fragment.from(updatedBlocks), cellNode.marks);
354+
};
355+
356+
// Iterate over each row value and build+insert a new row
357+
matchingAnnotation.input_value.forEach((_, rowIndex) => {
358+
// Build all cells for the new row
359+
const newCells = rowNode.content.content.map((cellNode) => rebuildCell(cellNode, rowIndex));
360+
const newRow = RowType.create(rowNode.attrs, Fragment.from(newCells), rowNode.marks);
361+
362+
tr.insert(insertPos, Fragment.from(newRow));
363+
insertPos += newRow.nodeSize;
364+
});
365+
366+
// Remove the original (placeholder) row
367+
tr.delete(rowStartPos, rowStartPos + rowNode.nodeSize);
368+
return seenTableRowAnnotationIds;
369+
};
370+
},
298371
};
299372
},
300373

0 commit comments

Comments
 (0)