Skip to content

Commit 2d54f8d

Browse files
authored
Fix FormulaInputField data record assignment in AB collection element (baserow#4335)
* fix: ensure arrays of inline content are wrapped in a `wrapper` node in `toTipTapVisitor` output. * refactor visitRoot * fix tests
1 parent c5832b4 commit 2d54f8d

File tree

2 files changed

+86
-61
lines changed

2 files changed

+86
-61
lines changed

tests/cases/tip_tap_visitor_cases.json

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,24 @@
5959
"type": "doc",
6060
"content": [
6161
{
62-
"type": "text",
63-
"text": "\u200B"
64-
},
65-
{
66-
"type": "get-formula-component",
67-
"attrs": { "path": "data_source.hello.there", "isSelected": false }
68-
},
69-
{
70-
"type": "text",
71-
"text": "\u200B"
62+
"type": "wrapper",
63+
"content": [
64+
{
65+
"type": "text",
66+
"text": "\u200B"
67+
},
68+
{
69+
"type": "get-formula-component",
70+
"attrs": {
71+
"path": "data_source.hello.there",
72+
"isSelected": false
73+
}
74+
},
75+
{
76+
"type": "text",
77+
"text": "\u200B"
78+
}
79+
]
7280
}
7381
]
7482
}

web-frontend/modules/core/formula/tiptap/toTipTapVisitor.js

Lines changed: 68 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,66 +11,83 @@ export class ToTipTapVisitor extends BaserowFormulaVisitor {
1111

1212
visitRoot(ctx) {
1313
const result = ctx.expr().accept(this)
14+
return this.mode === 'advanced'
15+
? this._wrapForAdvancedMode(result)
16+
: this._wrapForSimpleMode(result)
17+
}
1418

15-
// In advanced mode, ensure all content is wrapped in a single wrapper
16-
if (this.mode === 'advanced') {
17-
const content = _.isArray(result) ? result : [result]
18-
const flatContent = content.flatMap((item) => {
19-
// Filter out null or undefined items
20-
if (!item) return []
21-
22-
// If the item is an array (from functions without wrapper in advanced mode)
23-
if (Array.isArray(item)) {
24-
return item
25-
}
26-
27-
// If the item is a wrapper, extract its content
28-
if (item.type === 'wrapper' && item.content) {
29-
return item.content
30-
}
19+
/**
20+
* Wraps content for advanced mode - flattens all content into a single wrapper
21+
*/
22+
_wrapForAdvancedMode(result) {
23+
const content = _.isArray(result) ? result : [result]
24+
const flatContent = this._flattenContent(content)
25+
this._ensureStartsWithZWS(flatContent)
26+
27+
return {
28+
type: 'doc',
29+
content: [
30+
{
31+
type: 'wrapper',
32+
content: flatContent,
33+
},
34+
],
35+
}
36+
}
3137

32-
// Return the item if it has a type
33-
return item.type ? [item] : []
34-
})
38+
/**
39+
* Wraps content for simple mode - preserves wrapper structure or creates one
40+
*/
41+
_wrapForSimpleMode(result) {
42+
if (Array.isArray(result)) {
43+
return this._isArrayOfWrappers(result)
44+
? { type: 'doc', content: result }
45+
: { type: 'doc', content: [{ type: 'wrapper', content: result }] }
46+
}
3547

36-
// Ensure content starts with ZWS
37-
const firstNode = flatContent[0]
38-
if (
39-
!firstNode ||
40-
firstNode.type !== 'text' ||
41-
firstNode.text !== '\u200B'
42-
) {
43-
flatContent.unshift({ type: 'text', text: '\u200B' })
44-
}
48+
if (result?.type === 'wrapper') {
49+
return { type: 'doc', content: [result] }
50+
}
4551

46-
return {
47-
type: 'doc',
48-
content: [
49-
{
50-
type: 'wrapper',
51-
content: flatContent,
52-
},
53-
],
54-
}
52+
return {
53+
type: 'doc',
54+
content: [{ type: 'wrapper', content: [result] }],
5555
}
56+
}
5657

57-
// In simple mode, wrap inline content in a wrapper
58-
// The result can be a wrapper, an array of wrappers, or inline content
59-
if (Array.isArray(result)) {
60-
// Array of wrappers (e.g., from concat with newlines)
61-
return { type: 'doc', content: result }
62-
} else if (result?.type === 'wrapper') {
63-
// Already a wrapper
64-
return { type: 'doc', content: [result] }
65-
} else {
66-
// Inline content (text, nodes, etc.) - wrap it
67-
return {
68-
type: 'doc',
69-
content: [{ type: 'wrapper', content: [result] }],
70-
}
58+
/**
59+
* Flattens nested content, extracting items from wrappers and arrays
60+
*/
61+
_flattenContent(content) {
62+
return content.flatMap((item) => {
63+
if (!item) return []
64+
if (Array.isArray(item)) return item
65+
if (item.type === 'wrapper' && item.content) return item.content
66+
return item.type ? [item] : []
67+
})
68+
}
69+
70+
/**
71+
* Ensures the content array starts with a Zero-Width Space text node
72+
*/
73+
_ensureStartsWithZWS(content) {
74+
const firstNode = content[0]
75+
if (
76+
!firstNode ||
77+
firstNode.type !== 'text' ||
78+
firstNode.text !== '\u200B'
79+
) {
80+
content.unshift({ type: 'text', text: '\u200B' })
7181
}
7282
}
7383

84+
/**
85+
* Checks if an array contains only wrapper nodes
86+
*/
87+
_isArrayOfWrappers(array) {
88+
return array.every((item) => item?.type === 'wrapper')
89+
}
90+
7491
visitStringLiteral(ctx) {
7592
switch (ctx.getText()) {
7693
case "'\n'":

0 commit comments

Comments
 (0)