@portabletext/block-tools 3.4.1 → 3.5.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/lib/_chunks-cjs/helpers.cjs +479 -0
- package/lib/_chunks-cjs/helpers.cjs.map +1 -0
- package/lib/_chunks-dts/types.d.cts +85 -0
- package/lib/_chunks-dts/types.d.ts +85 -0
- package/lib/_chunks-es/helpers.js +478 -0
- package/lib/_chunks-es/helpers.js.map +1 -0
- package/lib/index.cjs +84 -534
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +1 -83
- package/lib/index.d.ts +1 -83
- package/lib/index.js +3 -453
- package/lib/index.js.map +1 -1
- package/lib/rules/index.cjs +72 -0
- package/lib/rules/index.cjs.map +1 -0
- package/lib/rules/index.d.cts +72 -0
- package/lib/rules/index.d.ts +72 -0
- package/lib/rules/index.js +73 -0
- package/lib/rules/index.js.map +1 -0
- package/package.json +8 -2
- package/src/HtmlDeserializer/flatten-nested-blocks.test.ts +5 -8
- package/src/HtmlDeserializer/flatten-nested-blocks.ts +0 -1
- package/src/HtmlDeserializer/index.ts +1 -1
- package/src/rules/_exports/index.ts +1 -0
- package/src/rules/flatten-tables.test.ts +405 -0
- package/src/rules/flatten-tables.ts +225 -0
- package/src/rules/index.ts +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isTextBlock,
|
|
3
|
+
type PortableTextObject,
|
|
4
|
+
type PortableTextSpan,
|
|
5
|
+
type Schema,
|
|
6
|
+
} from '@portabletext/schema'
|
|
7
|
+
import {flattenNestedBlocks} from '../HtmlDeserializer/flatten-nested-blocks'
|
|
8
|
+
import {isElement, tagName} from '../HtmlDeserializer/helpers'
|
|
9
|
+
import type {
|
|
10
|
+
ArbitraryTypedObject,
|
|
11
|
+
DeserializerRule,
|
|
12
|
+
TypedObject,
|
|
13
|
+
} from '../types'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* An opinionated `DeserializerRule` that flattens tables in a way that repeats
|
|
17
|
+
* the header row for each cell in the row.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```html
|
|
21
|
+
* <table>
|
|
22
|
+
* <thead>
|
|
23
|
+
* <tr>
|
|
24
|
+
* <th>Header 1</th>
|
|
25
|
+
* <th>Header 2</th>
|
|
26
|
+
* </tr>
|
|
27
|
+
* </thead>
|
|
28
|
+
* <tbody>
|
|
29
|
+
* <tr>
|
|
30
|
+
* <td>Cell 1</td>
|
|
31
|
+
* <td>Cell 2</td>
|
|
32
|
+
* </tr>
|
|
33
|
+
* </tbody>
|
|
34
|
+
* </table>
|
|
35
|
+
* ```
|
|
36
|
+
* Turns into
|
|
37
|
+
* ```json
|
|
38
|
+
* [
|
|
39
|
+
* {
|
|
40
|
+
* _type: 'block',
|
|
41
|
+
* children: [
|
|
42
|
+
* {
|
|
43
|
+
* _type: 'text',
|
|
44
|
+
* text: 'Header 1'
|
|
45
|
+
* },
|
|
46
|
+
* {
|
|
47
|
+
* _type: 'text',
|
|
48
|
+
* text: 'Cell 1'
|
|
49
|
+
* }
|
|
50
|
+
* ]
|
|
51
|
+
* },
|
|
52
|
+
* {
|
|
53
|
+
* _type: 'block',
|
|
54
|
+
* children: [
|
|
55
|
+
* {
|
|
56
|
+
* _type: 'text',
|
|
57
|
+
* text: 'Header 2'
|
|
58
|
+
* },
|
|
59
|
+
* {
|
|
60
|
+
* _type: 'text',
|
|
61
|
+
* text: 'Cell 2'
|
|
62
|
+
* }
|
|
63
|
+
* ]
|
|
64
|
+
* }
|
|
65
|
+
* ]
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* Use the `separator` option to control if a child element should separate
|
|
69
|
+
* headers and cells.
|
|
70
|
+
*
|
|
71
|
+
* @beta
|
|
72
|
+
*/
|
|
73
|
+
export function createFlattenTableRule({
|
|
74
|
+
schema,
|
|
75
|
+
separator,
|
|
76
|
+
}: {
|
|
77
|
+
schema: Schema
|
|
78
|
+
separator?: () =>
|
|
79
|
+
| (Omit<PortableTextSpan, '_key'> & {_key?: string})
|
|
80
|
+
| (Omit<PortableTextObject, '_key'> & {_key?: string})
|
|
81
|
+
| undefined
|
|
82
|
+
}): DeserializerRule {
|
|
83
|
+
return {
|
|
84
|
+
deserialize: (node, next) => {
|
|
85
|
+
if (!isElement(node) || tagName(node) !== 'table') {
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const thead = node.querySelector('thead')
|
|
90
|
+
let headerRow = thead?.querySelector('tr')
|
|
91
|
+
const tbody = node.querySelector('tbody')
|
|
92
|
+
let bodyRows = tbody ? [...tbody.querySelectorAll('tr')] : []
|
|
93
|
+
|
|
94
|
+
if (!headerRow || !bodyRows) {
|
|
95
|
+
// If there is not thead or tbody, we look at the column count. If the
|
|
96
|
+
// column count is greater than 2 then we infer that the first row is
|
|
97
|
+
// the header row and the rest are the body rows.
|
|
98
|
+
|
|
99
|
+
const columnCounts = [...node.querySelectorAll('tr')].map((row) => {
|
|
100
|
+
const cells = row.querySelectorAll('td')
|
|
101
|
+
return cells.length
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const firstColumnCount = columnCounts[0]
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
!firstColumnCount ||
|
|
108
|
+
!columnCounts.every((count) => count === firstColumnCount)
|
|
109
|
+
) {
|
|
110
|
+
return undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (firstColumnCount < 3) {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Now we know that all rows have the same column count and that
|
|
118
|
+
// count is >2
|
|
119
|
+
const rows = [...node.querySelectorAll('tr')]
|
|
120
|
+
headerRow = rows.slice(0, 1)[0]
|
|
121
|
+
bodyRows = rows.slice(1)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!headerRow) {
|
|
125
|
+
return undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const headerCells = headerRow.querySelectorAll('th, td')
|
|
129
|
+
const headerResults = [...headerCells].map((headerCell) =>
|
|
130
|
+
next(headerCell),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// Process tbody rows and combine with headers
|
|
134
|
+
const rows: TypedObject[] = []
|
|
135
|
+
|
|
136
|
+
for (const row of bodyRows) {
|
|
137
|
+
const cells = row.querySelectorAll('td')
|
|
138
|
+
|
|
139
|
+
let cellIndex = 0
|
|
140
|
+
for (const cell of cells) {
|
|
141
|
+
const result = next(cell)
|
|
142
|
+
|
|
143
|
+
if (!result) {
|
|
144
|
+
cellIndex++
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const headerResult = headerResults[cellIndex]
|
|
149
|
+
|
|
150
|
+
if (!headerResult) {
|
|
151
|
+
// If we can't find a corresponding header, then we just push
|
|
152
|
+
// the deserialized cell as is.
|
|
153
|
+
if (Array.isArray(result)) {
|
|
154
|
+
rows.push(...result)
|
|
155
|
+
} else {
|
|
156
|
+
rows.push(result)
|
|
157
|
+
}
|
|
158
|
+
cellIndex++
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const flattenedHeaderResult = flattenNestedBlocks(
|
|
163
|
+
{schema},
|
|
164
|
+
(Array.isArray(headerResult)
|
|
165
|
+
? headerResult
|
|
166
|
+
: [headerResult]) as Array<ArbitraryTypedObject>,
|
|
167
|
+
)
|
|
168
|
+
const firstFlattenedHeaderResult = flattenedHeaderResult[0]
|
|
169
|
+
const flattenedResult = flattenNestedBlocks(
|
|
170
|
+
{schema},
|
|
171
|
+
(Array.isArray(result)
|
|
172
|
+
? result
|
|
173
|
+
: [result]) as Array<ArbitraryTypedObject>,
|
|
174
|
+
)
|
|
175
|
+
const firstFlattenedResult = flattenedResult[0]
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
flattenedHeaderResult.length === 1 &&
|
|
179
|
+
isTextBlock({schema}, firstFlattenedHeaderResult) &&
|
|
180
|
+
flattenedResult.length === 1 &&
|
|
181
|
+
isTextBlock({schema}, firstFlattenedResult)
|
|
182
|
+
) {
|
|
183
|
+
const separatorChild = separator?.()
|
|
184
|
+
// If the header result and the cell result are text blocks then
|
|
185
|
+
// we merge them together.
|
|
186
|
+
const mergedTextBlock = {
|
|
187
|
+
...firstFlattenedHeaderResult,
|
|
188
|
+
children: [
|
|
189
|
+
...firstFlattenedHeaderResult.children,
|
|
190
|
+
...(separatorChild ? [separatorChild] : []),
|
|
191
|
+
...firstFlattenedResult.children,
|
|
192
|
+
],
|
|
193
|
+
markDefs: [
|
|
194
|
+
...(firstFlattenedHeaderResult.markDefs ?? []),
|
|
195
|
+
...(firstFlattenedResult.markDefs ?? []),
|
|
196
|
+
],
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
rows.push(mergedTextBlock)
|
|
200
|
+
cellIndex++
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Otherwise, we push the header result and the cell result as is.
|
|
205
|
+
if (Array.isArray(headerResult)) {
|
|
206
|
+
rows.push(...headerResult)
|
|
207
|
+
} else {
|
|
208
|
+
rows.push(headerResult)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (Array.isArray(result)) {
|
|
212
|
+
rows.push(...result)
|
|
213
|
+
} else {
|
|
214
|
+
rows.push(result)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
cellIndex++
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Return the processed rows as individual text blocks
|
|
222
|
+
return rows
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './flatten-tables'
|