Paste another site's text without replaying it forever

view.pasteText(text, event) re-asks every paste handler with the event it was given, so the composer's own handler replayed the paste until the stack overflowed (any rich paste from VS Code, Slack or a browser page). It now passes no event, and a ref keeps it from re-entering either way.

The plain-text decision moves to shouldPastePlainText with its own tests.
This commit is contained in:
2026-10-22 08:35:39 +01:00
parent 9af5323530
commit ffd32c8b27
3 changed files with 68 additions and 2 deletions
@@ -0,0 +1,29 @@
import { describe, expect, test } from "bun:test";
import shouldPastePlainText from "./should-paste-plain-text";
describe("should paste plain text", () => {
test("replays another site's rich paste as plain text", () => {
expect(
shouldPastePlainText({
html: '<meta charset="utf-8"><span>GROK_BOT_MAILBOX_PASSWORD</span>',
text: "GROK_BOT_MAILBOX_PASSWORD",
}),
).toBe(true);
expect(shouldPastePlainText({ html: "<p>line one</p><p>line two</p>", text: "one\ntwo" })).toBe(true);
});
test("keeps our own copies formatted", () => {
expect(
shouldPastePlainText({
html: '<p data-pm-slice="1 1 []"><strong>bold</strong></p>',
text: "**bold**",
}),
).toBe(false);
});
test("leaves a plain paste and a file paste alone", () => {
expect(shouldPastePlainText({ html: "", text: "just text" })).toBe(false);
expect(shouldPastePlainText({ html: '<img src="blob:x">', text: "" })).toBe(false);
expect(shouldPastePlainText({ html: "", text: "" })).toBe(false);
});
});
@@ -0,0 +1,19 @@
type Params = {
/** The clipboard's `text/html`. */
html: string;
/** The clipboard's `text/plain`. */
text: string;
};
/**
* Whether a paste is replayed as plain text instead of parsed as HTML: a
* rich paste from another site drops its styling (its markup and CSS),
* while our own copies (`data-pm-slice`) keep their formatting and a paste
* with no text (files, an image) is none of this handler's business.
*/
export default function shouldPastePlainText({ html, text }: Params) {
if (!text) return false;
if (!html) return false;
return !html.includes("data-pm-slice");
}