Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 16 additions & 30 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1502,12 +1502,9 @@ class PDFPageProxy {
optionalContentConfigPromise ||=
this._transport.getOptionalContentConfig(renderingIntent);

let intentState = this._intentStates.get(cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(cacheKey, intentState);
}

const intentState = this._intentStates.getOrInsertComputed(cacheKey, () =>
Object.create(null)
);
// Ensure that a pending `streamReader` cancel timeout is always aborted.
if (intentState.streamReaderCancelTimeout) {
clearTimeout(intentState.streamReaderCancelTimeout);
Expand Down Expand Up @@ -1676,11 +1673,10 @@ class PDFPageProxy {
isEditing,
/* isOpList = */ true
);
let intentState = this._intentStates.get(intentArgs.cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(intentArgs.cacheKey, intentState);
}
const intentState = this._intentStates.getOrInsertComputed(
intentArgs.cacheKey,
() => Object.create(null)
);
let opListTask;

if (!intentState.opListReadCapability) {
Expand Down Expand Up @@ -2516,14 +2512,9 @@ class WorkerTransport {
}

#cacheSimpleMethod(name, data = null) {
const cachedPromise = this.#methodPromises.get(name);
if (cachedPromise) {
return cachedPromise;
}
const promise = this.messageHandler.sendWithPromise(name, data);

this.#methodPromises.set(name, promise);
return promise;
return this.#methodPromises.getOrInsertComputed(name, () =>
this.messageHandler.sendWithPromise(name, data)
);
}

#onProgress({ loaded, total }) {
Expand Down Expand Up @@ -3135,22 +3126,17 @@ class WorkerTransport {
}

getMetadata() {
const name = "GetMetadata",
cachedPromise = this.#methodPromises.get(name);
if (cachedPromise) {
return cachedPromise;
}
const promise = this.messageHandler
.sendWithPromise(name, null)
.then(results => ({
const name = "GetMetadata";

return this.#methodPromises.getOrInsertComputed(name, () =>
this.messageHandler.sendWithPromise(name, null).then(results => ({
info: results[0],
metadata: results[1] ? new Metadata(results[1]) : null,
contentDispositionFilename: this.#fullReader?.filename ?? null,
contentLength: this.#fullReader?.contentLength ?? null,
hasStructTree: results[2],
}));
this.#methodPromises.set(name, promise);
return promise;
}))
);
}

getMarkInfo() {
Expand Down
143 changes: 143 additions & 0 deletions test/integration/reorganize_pages_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
getAnnotationSelector,
getRect,
getThumbnailSelector,
kbCopy,
kbCut,
kbDelete,
loadAndWait,
scrollIntoView,
waitAndClick,
Expand Down Expand Up @@ -841,4 +844,144 @@ describe("Reorganize Pages View", () => {
);
});
});

describe("Keyboard shortcuts for cut and copy (bug 2018139)", () => {
let pages;

beforeEach(async () => {
pages = await loadAndWait(
"page_with_number.pdf",
"#viewsManagerToggleButton",
"1",
null,
{ enableSplitMerge: true }
);
});

afterEach(async () => {
await closePages(pages);
});

it("should cut pages with Ctrl+X and paste them", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await waitForThumbnailVisible(page, 1);
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(1)}) input`
);
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(3)}) input`
);

let handlePagesEdited = await waitForPagesEdited(page, "cut");
await kbCut(page);

let pageIndices = await awaitPromise(handlePagesEdited);
let expected = [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
expect(pageIndices)
.withContext(`In ${browserName}`)
.toEqual(expected);
await waitForHavingContents(page, expected);

handlePagesEdited = await waitForPagesEdited(page);
await waitAndClick(page, `${getThumbnailSelector(1)}+button`);
pageIndices = await awaitPromise(handlePagesEdited);
expected = [
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
];
expect(pageIndices)
.withContext(`In ${browserName}`)
.toEqual(expected);
await waitForHavingContents(page, expected);
})
);
});

it("should copy pages with Ctrl+C and paste them", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await waitForThumbnailVisible(page, 1);
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(1)}) input`
);
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(3)}) input`
);

let handlePagesEdited = await waitForPagesEdited(page, "copy");
await kbCopy(page);

let pageIndices = await awaitPromise(handlePagesEdited);
let expected = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
];
expect(pageIndices)
.withContext(`In ${browserName}`)
.toEqual(expected);
await waitForHavingContents(page, expected);

handlePagesEdited = await waitForPagesEdited(page);
await waitAndClick(page, `${getThumbnailSelector(2)}+button`);
pageIndices = await awaitPromise(handlePagesEdited);
expected = [
1, 2, 1, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
];
expect(pageIndices)
.withContext(`In ${browserName}`)
.toEqual(expected);
await waitForHavingContents(page, expected);
})
);
});
});

describe("Keyboard shortcuts for delete (bug 2010831)", () => {
let pages;

beforeEach(async () => {
pages = await loadAndWait(
"page_with_number.pdf",
"#viewsManagerToggleButton",
"1",
null,
{ enableSplitMerge: true }
);
});

afterEach(async () => {
await closePages(pages);
});

it("should delete pages with the Delete key", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await waitForThumbnailVisible(page, 1);
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(1)}) input`
);
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(3)}) input`
);

