mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(app): paste copied text and its image together
Pasting from Word put only an image of the text in the composer, because the paste handler cancelled the paste whenever the clipboard held an image. Let the text paste normally when the clipboard has text, and still attach the images, so content copied with both (Word, or the desktop browser's element screenshot) keeps both. Fixes #3851 Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
This commit is contained in:
co-authored by
Bae Seongmin
parent
5eaf6d5464
commit
2291f54dc5
@@ -4,7 +4,6 @@ import { expectComposerVisible } from "../support/helpers/composer";
|
||||
import { expectAgentIdle } from "../support/helpers/agent-stream";
|
||||
import {
|
||||
openAttachmentMenu,
|
||||
composerLocator,
|
||||
expectAttachmentSheetRowsOnTitleRail,
|
||||
openGithubPickerFromMenu,
|
||||
attachImageFromMenu,
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
expectAttachButtonDisabled,
|
||||
fillComposerDraft,
|
||||
dropFileOnComposer,
|
||||
pasteClipboardIntoComposer,
|
||||
sendDraftToQueue,
|
||||
expectQueuedMessageButton,
|
||||
startRunningMockAgent,
|
||||
@@ -54,35 +54,41 @@ const TEST_JSON = {
|
||||
};
|
||||
|
||||
test.describe("Composer attachments", () => {
|
||||
test("pastes copied document text instead of its image representation", async ({
|
||||
test("pasting copied document text keeps the text and attaches its image", async ({
|
||||
page,
|
||||
context,
|
||||
withWorkspace,
|
||||
}) => {
|
||||
const workspace = await withWorkspace({ prefix: "paste-document-text-" });
|
||||
const workspace = await withWorkspace({ prefix: "paste-document-" });
|
||||
await workspace.navigateTo();
|
||||
await clickNewChat(page);
|
||||
await expectComposerVisible(page);
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
const text = "Document text with 한글 and two lines.\nSecond line.";
|
||||
await page.evaluate(
|
||||
async ({ content, png }) => {
|
||||
const response = await fetch(`data:image/png;base64,${png}`);
|
||||
const image = await response.blob();
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
"text/plain": new Blob([content], { type: "text/plain" }),
|
||||
"text/html": new Blob([`<p>${content}</p>`], { type: "text/html" }),
|
||||
"image/png": image,
|
||||
}),
|
||||
]);
|
||||
},
|
||||
{ content: text, png: MINIMAL_PNG.toString("base64") },
|
||||
);
|
||||
await composerLocator(page).click();
|
||||
await page.keyboard.press("ControlOrMeta+v");
|
||||
const text = "Copied paragraph with 한글.\nSecond line.";
|
||||
|
||||
await pasteClipboardIntoComposer(page, context, {
|
||||
text,
|
||||
html: "<p>Copied paragraph with 한글.</p><p>Second line.</p>",
|
||||
png: MINIMAL_PNG,
|
||||
});
|
||||
|
||||
await expectComposerDraft(page, text);
|
||||
await expect(page.getByTestId("composer-image-attachment-pill")).toHaveCount(0);
|
||||
await expectAttachmentPill(page, "composer-image-attachment-pill");
|
||||
});
|
||||
|
||||
test("pasting an image without text attaches it and leaves the draft empty", async ({
|
||||
page,
|
||||
context,
|
||||
withWorkspace,
|
||||
}) => {
|
||||
const workspace = await withWorkspace({ prefix: "paste-image-" });
|
||||
await workspace.navigateTo();
|
||||
await clickNewChat(page);
|
||||
await expectComposerVisible(page);
|
||||
|
||||
await pasteClipboardIntoComposer(page, context, { png: MINIMAL_PNG });
|
||||
|
||||
await expectAttachmentPill(page, "composer-image-attachment-pill");
|
||||
await expectComposerDraft(page, "");
|
||||
});
|
||||
|
||||
test("selected file shows a loading attachment until upload is acknowledged", async ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import { expect, type BrowserContext, type Page } from "@playwright/test";
|
||||
import { buildHostWorkspaceRoute } from "@/utils/host-routes";
|
||||
import { createTempGitRepo } from "./workspace";
|
||||
import { connectSeedClient, type SeedDaemonClient } from "./seed-client";
|
||||
@@ -172,6 +172,30 @@ export async function dropFileOnComposer(
|
||||
await dataTransfer.dispose();
|
||||
}
|
||||
|
||||
/** Puts the given formats on the system clipboard together, then pastes them into the composer with the keyboard shortcut. */
|
||||
export async function pasteClipboardIntoComposer(
|
||||
page: Page,
|
||||
context: BrowserContext,
|
||||
clipboard: { text?: string; html?: string; png?: Buffer },
|
||||
): Promise<void> {
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await page.evaluate(
|
||||
async ({ text, html, pngBase64 }) => {
|
||||
const formats: Record<string, Blob> = {};
|
||||
if (text !== undefined) formats["text/plain"] = new Blob([text], { type: "text/plain" });
|
||||
if (html !== undefined) formats["text/html"] = new Blob([html], { type: "text/html" });
|
||||
if (pngBase64 !== undefined) {
|
||||
const bytes = Uint8Array.from(atob(pngBase64), (char) => char.charCodeAt(0));
|
||||
formats["image/png"] = new Blob([bytes], { type: "image/png" });
|
||||
}
|
||||
await navigator.clipboard.write([new ClipboardItem(formats)]);
|
||||
},
|
||||
{ text: clipboard.text, html: clipboard.html, pngBase64: clipboard.png?.toString("base64") },
|
||||
);
|
||||
await composerInput(page).click();
|
||||
await page.keyboard.press("ControlOrMeta+v");
|
||||
}
|
||||
|
||||
/** Hover to reveal the X button (hidden until hover on desktop web), then click by accessible label. */
|
||||
export async function removeAttachmentPill(
|
||||
page: Page,
|
||||
|
||||
@@ -471,12 +471,13 @@ function usePasteImagesEffect(args: PasteImagesEffectArgs): void {
|
||||
let disposed = false;
|
||||
const handlePaste = (event: ClipboardEvent) => {
|
||||
if (!isConnected || disabled || isDictating || isRealtimeVoiceForCurrentAgent) return;
|
||||
if (event.clipboardData?.getData("text/plain")) return;
|
||||
|
||||
const imageFiles = collectImageFilesFromClipboardData(event.clipboardData);
|
||||
if (imageFiles.length === 0) return;
|
||||
|
||||
event.preventDefault();
|
||||
// Copied text often carries an image of itself, as from Word. Let the
|
||||
// text paste normally and attach the images alongside it.
|
||||
if (!event.clipboardData?.getData("text/plain")) event.preventDefault();
|
||||
|
||||
void filesToImageAttachments(imageFiles)
|
||||
.then((pastedAttachments) => {
|
||||
|
||||
Reference in New Issue
Block a user