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

Commit 8ac1922

Browse files
niveditcfacebook-github-bot
authored andcommitted
Fix raw to tree conversion
Summary: The existing conversion logic doesn't correctly deal with complex nested lists. Rather than using a hash table (which doesn't track order), we need to use a stack that keeps track of the active parent blocks at each level. **Old (buggy behavior):** https://pxl.cl/hMPf **New (fixed behavior):** https://pxl.cl/hMPh Reviewed By: vdurmont Differential Revision: D10048052 fbshipit-source-id: b2c1a3fcad19818037aa1ebfdedf3025e794048d
1 parent 02e0e00 commit 8ac1922

File tree

3 files changed

+215
-35
lines changed

3 files changed

+215
-35
lines changed

src/component/utils/exploration/DraftTreeAdapter.js

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ const DraftTreeAdapter = {
112112
fromRawStateToRawTreeState(
113113
draftState: RawDraftContentState,
114114
): RawDraftContentState {
115-
let lastListDepthCacheRef = {};
116115
const transformedBlocks = [];
116+
const parentStack = [];
117117

118118
draftState.blocks.forEach(block => {
119119
const isList = isListBlock(block);
@@ -124,46 +124,51 @@ const DraftTreeAdapter = {
124124
};
125125

126126
if (!isList) {
127-
// reset the cache path
128-
lastListDepthCacheRef = {};
129127
transformedBlocks.push(treeBlock);
130128
return;
131129
}
132130

133-
// nesting
134-
if (depth > 0) {
135-
let parent = lastListDepthCacheRef[depth - 1];
136-
if (parent == null) {
137-
parent = {
138-
key: generateRandomKey(),
139-
text: '',
140-
depth: depth - 1,
141-
type: block.type,
142-
children: [],
143-
entityRanges: [],
144-
inlineStyleRanges: [],
145-
};
146-
147-
lastListDepthCacheRef[depth - 1] = parent;
148-
if (depth === 1) {
149-
// add as a root-level block
150-
transformedBlocks.push(parent);
151-
} else {
152-
// depth > 1 => also add as previous parent's child
153-
const grandparent = lastListDepthCacheRef[depth - 2];
154-
grandparent.children.push(parent);
155-
}
131+
let lastParent = parentStack[0];
132+
// block is non-nested & there are no nested blocks, directly push block
133+
if (lastParent == null && depth === 0) {
134+
transformedBlocks.push(treeBlock);
135+
// block is first nested block or previous nested block is at a lower level
136+
} else if (lastParent == null || lastParent.depth < depth - 1) {
137+
// create new parent block
138+
const newParent = {
139+
key: generateRandomKey(),
140+
text: '',
141+
depth: depth - 1,
142+
type: block.type,
143+
children: [],
144+
entityRanges: [],
145+
inlineStyleRanges: [],
146+
};
147+
148+
parentStack.unshift(newParent);
149+
if (depth === 1) {
150+
// add as a root-level block
151+
transformedBlocks.push(newParent);
152+
} else if (lastParent != null) {
153+
// depth > 1 => also add as previous parent's child
154+
lastParent.children.push(newParent);
155+
}
156+
newParent.children.push(treeBlock);
157+
} else if (lastParent.depth === depth - 1) {
158+
// add as child of last parent
159+
lastParent.children.push(treeBlock);
160+
} else {
161+
// pop out parents at levels above this one from the parent stack
162+
while (lastParent != null && lastParent.depth >= depth) {
163+
parentStack.shift();
164+
lastParent = parentStack[0];
165+
}
166+
if (depth > 0) {
167+
lastParent.children.push(treeBlock);
168+
} else {
169+
transformedBlocks.push(treeBlock);
156170
}
157-
158-
invariant(parent, 'Invalid depth for RawDraftContentBlock');
159-
160-
// push nested list blocks
161-
parent.children.push(treeBlock);
162-
return;
163171
}
164-
165-
// push root list blocks
166-
transformedBlocks.push(treeBlock);
167172
});
168173

169174
return {

src/component/utils/exploration/__tests__/DraftTreeAdapter-test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,67 @@ test('must be able to convert from raw state to raw tree state with nested trees
265265

266266
assertFromRawStateToRawTreeState(rawState);
267267
});
268+
269+
test('must be able to convert from raw state to raw tree state with nested trees of various depths', () => {
270+
const rawState = {
271+
blocks: [
272+
{
273+
key: 'A',
274+
text: 'alpha',
275+
type: 'unordered-list-item',
276+
depth: 0,
277+
},
278+
{
279+
key: 'B',
280+
text: 'beta',
281+
type: 'unordered-list-item',
282+
depth: 1,
283+
},
284+
{
285+
key: 'C',
286+
text: 'charlie',
287+
type: 'unordered-list-item',
288+
depth: 1,
289+
},
290+
{
291+
key: 'D',
292+
text: 'delta',
293+
type: 'unordered-list-item',
294+
depth: 2,
295+
},
296+
{
297+
key: 'E',
298+
text: 'epsilon',
299+
type: 'unordered-list-item',
300+
depth: 1,
301+
},
302+
{
303+
key: 'F',
304+
text: 'foo',
305+
type: 'unordered-list-item',
306+
depth: 2,
307+
},
308+
{
309+
key: 'G',
310+
text: 'gamma',
311+
type: 'unordered-list-item',
312+
depth: 3,
313+
},
314+
{
315+
key: 'H',
316+
text: 'house',
317+
type: 'unordered-list-item',
318+
depth: 1,
319+
},
320+
{
321+
key: 'I',
322+
text: 'iota',
323+
type: 'unordered-list-item',
324+
depth: 0,
325+
},
326+
],
327+
entityMap: {},
328+
};
329+
330+
assertFromRawStateToRawTreeState(rawState);
331+
});

src/component/utils/exploration/__tests__/__snapshots__/DraftTreeAdapter-test.js.snap

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,117 @@ Object {
135135
}
136136
`;
137137

138+
exports[`must be able to convert from raw state to raw tree state with nested trees of various depths 1`] = `
139+
Object {
140+
"blocks": Array [
141+
Object {
142+
"children": Array [],
143+
"depth": 0,
144+
"key": "A",
145+
"text": "alpha",
146+
"type": "unordered-list-item",
147+
},
148+
Object {
149+
"children": Array [
150+
Object {
151+
"children": Array [],
152+
"depth": 1,
153+
"key": "B",
154+
"text": "beta",
155+
"type": "unordered-list-item",
156+
},
157+
Object {
158+
"children": Array [],
159+
"depth": 1,
160+
"key": "C",
161+
"text": "charlie",
162+
"type": "unordered-list-item",
163+
},
164+
Object {
165+
"children": Array [
166+
Object {
167+
"children": Array [],
168+
"depth": 2,
169+
"key": "D",
170+
"text": "delta",
171+
"type": "unordered-list-item",
172+
},
173+
],
174+
"depth": 1,
175+
"entityRanges": Array [],
176+
"inlineStyleRanges": Array [],
177+
"key": "key5",
178+
"text": "",
179+
"type": "unordered-list-item",
180+
},
181+
Object {
182+
"children": Array [],
183+
"depth": 1,
184+
"key": "E",
185+
"text": "epsilon",
186+
"type": "unordered-list-item",
187+
},
188+
Object {
189+
"children": Array [
190+
Object {
191+
"children": Array [],
192+
"depth": 2,
193+
"key": "F",
194+
"text": "foo",
195+
"type": "unordered-list-item",
196+
},
197+
Object {
198+
"children": Array [
199+
Object {
200+
"children": Array [],
201+
"depth": 3,
202+
"key": "G",
203+
"text": "gamma",
204+
"type": "unordered-list-item",
205+
},
206+
],
207+
"depth": 2,
208+
"entityRanges": Array [],
209+
"inlineStyleRanges": Array [],
210+
"key": "key7",
211+
"text": "",
212+
"type": "unordered-list-item",
213+
},
214+
],
215+
"depth": 1,
216+
"entityRanges": Array [],
217+
"inlineStyleRanges": Array [],
218+
"key": "key6",
219+
"text": "",
220+
"type": "unordered-list-item",
221+
},
222+
Object {
223+
"children": Array [],
224+
"depth": 1,
225+
"key": "H",
226+
"text": "house",
227+
"type": "unordered-list-item",
228+
},
229+
],
230+
"depth": 0,
231+
"entityRanges": Array [],
232+
"inlineStyleRanges": Array [],
233+
"key": "key4",
234+
"text": "",
235+
"type": "unordered-list-item",
236+
},
237+
Object {
238+
"children": Array [],
239+
"depth": 0,
240+
"key": "I",
241+
"text": "iota",
242+
"type": "unordered-list-item",
243+
},
244+
],
245+
"entityMap": Object {},
246+
}
247+
`;
248+
138249
exports[`must be able to convert from tree raw state with nested blocks to raw state 1`] = `
139250
Object {
140251
"blocks": Array [

0 commit comments

Comments
 (0)