1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-20 15:18:08 +02:00

[Editor] Correctly handle lines when pasting some text in a freetext

This commit is contained in:
Calixte Denizet 2024-03-26 21:17:36 +01:00
parent 3d7ea6076d
commit 2dbd7acc41
4 changed files with 319 additions and 44 deletions

View file

@ -39,6 +39,7 @@ import {
kbSelectAll,
kbUndo,
loadAndWait,
pasteFromClipboard,
scrollIntoView,
waitForAnnotationEditorLayer,
waitForEvent,
@ -3546,4 +3547,166 @@ describe("FreeText Editor", () => {
);
});
});
describe("Paste some html", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must check that pasting html just keep the text", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToFreeText(page);
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
let editorSelector = getEditorSelector(0);
const data = "Hello PDF.js World !!";
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.waitForSelector(editorSelector, {
visible: true,
});
await page.type(`${editorSelector} .internal`, data);
const editorRect = await page.$eval(editorSelector, el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
// Commit.
await page.keyboard.press("Escape");
await page.waitForSelector(`${editorSelector} .overlay.enabled`);
const waitForTextChange = (previous, edSelector) =>
page.waitForFunction(
(prev, sel) => document.querySelector(sel).innerText !== prev,
{},
previous,
`${edSelector} .internal`
);
const getText = edSelector =>
page.$eval(`${edSelector} .internal`, el => el.innerText.trimEnd());
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2,
{ count: 2 }
);
await page.waitForSelector(
`${editorSelector} .overlay:not(.enabled)`
);
const select = position =>
page.evaluate(
(sel, pos) => {
const el = document.querySelector(sel);
document.getSelection().setPosition(el.firstChild, pos);
},
`${editorSelector} .internal`,
position
);
await select(0);
await pasteFromClipboard(
page,
{
"text/html": "<b>Bold Foo</b>",
"text/plain": "Foo",
},
`${editorSelector} .internal`
);
let lastText = data;
await waitForTextChange(lastText, editorSelector);
let text = await getText(editorSelector);
lastText = `Foo${data}`;
expect(text).withContext(`In ${browserName}`).toEqual(lastText);
await select(3);
await pasteFromClipboard(
page,
{
"text/html": "<b>Bold Bar</b><br><b>Oof</b>",
"text/plain": "Bar\nOof",
},
`${editorSelector} .internal`
);
await waitForTextChange(lastText, editorSelector);
text = await getText(editorSelector);
lastText = `FooBar\nOof${data}`;
expect(text).withContext(`In ${browserName}`).toEqual(lastText);
await select(0);
await pasteFromClipboard(
page,
{
"text/html": "<b>basic html</b>",
},
`${editorSelector} .internal`
);
// Nothing should change, so it's hard to wait on something.
await waitForTimeout(100);
text = await getText(editorSelector);
expect(text).withContext(`In ${browserName}`).toEqual(lastText);
const getHTML = () =>
page.$eval(`${editorSelector} .internal`, el => el.innerHTML);
const prevHTML = await getHTML();
// Try to paste an image.
await pasteFromClipboard(
page,
{
"image/png":
// 1x1 transparent png.
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
},
`${editorSelector} .internal`
);
// Nothing should change, so it's hard to wait on something.
await waitForTimeout(100);
const html = await getHTML();
expect(html).withContext(`In ${browserName}`).toEqual(prevHTML);
// Commit.
await page.keyboard.press("Escape");
await page.waitForSelector(`${editorSelector} .overlay.enabled`);
editorSelector = getEditorSelector(1);
await page.mouse.click(rect.x + 200, rect.y + 200);
await page.waitForSelector(editorSelector, {
visible: true,
});
const fooBar = "Foo\nBar\nOof";
await pasteFromClipboard(
page,
{
"text/html": "<b>html</b>",
"text/plain": fooBar,
},
`${editorSelector} .internal`
);
await waitForTextChange("", editorSelector);
text = await getText(editorSelector);
expect(text).withContext(`In ${browserName}`).toEqual(fooBar);
})
);
});
});
});

View file

@ -26,6 +26,7 @@ import {
kbSelectAll,
kbUndo,
loadAndWait,
pasteFromClipboard,
scrollIntoView,
serializeBitmapDimensions,
waitForAnnotationEditorLayer,
@ -72,43 +73,12 @@ const copyImage = async (page, imagePath, number) => {
const data = fs
.readFileSync(path.join(__dirname, imagePath))
.toString("base64");
await page.evaluate(async imageData => {
const resp = await fetch(`data:image/png;base64,${imageData}`);
const blob = await resp.blob();
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
}, data);
let hasPasteEvent = false;
while (!hasPasteEvent) {
// We retry to paste if nothing has been pasted before 500ms.
const handle = await page.evaluateHandle(() => {
let callback = null;
return [
Promise.race([
new Promise(resolve => {
callback = e => resolve(e.clipboardData.items.length !== 0);
document.addEventListener("paste", callback, {
once: true,
});
}),
new Promise(resolve => {
setTimeout(() => {
document.removeEventListener("paste", callback);
resolve(false);
}, 500);
}),
]),
];
});
await kbPaste(page);
hasPasteEvent = await awaitPromise(handle);
}
await pasteFromClipboard(
page,
{ "image/png": `data:image/png;base64,${data}` },
"",
500
);
await waitForImage(page, getEditorSelector(number));
};

View file

@ -206,6 +206,57 @@ async function mockClipboard(pages) {
);
}
async function pasteFromClipboard(page, data, selector, timeout = 100) {
await page.evaluate(async dat => {
const items = Object.create(null);
for (const [type, value] of Object.entries(dat)) {
if (value.startsWith("data:")) {
const resp = await fetch(value);
items[type] = await resp.blob();
} else {
items[type] = new Blob([value], { type });
}
}
await navigator.clipboard.write([new ClipboardItem(items)]);
}, data);
let hasPasteEvent = false;
while (!hasPasteEvent) {
// We retry to paste if nothing has been pasted before the timeout.
const handle = await page.evaluateHandle(
(sel, timeOut) => {
let callback = null;
return [
Promise.race([
new Promise(resolve => {
callback = e => resolve(e.clipboardData.items.length !== 0);
(sel ? document.querySelector(sel) : document).addEventListener(
"paste",
callback,
{
once: true,
}
);
}),
new Promise(resolve => {
setTimeout(() => {
document
.querySelector(sel)
.removeEventListener("paste", callback);
resolve(false);
}, timeOut);
}),
]),
];
},
selector,
timeout
);
await kbPaste(page);
hasPasteEvent = await awaitPromise(handle);
}
}
async function getSerialized(page, filter = undefined) {
const values = await page.evaluate(() => {
const { map } =
@ -526,6 +577,7 @@ export {
kbUndo,
loadAndWait,
mockClipboard,
pasteFromClipboard,
scrollIntoView,
serializeBitmapDimensions,
waitForAnnotationEditorLayer,