From 39d059b50f72ff98db66f44caccd702eafd28096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez-Capel?= Date: Wed, 1 May 2024 11:56:39 +0100 Subject: [PATCH] Prioritize pasting files over pasting HTML content When right clicking and pasting an image the dataTransfer object contains a file and text/html. If we paste the HTML content, the result is often a broken image. This change prioritizes pasting the file over pasting the HTML content. The gotcha is that when pasting text from MS Word, the dataTransfer object contains a File with a screenshot of the text and text/html with the actual text. We need to check if the paste is from MS Word and if so, we should paste the text instead of the screenshot. --- src/test/system/level_2_input_test.js | 9 ++++++- .../controllers/level_2_input_controller.js | 24 +++++++++---------- src/trix/core/helpers/events.js | 6 +++++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/test/system/level_2_input_test.js b/src/test/system/level_2_input_test.js index 98d03f1d6..150419b74 100644 --- a/src/test/system/level_2_input_test.js +++ b/src/test/system/level_2_input_test.js @@ -231,7 +231,14 @@ testGroup("Level 2 Input", testOptions, () => { test("pasting text from MS Word", async () => { const file = await createFile() const dataTransfer = createDataTransfer({ - "text/html": "abc", + "text/html": ` + + abc + + `, "text/plain": "abc", Files: [ file ], }) diff --git a/src/trix/controllers/level_2_input_controller.js b/src/trix/controllers/level_2_input_controller.js index db10fb381..a83cf6760 100644 --- a/src/trix/controllers/level_2_input_controller.js +++ b/src/trix/controllers/level_2_input_controller.js @@ -2,7 +2,7 @@ import { getAllAttributeNames, squishBreakableWhitespace } from "trix/core/helpe import InputController from "trix/controllers/input_controller" import * as config from "trix/config" -import { dataTransferIsPlainText, keyEventIsKeyboardCommand, objectsAreEqual } from "trix/core/helpers" +import { dataTransferIsMsOfficePaste, dataTransferIsPlainText, keyEventIsKeyboardCommand, objectsAreEqual } from "trix/core/helpers" import { selectionChangeObserver } from "trix/observers/selection_change_observer" @@ -360,8 +360,10 @@ export default class Level2InputController extends InputController { insertFromPaste() { const { dataTransfer } = this.event const paste = { dataTransfer } + const href = dataTransfer.getData("URL") const html = dataTransfer.getData("text/html") + const file = dataTransfer.files?.[0] if (href) { let string @@ -378,7 +380,6 @@ export default class Level2InputController extends InputController { this.withTargetDOMRange(function() { return this.responder?.insertHTML(paste.html) }) - this.afterRender = () => { return this.delegate?.inputControllerDidPaste(paste) } @@ -393,26 +394,25 @@ export default class Level2InputController extends InputController { this.afterRender = () => { return this.delegate?.inputControllerDidPaste(paste) } - } else if (html) { - this.event.preventDefault() - paste.type = "text/html" - paste.html = html + } else if (file && !dataTransferIsMsOfficePaste(dataTransfer)) { + paste.type = "File" + paste.file = file this.delegate?.inputControllerWillPaste(paste) this.withTargetDOMRange(function() { - return this.responder?.insertHTML(paste.html) + return this.responder?.insertFile(paste.file) }) this.afterRender = () => { return this.delegate?.inputControllerDidPaste(paste) } - } else if (dataTransfer.files?.length) { - paste.type = "File" - paste.file = dataTransfer.files[0] + } else if (html) { + this.event.preventDefault() + paste.type = "text/html" + paste.html = html this.delegate?.inputControllerWillPaste(paste) this.withTargetDOMRange(function() { - return this.responder?.insertFile(paste.file) + return this.responder?.insertHTML(paste.html) }) - this.afterRender = () => { return this.delegate?.inputControllerDidPaste(paste) } diff --git a/src/trix/core/helpers/events.js b/src/trix/core/helpers/events.js index 07bd0bc24..0565a608d 100644 --- a/src/trix/core/helpers/events.js +++ b/src/trix/core/helpers/events.js @@ -14,6 +14,12 @@ export const dataTransferIsPlainText = function(dataTransfer) { } } +export const dataTransferIsMsOfficePaste = function(dataTransfer) { + return dataTransfer.types.includes("Files") && + dataTransfer.types.includes("text/html") && + dataTransfer.getData("text/html").includes("urn:schemas-microsoft-com:office:office") +} + export const dataTransferIsWritable = function(dataTransfer) { if (!dataTransfer?.setData) return false