Skip to content
This repository was archived by the owner on Aug 18, 2023. It is now read-only.

Commit dfd19dc

Browse files
committed
refactor nesting logic, support list type change, add tests
1 parent fbfafe3 commit dfd19dc

File tree

8 files changed

+253
-68
lines changed

8 files changed

+253
-68
lines changed

jest.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ module.exports = {
99
'^.+\\.vue$': 'vue-jest',
1010
},
1111
watchPathIgnorePatterns: ['<rootDir>/node_modules/'],
12+
globals: {
13+
'ts-jest': {
14+
tsconfig: 'tsconfig.jest.json',
15+
},
16+
},
1217
};

src/index.ts

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DefineComponent, defineComponent, h, PropType, toRaw } from 'vue';
44
import {
55
Block,
66
BlockList,
7+
BlockListItem,
78
BlockSerializer,
89
BlockSpan,
910
BlockText,
@@ -16,8 +17,10 @@ import {
1617
SpanSerializer,
1718
} from './types';
1819

20+
const notNull = <T>(x: T | null): x is T => x !== null;
21+
1922
const createElementFromStyle = (
20-
block: BlockText,
23+
block: BlockText | BlockListItem,
2124
serializers: Serializers,
2225
children: (SerializedNode | SerializedNode[])[]
2326
) => {
@@ -96,7 +99,10 @@ const attachMarks = (
9699
}
97100

98101
if (typeof serializer === 'function') {
99-
return serializer(markDef, attachMarks(span, marks, serializers, markDefs));
102+
return serializer(
103+
markDef || {},
104+
attachMarks(span, marks, serializers, markDefs)
105+
);
100106
}
101107

102108
return h(
@@ -118,8 +124,7 @@ const spanSerializer: SpanSerializer = (span, serializers, markDefs) => {
118124
};
119125

120126
const blockTextSerializer = (block: BlockText, serializers: Serializers) => {
121-
// @TODO Using type assertion, maybe not ideal
122-
const nodes = block.children.flatMap((span: BlockSpan) => {
127+
const nodes = block.children.flatMap((span) => {
123128
return spanSerializer(span, serializers, block.markDefs);
124129
});
125130
return createElementFromStyle(block, serializers, nodes);
@@ -136,12 +141,12 @@ const linkSerializer: MarkSerializer = (props, children) => {
136141
);
137142
};
138143

139-
const listSerializer = (block: BlockText, serializers: Serializers) => {
144+
const listSerializer = (block: BlockListItem, serializers: Serializers) => {
140145
const el = block.listItem === 'number' ? 'ol' : 'ul';
141146
return h(el, {}, renderBlocks(block.children, serializers, block.level));
142147
};
143148

144-
const listItemSerializer = (block: BlockText, serializers: Serializers) => {
149+
const listItemSerializer = (block: BlockListItem, serializers: Serializers) => {
145150
// Array of array of strings or nodes
146151
const children = renderBlocks(block.children, serializers, block.level);
147152
const shouldWrap = block.style && block.style !== 'normal';
@@ -185,7 +190,7 @@ const serializeBlock = (block: Block | BlockSpan, serializers: Serializers) => {
185190
return h(serializer, {});
186191
};
187192

188-
const createList = (block: BlockText): BlockList => {
193+
const createList = (block: BlockListItem): BlockList => {
189194
return {
190195
_type: 'list',
191196
_key: `${block._key}-parent`,
@@ -196,63 +201,40 @@ const createList = (block: BlockText): BlockList => {
196201
};
197202

198203
const nestBlocks = (blocks: Array<Block | BlockSpan>, level = 0) => {
199-
const isListOrListItem = (block: Block | BlockSpan): block is BlockText =>
204+
const isListOrListItem = (block: Block | BlockSpan): block is BlockListItem =>
200205
'level' in block;
201206
const hasChildren = (
202207
block: Block | BlockSpan
203208
): block is BlockText | BlockList => block && 'children' in block;
204209
const newBlocks: Array<Block | BlockSpan> = [];
205-
let currentList: BlockList | null = null;
206210

207-
blocks.forEach((block, i) => {
211+
blocks.forEach((block) => {
208212
if (!isListOrListItem(block)) {
209213
newBlocks.push(block);
210214
return;
211215
}
212216

213217
const lastBlock = newBlocks[newBlocks.length - 1];
214218

215-
// If this is the first level, nest a bit differently
216-
if (level === 0) {
217-
if (
218-
!lastBlock ||
219-
!isListOrListItem(lastBlock) ||
220-
lastBlock.level > block.level
221-
) {
222-
newBlocks.push(createList(block));
223-
return;
224-
}
225-
lastBlock.children.push(block);
226-
return;
227-
}
228-
229-
// Level > 0
230219
if (block.level === level) {
231220
newBlocks.push(block);
232-
if (currentList && hasChildren(lastBlock)) {
233-
// @TODO Why is this only ever a block?
234-
lastBlock.children.push(currentList);
235-
currentList = null;
236-
return;
237-
}
221+
return;
238222
}
239223

240-
if (block.level > level) {
241-
if (hasChildren(lastBlock)) {
242-
if (currentList) {
243-
currentList.children.push(block);
244-
} else {
245-
currentList = createList(block);
246-
}
247-
}
248-
if (i === blocks.length - 1) {
249-
if (currentList && lastBlock && 'children' in lastBlock) {
250-
lastBlock.children.push(currentList);
251-
currentList = null;
252-
return;
253-
}
254-
newBlocks.push(block);
255-
return;
224+
if (block.level && block.level > level) {
225+
if (
226+
!hasChildren(lastBlock) ||
227+
!isListOrListItem(lastBlock) ||
228+
(lastBlock.level && lastBlock.level > block.level)
229+
) {
230+
newBlocks.push(createList(block));
231+
} else if (
232+
lastBlock.level === block.level &&
233+
lastBlock.listItem !== block.listItem
234+
) {
235+
newBlocks.push(createList(block));
236+
} else {
237+
lastBlock.children.push(block);
256238
}
257239
}
258240
});
@@ -270,7 +252,9 @@ const renderBlocks = (
270252
const nestedBlocks = nestBlocks(blocks, level);
271253

272254
// Loop through each block, and serialize it
273-
return nestedBlocks.map((block) => serializeBlock(block, serializers));
255+
return nestedBlocks
256+
.map((block) => serializeBlock(block, serializers))
257+
.filter(notNull);
274258
};
275259

276260
const defaultSerializers: Serializers = {

src/test/blocks.test.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { mount } from '@vue/test-utils';
22
import { SanityBlocks } from '..';
33

4-
// The component to test
5-
// const MessageComponent = {
6-
// template: '<p>{{ msg }}</p>',
7-
// props: ['msg']
8-
// };
9-
104
const content = [
115
{
126
_key: '64501e9fa998',

src/test/lists.test.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { mount } from '@vue/test-utils';
2+
import { SanityBlocks } from '..';
3+
4+
test('handles sequences of ordered and unordered lists', () => {
5+
const blocks = [
6+
{
7+
_key: '2c6fdfe3b63f',
8+
_type: 'block',
9+
children: [
10+
{
11+
_key: '0d6f58f717a3',
12+
_type: 'span',
13+
marks: [],
14+
text: 'One',
15+
},
16+
],
17+
level: 1,
18+
listItem: 'bullet',
19+
markDefs: [],
20+
style: 'normal',
21+
},
22+
{
23+
_key: '7e26d666420d',
24+
_type: 'block',
25+
children: [
26+
{
27+
_key: '568c79b83f5d',
28+
_type: 'span',
29+
marks: [],
30+
text: 'Two',
31+
},
32+
],
33+
level: 1,
34+
listItem: 'bullet',
35+
markDefs: [],
36+
style: 'normal',
37+
},
38+
{
39+
_key: 'e543c684b6ac',
40+
_type: 'block',
41+
children: [
42+
{
43+
_key: '4b2a4a47827e',
44+
_type: 'span',
45+
marks: [],
46+
text: 'Three',
47+
},
48+
],
49+
level: 1,
50+
listItem: 'number',
51+
markDefs: [],
52+
style: 'normal',
53+
},
54+
{
55+
_key: '28ed94e89ee2',
56+
_type: 'block',
57+
children: [
58+
{
59+
_key: '04f728babe2e',
60+
_type: 'span',
61+
marks: [],
62+
text: 'Four',
63+
},
64+
],
65+
level: 1,
66+
listItem: 'number',
67+
markDefs: [],
68+
style: 'normal',
69+
},
70+
{
71+
_key: '7b3ed455e5c8',
72+
_type: 'block',
73+
children: [
74+
{
75+
_key: 'b136c78e14ee',
76+
_type: 'span',
77+
marks: [],
78+
text: 'Five',
79+
},
80+
],
81+
level: 1,
82+
listItem: 'bullet',
83+
markDefs: [],
84+
style: 'normal',
85+
},
86+
{
87+
_key: '875b9781290a',
88+
_type: 'block',
89+
children: [
90+
{
91+
_key: '3566c13afd15',
92+
_type: 'span',
93+
marks: [],
94+
text: 'Six',
95+
},
96+
],
97+
level: 1,
98+
listItem: 'bullet',
99+
markDefs: [],
100+
style: 'normal',
101+
},
102+
{
103+
_key: 'd279400dfff2',
104+
_type: 'block',
105+
children: [
106+
{
107+
_key: 'c09e0dc89560',
108+
_type: 'span',
109+
marks: [],
110+
text: 'Seven',
111+
},
112+
],
113+
level: 2,
114+
listItem: 'number',
115+
markDefs: [],
116+
style: 'normal',
117+
},
118+
{
119+
_key: '2cabbe83f886',
120+
_type: 'block',
121+
children: [
122+
{
123+
_key: 'f15c8ac1ca4c',
124+
_type: 'span',
125+
marks: [],
126+
text: 'Eight',
127+
},
128+
],
129+
level: 2,
130+
listItem: 'number',
131+
markDefs: [],
132+
style: 'normal',
133+
},
134+
{
135+
_key: '8c39120c5830',
136+
_type: 'block',
137+
children: [
138+
{
139+
_key: '195af314c5d4',
140+
_type: 'span',
141+
marks: [],
142+
text: 'Nine',
143+
},
144+
],
145+
level: 2,
146+
listItem: 'bullet',
147+
markDefs: [],
148+
style: 'normal',
149+
},
150+
{
151+
_key: '24686faf7e52',
152+
_type: 'block',
153+
children: [
154+
{
155+
_key: 'b968d408863a',
156+
_type: 'span',
157+
marks: [],
158+
text: 'Ten',
159+
},
160+
],
161+
level: 2,
162+
listItem: 'bullet',
163+
markDefs: [],
164+
style: 'normal',
165+
},
166+
];
167+
168+
const wrapper = mount(SanityBlocks, {
169+
props: {
170+
blocks,
171+
},
172+
});
173+
174+
expect(wrapper.html()).toMatch(
175+
'<ul><li>One</li><li>Two</li></ul><ol><li>Three</li><li>Four</li></ol><ul><li>Five</li><li>Six<ol><li>Seven</li><li>Eight</li></ol><ul><li>Nine</li><li>Ten</li></ul></li></ul>'
176+
);
177+
});

src/test/serializer.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ test('custom type serializer with template', () => {
1414
],
1515
serializers: {
1616
types: {
17-
message: {
17+
message: defineComponent({
1818
template: '<p>{{ msg }}</p>',
1919
props: ['msg'],
20-
},
20+
}),
2121
},
2222
},
2323
},

0 commit comments

Comments
 (0)