Skip to content

Commit b7fc97d

Browse files
authored
Merge branch 'master' into u/juliaroldi/table-borders
2 parents 2c65281 + 43cfc1d commit b7fc97d

2 files changed

Lines changed: 122 additions & 3 deletions

File tree

packages/roosterjs-content-model-core/lib/command/paste/mergePasteContent.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export function mergePasteContent(
3737
containsBlockElements,
3838
} = eventResult;
3939

40+
const shouldScrollCaretIntoView = !isImageOnlyFragment(fragment);
41+
4042
editor.formatContentModel(
4143
(model, context) => {
4244
if (!isFirstPaste && clipboardData.modelBeforePaste) {
@@ -81,7 +83,7 @@ export function mergePasteContent(
8183
{
8284
changeSource: ChangeSource.Paste,
8385
getChangeData: () => clipboardData,
84-
scrollCaretIntoView: true,
86+
scrollCaretIntoView: shouldScrollCaretIntoView,
8587
apiName: 'paste',
8688
}
8789
);
@@ -138,3 +140,12 @@ function getLastSegmentFormat(pasteModel: ContentModelDocument): ContentModelSeg
138140

139141
return {};
140142
}
143+
144+
function isImageOnlyFragment(pasteFragment: DocumentFragment): boolean {
145+
const images = pasteFragment.querySelectorAll('img');
146+
return (
147+
images.length === 1 &&
148+
pasteFragment.childNodes.length === 1 &&
149+
pasteFragment.textContent?.trim() === ''
150+
);
151+
}

packages/roosterjs-content-model-core/test/command/paste/mergePasteContentTest.ts

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,18 @@ describe('mergePasteContent', () => {
3636
let editor: IEditor;
3737
let mockedClipboard: ClipboardData;
3838
let mockedDOMHelper: DOMHelper;
39+
let formatOptions: FormatContentModelOptions | undefined;
3940

4041
beforeEach(() => {
4142
formatResult = undefined;
4243
context = undefined;
43-
mockedClipboard = 'CLIPBOARD' as any;
44+
formatOptions = undefined;
45+
mockedClipboard = {
46+
fragment: {
47+
childNodes: [],
48+
length: 0,
49+
},
50+
} as any;
4451

4552
formatContentModel = jasmine
4653
.createSpy('formatContentModel')
@@ -50,6 +57,7 @@ describe('mergePasteContent', () => {
5057
deletedEntities: [],
5158
newImages: [],
5259
};
60+
formatOptions = options;
5361
formatResult = callback(sourceModel, context);
5462

5563
const changedData = options.getChangeData!();
@@ -180,6 +188,7 @@ describe('mergePasteContent', () => {
180188
pasteType: 'normal',
181189
domToModelOption: { additionalAllowedTags: [] },
182190
clipboardData: mockedClipboard,
191+
fragment: document.createDocumentFragment(),
183192
} as any;
184193

185194
mergePasteContent(editor, eventResult, true);
@@ -287,6 +296,7 @@ describe('mergePasteContent', () => {
287296
domToModelOption: { additionalAllowedTags: [] },
288297
customizedMerge,
289298
clipboardData: mockedClipboard,
299+
fragment: document.createDocumentFragment(),
290300
} as any;
291301

292302
mergePasteContent(editor, eventResult, true);
@@ -309,6 +319,7 @@ describe('mergePasteContent', () => {
309319
pasteType: 'mergeFormat',
310320
domToModelOption: { additionalAllowedTags: [] },
311321
clipboardData: mockedClipboard,
322+
fragment: document.createDocumentFragment(),
312323
} as any;
313324

314325
mergePasteContent(editor, eventResult, true);
@@ -384,7 +395,7 @@ describe('mergePasteContent', () => {
384395

385396
const mockedDomToModelOptions = 'OPTION1' as any;
386397
const mockedDefaultDomToModelOptions = 'OPTIONS3' as any;
387-
const mockedFragment = 'FRAGMENT' as any;
398+
const mockedFragment = document.createDocumentFragment();
388399

389400
(editor as any).getEnvironment = () => ({
390401
domToModelSettings: {
@@ -458,6 +469,7 @@ describe('mergePasteContent', () => {
458469
domToModelOption: { additionalAllowedTags: [] },
459470
clipboardData: mockedClipboard,
460471
containsBlockElements: true,
472+
fragment: document.createDocumentFragment(),
461473
} as any;
462474

463475
mergePasteContent(editor, eventResult, true);
@@ -2049,4 +2061,100 @@ describe('mergePasteContent', () => {
20492061
});
20502062
expect(cloneModelSpy).toHaveBeenCalledTimes(1);
20512063
});
2064+
2065+
describe('scrollCaretIntoView based on paste fragment', () => {
2066+
function runTest(fragment: DocumentFragment, expectedScrollCaretIntoView: boolean) {
2067+
spyOn(mergeModelFile, 'mergeModel').and.callThrough();
2068+
sourceModel = createContentModelDocument();
2069+
const para = createParagraph();
2070+
para.segments.push(createSelectionMarker());
2071+
sourceModel.blocks.push(para);
2072+
2073+
mergePasteContent(
2074+
editor,
2075+
<any>{
2076+
fragment,
2077+
domToModelOption: <any>{},
2078+
pasteType: 'normal',
2079+
clipboardData: mockedClipboard,
2080+
},
2081+
true
2082+
);
2083+
2084+
expect(formatOptions).toBeDefined();
2085+
expect(formatOptions!.scrollCaretIntoView).toBe(expectedScrollCaretIntoView);
2086+
}
2087+
2088+
function createFragment(...nodes: Node[]): DocumentFragment {
2089+
const fragment = document.createDocumentFragment();
2090+
nodes.forEach(node => fragment.appendChild(node));
2091+
return fragment;
2092+
}
2093+
2094+
it('should not scroll caret into view when fragment is a single image', () => {
2095+
const img = document.createElement('img');
2096+
img.src = 'test.png';
2097+
2098+
runTest(createFragment(img), false);
2099+
});
2100+
2101+
it('should not scroll caret into view when fragment is a span wrapping an image', () => {
2102+
const span = document.createElement('span');
2103+
span.appendChild(document.createElement('img'));
2104+
2105+
runTest(createFragment(span), false);
2106+
});
2107+
2108+
it('should not scroll caret into view when fragment is a div wrapping an image', () => {
2109+
const div = document.createElement('div');
2110+
div.appendChild(document.createElement('img'));
2111+
2112+
runTest(createFragment(div), false);
2113+
});
2114+
2115+
it('should scroll caret into view when fragment is a single text node', () => {
2116+
runTest(createFragment(document.createTextNode('text')), true);
2117+
});
2118+
2119+
it('should scroll caret into view when fragment is a paragraph', () => {
2120+
const p = document.createElement('p');
2121+
p.textContent = 'text';
2122+
2123+
runTest(createFragment(p), true);
2124+
});
2125+
2126+
it('should scroll caret into view when fragment has multiple children', () => {
2127+
const img1 = document.createElement('img');
2128+
const img2 = document.createElement('img');
2129+
2130+
runTest(createFragment(img1, img2), true);
2131+
});
2132+
2133+
it('should scroll caret into view when span wraps multiple children', () => {
2134+
const span = document.createElement('span');
2135+
span.appendChild(document.createElement('img'));
2136+
span.appendChild(document.createElement('img'));
2137+
2138+
runTest(createFragment(span), true);
2139+
});
2140+
2141+
it('should scroll caret into view when span wraps a non-image element', () => {
2142+
const span = document.createElement('span');
2143+
span.appendChild(document.createElement('b'));
2144+
2145+
runTest(createFragment(span), true);
2146+
});
2147+
2148+
it('should scroll caret into view when span wraps a text node', () => {
2149+
const span = document.createElement('span');
2150+
span.appendChild(document.createTextNode('text'));
2151+
runTest(createFragment(span), true);
2152+
});
2153+
2154+
it('should not scroll caret into view when fragment is an anchor wrapping an image', () => {
2155+
const anchor = document.createElement('a');
2156+
anchor.appendChild(document.createElement('img'));
2157+
runTest(createFragment(anchor), false);
2158+
});
2159+
});
20522160
});

0 commit comments

Comments
 (0)