From f753bec82d520ba176b9eded0a17072d98681042 Mon Sep 17 00:00:00 2001 From: Rain-Zheng <67583056+Rain-Zheng@users.noreply.github.com> Date: Fri, 8 Nov 2024 06:36:43 +0800 Subject: [PATCH] Allow browser's default paste behavior when pasting from Office Android (#2863) * Implement shouldPreventDefaultPaste function * fix build * fix build --- .../corePlugin/copyPaste/CopyPastePlugin.ts | 30 +++++++++++- .../copyPaste/CopyPastePluginTest.ts | 46 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/copyPaste/CopyPastePlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/copyPaste/CopyPastePlugin.ts index 62ba6d1248e..bfc3eb63042 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/copyPaste/CopyPastePlugin.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/copyPaste/CopyPastePlugin.ts @@ -198,10 +198,10 @@ class CopyPastePlugin implements PluginWithState { const dataTransfer = event.clipboardData; - if (dataTransfer?.items) { + if (shouldPreventDefaultPaste(dataTransfer, editor)) { event.preventDefault(); extractClipboardItems( - toArray(dataTransfer.items), + toArray(dataTransfer!.items), this.state.allowedCustomPasteType ).then((clipboardData: ClipboardData) => { if (!editor.isDisposed()) { @@ -339,6 +339,32 @@ export function preprocessTable(table: ContentModelTable) { : []; } +/** + * @internal + * Exported only for unit testing + */ +export function shouldPreventDefaultPaste( + dataTransfer: DataTransfer | null, + editor: IEditor +): boolean { + if (!dataTransfer?.items) { + return false; + } + + if (!editor.getEnvironment().isAndroid) { + return true; + } + + // On Android, the clipboard data from Office apps is a file, which can't be loaded + // so we have to allow the default browser behavior + return toArray(dataTransfer.items).some(item => { + const { type } = item; + const isNormalFile = item.kind === 'file' && type !== ''; + const isText = type.indexOf('text/') === 0; + return isNormalFile || isText; + }); +} + /** * @internal * Create a new instance of CopyPastePlugin diff --git a/packages/roosterjs-content-model-core/test/corePlugin/copyPaste/CopyPastePluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/copyPaste/CopyPastePluginTest.ts index 0ecfcf4a175..92a80cd199f 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/copyPaste/CopyPastePluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/copyPaste/CopyPastePluginTest.ts @@ -27,6 +27,7 @@ import { createCopyPastePlugin, onNodeCreated, preprocessTable, + shouldPreventDefaultPaste, } from '../../../lib/corePlugin/copyPaste/CopyPastePlugin'; const modelValue = { @@ -1390,4 +1391,49 @@ describe('CopyPastePlugin |', () => { }); }); }); + + describe('shouldPreventDefaultPaste', () => { + it('should not prevent default for empty clipboard data', () => { + const clipboardData = ({ + items: null + }); + const editor = ({}); + expect(shouldPreventDefaultPaste(clipboardData, editor)).toBeFalse(); + expect(shouldPreventDefaultPaste(null, editor)).toBeFalse(); + }); + + it('should prevent default on non-Android platforms', () => { + const clipboardData = ({ + items: [{ type: '', kind: 'file' }] + }); + const editor = ({ + getEnvironment: () => ({ isAndroid: false }) + }); + expect(shouldPreventDefaultPaste(clipboardData, editor)).toBeTrue(); + }); + + it('should prevent default for text or image clipboard data on Android platform', () => { + const textClipboardData = ({ + items: [{ type: 'text/plain', kind: 'string' }] + }); + const imageClipboardData = ({ + items: [{ type: 'image/png', kind: 'file' }] + }); + const editor = ({ + getEnvironment: () => ({ isAndroid: true }) + }); + expect(shouldPreventDefaultPaste(textClipboardData, editor)).toBeTrue(); + expect(shouldPreventDefaultPaste(imageClipboardData, editor)).toBeTrue(); + }); + + it('should not prevent default for file-only clipboard data on Android platform', () => { + const clipboardData = ({ + items: [{ type: '', kind: 'file' }] + }); + const editor = ({ + getEnvironment: () => ({ isAndroid: true }) + }); + expect(shouldPreventDefaultPaste(clipboardData, editor)).toBeFalse(); + }); + }); });