Skip to content

Commit d325e39

Browse files
authored
fix(Clipboard): fixed trimContent (#709)
1 parent 93b6111 commit d325e39

File tree

2 files changed

+114
-45
lines changed

2 files changed

+114
-45
lines changed

src/extensions/behavior/Clipboard/utils.test.ts

Lines changed: 104 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -125,71 +125,134 @@ describe('findNotEmptyContentPosses', () => {
125125
});
126126

127127
describe('trimContent', () => {
128-
it('removes empty list items at the start and end of a bullet list', () => {
129-
const fragment = doc(ul(li(), li(p('11')), li(p('22')), li())).content;
128+
describe('should trim empty list items at both ends', () => {
129+
it('of bullet list', () => {
130+
const fragment = doc(ul(li(), li(p('11')), li(p('22')), li())).content;
131+
132+
const trimmedFragment = trimContent(fragment);
133+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
134+
doc(ul(li(p('11')), li(p('22')))),
135+
);
136+
});
137+
138+
it('of bullet list with nested list', () => {
139+
const fragment = doc(
140+
ul(li(), li(p('Outer item'), ul(li(p('Nested item')))), li()),
141+
).content;
142+
143+
const trimmedFragment = trimContent(fragment);
144+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
145+
doc(ul(li(p('Outer item'), ul(li(p('Nested item')))))),
146+
);
147+
});
148+
149+
it('of ordered list', () => {
150+
const fragment = doc(ol(li(), li(p('11')), li(p('22')), li())).content;
151+
152+
const trimmedFragment = trimContent(fragment);
153+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
154+
doc(ol(li(p('11')), li(p('22')))),
155+
);
156+
});
157+
158+
it('of bullet list with nested list', () => {
159+
const fragment = doc(ul(li(), li(ul(li(ul(li(ul(li(p('Deep'))))))), li()))).content;
160+
const trimmedFragment = trimContent(fragment);
161+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
162+
doc(ul(li(ul(li(ul(li(ul(li(p('Deep')))))))))),
163+
);
164+
});
165+
166+
it('of mixed lists', () => {
167+
const fragment = doc(
168+
ul(li(), li(p('11')), li(), li(p('22')), li()),
169+
ol(li(), li(p('aaa')), li(p('bbb')), li()),
170+
).content;
171+
172+
const trimmedFragment = trimContent(fragment);
173+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
174+
doc(ul(li(p('11')), li(), li(p('22')), li()), ol(li(), li(p('aaa')), li(p('bbb')))),
175+
);
176+
});
177+
});
130178

131-
const trimmedFragment = trimContent(fragment);
132-
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
133-
doc(ul(li(p('11')), li(p('22')))),
134-
);
179+
describe('should preserve empty list items', () => {
180+
it('in the middle of bullet list', () => {
181+
const fragment = doc(ul(li(), li(p('11')), li(), li(p('22')), li())).content;
182+
183+
const trimmedFragment = trimContent(fragment);
184+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
185+
doc(ul(li(p('11')), li(), li(p('22')))),
186+
);
187+
});
188+
189+
it('in the middle of bullet list with nested list', () => {
190+
const fragment = doc(
191+
ul(li(ul(li(p('Begin')), li())), li(ul(li(), li(p('End'))))),
192+
).content;
193+
const trimmedFragment = trimContent(fragment);
194+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
195+
doc(ul(li(ul(li(p('Begin')), li())), li(ul(li(), li(p('End')))))),
196+
);
197+
});
198+
199+
it('in the middle and end of bullet list', () => {
200+
const fragment = doc(
201+
p('Some text'),
202+
ul(li(), li(p('11')), li(p('22')), li()),
203+
p('More text'),
204+
).content;
205+
206+
const trimmedFragment = trimContent(fragment);
207+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
208+
doc(p('Some text'), ul(li(), li(p('11')), li(p('22')), li()), p('More text')),
209+
);
210+
});
135211
});
136212

