mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-28 01:47:22 +02:00
Add a manage button in the thumbnail view in order to save an edited pdf (bug 2010830)
This commit is contained in:
parent
07a4aab246
commit
dd6a0c6cf4
@ -1182,10 +1182,39 @@ class PagesMapper {
|
|||||||
// Finally insert the moved pages.
|
// Finally insert the moved pages.
|
||||||
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
||||||
|
|
||||||
|
let hasChanged = false;
|
||||||
for (let i = 0, ii = pagesNumber; i < ii; i++) {
|
for (let i = 0, ii = pagesNumber; i < ii; i++) {
|
||||||
idToPageNumber[pageNumberToId[i] - 1] = i + 1;
|
const id = pageNumberToId[i];
|
||||||
|
hasChanged ||= id !== i + 1;
|
||||||
|
idToPageNumber[id - 1] = i + 1;
|
||||||
}
|
}
|
||||||
this.#updateListeners();
|
this.#updateListeners();
|
||||||
|
|
||||||
|
if (!hasChanged) {
|
||||||
|
// Reset.
|
||||||
|
this.pagesNumber = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the page mappings have been altered from their initial state.
|
||||||
|
* @returns {boolean} True if the mappings have been altered, false otherwise.
|
||||||
|
*/
|
||||||
|
hasBeenAltered() {
|
||||||
|
return PagesMapper.#pageNumberToId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current page mapping suitable for saving.
|
||||||
|
* @returns {Object} An object containing the page indices.
|
||||||
|
*/
|
||||||
|
getPageMappingForSaving() {
|
||||||
|
// Saving is index-based.
|
||||||
|
return {
|
||||||
|
pageIndices: PagesMapper.#idToPageNumber
|
||||||
|
? PagesMapper.#idToPageNumber.map(x => x - 1)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrevPageNumber(pageNumber) {
|
getPrevPageNumber(pageNumber) {
|
||||||
|
|||||||
@ -568,4 +568,66 @@ describe("Reorganize Pages View", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Save a pdf", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait(
|
||||||
|
"page_with_number.pdf",
|
||||||
|
"#viewsManagerToggleButton",
|
||||||
|
"1",
|
||||||
|
null,
|
||||||
|
{ enableSplitMerge: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should check that a save is triggered", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await waitForThumbnailVisible(page, 1);
|
||||||
|
await page.waitForSelector("#viewsManagerStatusActionButton", {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const rect1 = await getRect(page, getThumbnailSelector(1));
|
||||||
|
const rect2 = await getRect(page, getThumbnailSelector(2));
|
||||||
|
|
||||||
|
await dragAndDrop(
|
||||||
|
page,
|
||||||
|
getThumbnailSelector(1),
|
||||||
|
[[0, rect2.y - rect1.y + rect2.height / 2]],
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSaveAs = await createPromise(page, resolve => {
|
||||||
|
window.PDFViewerApplication.eventBus.on(
|
||||||
|
"savepageseditedpdf",
|
||||||
|
({ data }) => {
|
||||||
|
resolve(Array.from(data.pageIndices));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
once: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.click("#viewsManagerStatusActionButton");
|
||||||
|
await page.waitForSelector("#viewsManagerStatusActionSaveAs", {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.click("#viewsManagerStatusActionSaveAs");
|
||||||
|
const pageIndices = await awaitPromise(handleSaveAs);
|
||||||
|
expect(pageIndices)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toEqual([
|
||||||
|
1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -124,6 +124,13 @@ describe("PDF Thumbnail View", () => {
|
|||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
|
|
||||||
|
await kbFocusNext(page);
|
||||||
|
expect(
|
||||||
|
await isElementFocused(page, "#viewsManagerStatusActionButton")
|
||||||
|
)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBe(true);
|
||||||
|
|
||||||
await kbFocusNext(page);
|
await kbFocusNext(page);
|
||||||
expect(
|
expect(
|
||||||
await isElementFocused(
|
await isElementFocused(
|
||||||
|
|||||||
35
web/app.js
35
web/app.js
@ -605,6 +605,7 @@ const PDFViewerApplication = {
|
|||||||
abortSignal,
|
abortSignal,
|
||||||
enableHWA,
|
enableHWA,
|
||||||
enableSplitMerge: AppOptions.get("enableSplitMerge"),
|
enableSplitMerge: AppOptions.get("enableSplitMerge"),
|
||||||
|
manageMenu: appConfig.viewsManager.manageMenu,
|
||||||
});
|
});
|
||||||
renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
|
renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
|
||||||
}
|
}
|
||||||
@ -2194,6 +2195,11 @@ const PDFViewerApplication = {
|
|||||||
this.onBeforePagesEdited.bind(this),
|
this.onBeforePagesEdited.bind(this),
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
|
eventBus._on(
|
||||||
|
"savepageseditedpdf",
|
||||||
|
this.onSavePagesEditedPDF.bind(this),
|
||||||
|
opts
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
bindWindowEvents() {
|
bindWindowEvents() {
|
||||||
@ -2376,6 +2382,35 @@ const PDFViewerApplication = {
|
|||||||
this.pdfViewer.onPagesEdited(data);
|
this.pdfViewer.onPagesEdited(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async onSavePagesEditedPDF({
|
||||||
|
data: { includePages, excludePages, pageIndices },
|
||||||
|
}) {
|
||||||
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.pdfDocument) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pageInfo = {
|
||||||
|
document: null, // For now, no merge.
|
||||||
|
includePages,
|
||||||
|
excludePages,
|
||||||
|
pageIndices,
|
||||||
|
};
|
||||||
|
const modifiedPdfBytes = await this.pdfDocument.extractPages([pageInfo]);
|
||||||
|
if (!modifiedPdfBytes) {
|
||||||
|
console.error(
|
||||||
|
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.downloadManager.download(
|
||||||
|
modifiedPdfBytes,
|
||||||
|
this._downloadUrl,
|
||||||
|
this._docFilename
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
_accumulateTicks(ticks, prop) {
|
_accumulateTicks(ticks, prop) {
|
||||||
// If the direction changed, reset the accumulated ticks.
|
// If the direction changed, reset the accumulated ticks.
|
||||||
if ((this[prop] > 0 && ticks < 0) || (this[prop] < 0 && ticks > 0)) {
|
if ((this[prop] > 0 && ticks < 0) || (this[prop] < 0 && ticks > 0)) {
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import {
|
|||||||
watchScroll,
|
watchScroll,
|
||||||
} from "./ui_utils.js";
|
} from "./ui_utils.js";
|
||||||
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
||||||
|
import { Menu } from "./menu.js";
|
||||||
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
||||||
|
|
||||||
const SCROLL_OPTIONS = {
|
const SCROLL_OPTIONS = {
|
||||||
@ -67,6 +68,8 @@ const SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT = 15;
|
|||||||
* rendering. The default value is `false`.
|
* rendering. The default value is `false`.
|
||||||
* @property {boolean} [enableSplitMerge] - Enables split and merge features.
|
* @property {boolean} [enableSplitMerge] - Enables split and merge features.
|
||||||
* The default value is `false`.
|
* The default value is `false`.
|
||||||
|
* @property {Object} [manageMenu] - The menu elements to manage saving edited
|
||||||
|
* PDF.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,6 +112,8 @@ class PDFThumbnailViewer {
|
|||||||
|
|
||||||
#pagesMapper = PagesMapper.instance;
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
|
#manageSaveAsButton = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFThumbnailViewerOptions} options
|
* @param {PDFThumbnailViewerOptions} options
|
||||||
*/
|
*/
|
||||||
@ -123,6 +128,7 @@ class PDFThumbnailViewer {
|
|||||||
abortSignal,
|
abortSignal,
|
||||||
enableHWA,
|
enableHWA,
|
||||||
enableSplitMerge,
|
enableSplitMerge,
|
||||||
|
manageMenu,
|
||||||
}) {
|
}) {
|
||||||
this.scrollableContainer = container.parentElement;
|
this.scrollableContainer = container.parentElement;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@ -135,6 +141,20 @@ class PDFThumbnailViewer {
|
|||||||
this.enableHWA = enableHWA || false;
|
this.enableHWA = enableHWA || false;
|
||||||
this.#enableSplitMerge = enableSplitMerge || false;
|
this.#enableSplitMerge = enableSplitMerge || false;
|
||||||
|
|
||||||
|
if (this.#enableSplitMerge && manageMenu) {
|
||||||
|
const { button, menu, copy, cut, delete: del, saveAs } = manageMenu;
|
||||||
|
this._manageMenu = new Menu(menu, button, [copy, cut, del, saveAs]);
|
||||||
|
this.#manageSaveAsButton = saveAs;
|
||||||
|
saveAs.addEventListener("click", () => {
|
||||||
|
this.eventBus.dispatch("savepageseditedpdf", {
|
||||||
|
source: this,
|
||||||
|
data: this.#pagesMapper.getPageMappingForSaving(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
manageMenu.button.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.scroll = watchScroll(
|
this.scroll = watchScroll(
|
||||||
this.scrollableContainer,
|
this.scrollableContainer,
|
||||||
this.#scrollUpdated.bind(this),
|
this.#scrollUpdated.bind(this),
|
||||||
@ -519,10 +539,16 @@ class PDFThumbnailViewer {
|
|||||||
selectedPages.clear();
|
selectedPages.clear();
|
||||||
this.#pageNumberToRemove = NaN;
|
this.#pageNumberToRemove = NaN;
|
||||||
|
|
||||||
this.eventBus.dispatch("pagesedited", {
|
const isIdentity = (this.#manageSaveAsButton.disabled =
|
||||||
source: this,
|
!this.#pagesMapper.hasBeenAltered());
|
||||||
pagesMapper,
|
if (!isIdentity) {
|
||||||
});
|
this.eventBus.dispatch("pagesedited", {
|
||||||
|
source: this,
|
||||||
|
pagesMapper,
|
||||||
|
index: newIndex,
|
||||||
|
pagesToMove,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
|
const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -187,7 +187,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="viewsManagerStatus">
|
<div id="viewsManagerStatus">
|
||||||
<div id="viewsManagerStatusAction" class="hidden">
|
<div id="viewsManagerStatusAction">
|
||||||
<span
|
<span
|
||||||
id="viewsManagerStatusActionLabel"
|
id="viewsManagerStatusActionLabel"
|
||||||
class="viewsManagerStatusLabel"
|
class="viewsManagerStatusLabel"
|
||||||
@ -207,22 +207,22 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
</button>
|
</button>
|
||||||
<menu id="viewsManagerStatusActionOptions" class="popupMenu">
|
<menu id="viewsManagerStatusActionOptions" class="popupMenu">
|
||||||
<li>
|
<li>
|
||||||
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0">
|
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||||
<span data-l10n-id="pdfjs-views-manager-pages-status-copy-button-label"></span>
|
<span data-l10n-id="pdfjs-views-manager-pages-status-copy-button-label"></span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0">
|
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||||
<span data-l10n-id="pdfjs-views-manager-pages-status-cut-button-label"></span>
|
<span data-l10n-id="pdfjs-views-manager-pages-status-cut-button-label"></span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0">
|
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||||
<span data-l10n-id="pdfjs-views-manager-pages-status-delete-button-label"></span>
|
<span data-l10n-id="pdfjs-views-manager-pages-status-delete-button-label"></span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0">
|
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||||
<span data-l10n-id="pdfjs-views-manager-pages-status-save-as-button-label"></span>
|
<span data-l10n-id="pdfjs-views-manager-pages-status-save-as-button-label"></span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -132,6 +132,14 @@ function getViewerConfiguration() {
|
|||||||
viewsManagerHeaderLabel: document.getElementById(
|
viewsManagerHeaderLabel: document.getElementById(
|
||||||
"viewsManagerHeaderLabel"
|
"viewsManagerHeaderLabel"
|
||||||
),
|
),
|
||||||
|
manageMenu: {
|
||||||
|
button: document.getElementById("viewsManagerStatusActionButton"),
|
||||||
|
menu: document.getElementById("viewsManagerStatusActionOptions"),
|
||||||
|
copy: document.getElementById("viewsManagerStatusActionCopy"),
|
||||||
|
cut: document.getElementById("viewsManagerStatusActionCut"),
|
||||||
|
delete: document.getElementById("viewsManagerStatusActionDelete"),
|
||||||
|
saveAs: document.getElementById("viewsManagerStatusActionSaveAs"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
findBar: {
|
findBar: {
|
||||||
bar: document.getElementById("findbar"),
|
bar: document.getElementById("findbar"),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user