const handlePagesEdited = await waitForPagesEdited(page);
await kbDelete(page);

const pageIndices = await awaitPromise(handlePagesEdited);
const expected = [
2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
];
expect(pageIndices)
.withContext(`In ${browserName}`)
.toEqual(expected);
await waitForHavingContents(page, expected);
})
);
});
});
});
11 changes: 11 additions & 0 deletions test/integration/test_utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,14 @@ async function kbCopy(page) {
await page.keyboard.press("c", { commands: ["Copy"] });
await page.keyboard.up(modifier);
}
async function kbCut(page) {
await page.keyboard.down(modifier);
await page.keyboard.press("x", { commands: ["Cut"] });
await page.keyboard.up(modifier);
}
async function kbDelete(page) {
await page.keyboard.press("Delete");
}
async function kbPaste(page) {
await page.keyboard.down(modifier);
await page.keyboard.press("v", { commands: ["Paste"] });
Expand Down Expand Up @@ -997,6 +1005,9 @@ export {
kbBigMoveLeft,
kbBigMoveRight,
kbBigMoveUp,
kbCopy,
kbCut,
kbDelete,
kbDeleteLastWord,
kbFocusNext,
kbFocusPrevious,
Expand Down
12 changes: 11 additions & 1 deletion web/pdf_find_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,21 @@ function getNormalizeWithNFKC() {
(typeof PDFJSDev === "undefined" && FeatureTest.platform.isFirefox) ||
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL"))
) {
// The `NormalizeWithNFKC` string is generated with the for loop below.
// Because of a small difference between Chrome and Firefox, the string is
// only hardcoded for Firefox, and Chrome (or others) will generate it at
// runtime.
// In order to detect if the string is up to date, a check is performed in
// the loop below, and if a difference is detected, an error is thrown.
/* eslint-disable no-irregular-whitespace */
NormalizeWithNFKC ||= `\xA0¨ª¯²-µ¸-º¼-¾IJ-ijĿ-ŀʼnſDŽ-njDZ-dzʰ-ʸ˘-˝ˠ-ˤʹͺ;΄-΅·ϐ-ϖϰ-ϲϴ-ϵϹևٵ-ٸक़-य़ড়-ঢ়য়ਲ਼ਸ਼ਖ਼-ਜ਼ਫ਼ଡ଼-ଢ଼ำຳໜ-ໝ༌གྷཌྷདྷབྷཛྷཀྵჼᴬ-ᴮᴰ-ᴺᴼ-ᵍᵏ-ᵪᵸᶛ-ᶿẚ-ẛάέήίόύώΆ᾽-῁ΈΉ῍-῏ΐΊ῝-῟ΰΎ῭-`ΌΏ´-῾ - ‑‗․-… ″-‴‶-‷‼‾⁇-⁉⁗ ⁰-ⁱ⁴-₎ₐ-ₜ₨℀-℃℅-ℇ℉-ℓℕ-№ℙ-ℝ℠-™ℤΩℨK-ℭℯ-ℱℳ-ℹ℻-⅀ⅅ-ⅉ⅐-ⅿ↉∬-∭∯-∰〈-〉①-⓪⨌⩴-⩶⫝̸ⱼ-ⱽⵯ⺟⻳⼀-⿕ 〶〸-〺゛-゜ゟヿㄱ-ㆎ㆒-㆟㈀-㈞㈠-㉇㉐-㉾㊀-㏿ꚜ-ꚝꝰ꟱-ꟴꟸ-ꟹꭜ-ꭟꭩ豈-嗀塚晴凞-羽蘒諸逸-都飯-舘並-龎ff-stﬓ-ﬗיִײַ-זּטּ-לּמּנּ-סּףּ-פּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-﷼︐-︙︰-﹄﹇-﹒﹔-﹦﹨-﹫ﹰ-ﹲﹴﹶ-ﻼ!-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ¢-₩`;
}

if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
if (
typeof PDFJSDev === "undefined" ||
PDFJSDev.test("TESTING") ||
(!PDFJSDev.test("MOZCENTRAL") && !NormalizeWithNFKC)
) {
const ranges = [];
const range = [];
const diacriticsRegex = /^\p{M}$/u;
Expand Down
27 changes: 27 additions & 0 deletions web/pdf_thumbnail_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,33 @@ class PDFThumbnailViewer {
}
// For checkboxes, let the default behavior handle toggling
break;
case "c":
if (
this.#enableSplitMerge &&
(e.ctrlKey || e.metaKey) &&
this.#selectedPages?.size
) {
this.#copyPages();
stopEvent(e);
}
break;
case "x":
if (
this.#enableSplitMerge &&
(e.ctrlKey || e.metaKey) &&
this.#selectedPages?.size
) {
this.#cutPages();
stopEvent(e);
}
break;
case "Delete":
case "Backspace":
if (this.#enableSplitMerge && this.#selectedPages?.size) {
this.#deletePages();
stopEvent(e);
}
break;
}
});
this.container.addEventListener("click", e => {
Expand Down
Loading