137-
it('removes empty list items at the start and end of an ordered list', () => {
138-
const fragment = doc(ol(li(), li(p('11')), li(p('22')), li())).content;
213+
it('should trim empty items at boundaries of multi-level nested lists', () => {
214+
const fragment = doc(
215+
ul(
216+
li(),
217+
li(
218+
p('Level 1'),
219+
ul(li(), li(p('Level 2'), ul(li(), li(p('Level 3')), li())), li()),
220+
),
221+
li(),
222+
),
223+
).content;
139224

140225
const trimmedFragment = trimContent(fragment);
141226
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
142-
doc(ol(li(p('11')), li(p('22')))),
227+
doc(ul(li(p('Level 1'), ul(li(), li(p('Level 2'), ul(li(), li(p('Level 3')))))))),
143228
);
144229
});
145-
146-
it('removes only empty list items at the start and end, keeping empty ones in the middle', () => {
147-
const fragment = doc(ul(li(), li(p('11')), li(), li(p('22')), li())).content;
230+
it('should preserve only nested not-empty content', () => {
231+
const fragment = doc(ul(li(), li(ul(li(), li(p('Nested content')), li())), li())).content;
148232

149233
const trimmedFragment = trimContent(fragment);
150234
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
151-
doc(ul(li(p('11')), li(), li(p('22')))),
235+
doc(ul(li(ul(li(p('Nested content')))))),
152236
);
153237
});
154-
155-
it('does not modify a list with no empty items', () => {
156-
const fragment = doc(ul(li(p('11')), li(p('22')))).content;
157-
238+
it('should remove list items containing only whitespace', () => {
239+
const fragment = doc(ul(li(p(' ')), li(p('111')), li(p('\t\n')))).content;
158240
const trimmedFragment = trimContent(fragment);
159-
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
160-
doc(ul(li(p('11')), li(p('22')))),
161-
);
241+
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(doc(ul(li(p('111')))));
162242
});
163243

164-
it('trims a list to one empty list item if it contains only empty list items', () => {
244+
it('should return empty fragment when all list items are empty', () => {
165245
const fragment = doc(ul(li(), li(), li())).content;
166246

167247
const trimmedFragment = trimContent(fragment);
168248
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(doc());
169249
});
170-
171-
it('correctly handles multiple lists in a single fragment', () => {
172-
const fragment = doc(
173-
ul(li(), li(p('11')), li(), li(p('22')), li()),
174-
ol(li(), li(p('A')), li(p('B')), li()),
175-
).content;
176-
177-
const trimmedFragment = trimContent(fragment);
178-
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
179-
doc(ul(li(p('11')), li(), li(p('22')), li()), ol(li(), li(p('A')), li(p('B')))),
180-
);
181-
});
182-
183-
it('does not modify paragraphs and other nodes outside lists', () => {
184-
const fragment = doc(
185-
p('Some text'),
186-
ul(li(), li(p('11')), li(p('22')), li()),
187-
p('More text'),
188-
).content;
250+
it('should not modify lists without empty items', () => {
251+
const fragment = doc(ul(li(p('11')), li(p('22')))).content;
189252

190253
const trimmedFragment = trimContent(fragment);
191254
expect(schema.nodes.doc.create(null, trimmedFragment)).toMatchNode(
192-
doc(p('Some text'), ul(li(), li(p('11')), li(p('22')), li()), p('More text')),
255+
doc(ul(li(p('11')), li(p('22')))),
193256
);
194257
});
195258
});

src/extensions/behavior/Clipboard/utils.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ export function extractTextContentFromHtml(html: string) {
6262
return null;
6363
}
6464

65-
export function findNotEmptyContentPosses(fragment: Fragment): [number, number, number, number] {
65+
type ContentBoundaryPositions = [
66+
firstNotEmptyPos: number,
67+
lastNotEmptyPos: number,
68+
firstPos: number,
69+
lastPos: number,
70+
];
71+
export function findNotEmptyContentPosses(fragment: Fragment): ContentBoundaryPositions {
6672
let firstNodePos = -1;
6773
let lastNodePos = -1;
6874
let firstNotEmptyNodePos = -1;
@@ -78,10 +84,10 @@ export function findNotEmptyContentPosses(fragment: Fragment): [number, number,
7884
if (isListNode(contentNode) || isListItemNode(contentNode)) {
7985
const [start, end] = findNotEmptyContentPosses(contentNode.content);
8086
if (firstNotEmptyNodePos === -1 && start !== -1) {
81-
firstNotEmptyNodePos = offset + start;
87+
firstNotEmptyNodePos = offset + start + 1;
8288
}
8389
if (end !== -1) {
84-
lastNotEmptyNodePos = offset + end;
90+
lastNotEmptyNodePos = offset + end + 1;
8591
}
8692
} else {
8793
if (firstNotEmptyNodePos === -1) {
@@ -104,5 +110,5 @@ export function trimContent(fragment: Fragment, creatEmptyFragment?: () => Fragm
104110
return creatEmptyFragment ? creatEmptyFragment() : Fragment.empty;
105111
}
106112

107-
return fragment.cut(notEmptyStart + 1, notEmptyEnd + 1);
113+
return fragment.cut(notEmptyStart, notEmptyEnd);
108114
}

0 commit comments

Comments
 (0)