From 96d1465aab8ae0de2fdc7218ffae9dfe66706d24 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 11 Mar 2026 18:52:40 +0100 Subject: [PATCH] Allow to collapse/expand all the outlines in double clicking somewhere on the header bar (bug 2019550) --- l10n/en-US/viewer.ftl | 6 +- test/integration/viewer_spec.mjs | 97 ++++++++++++++++++++++++++++++++ web/views_manager.js | 20 ++++--- 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/l10n/en-US/viewer.ftl b/l10n/en-US/viewer.ftl index 544b4d634..d86048686 100644 --- a/l10n/en-US/viewer.ftl +++ b/l10n/en-US/viewer.ftl @@ -705,9 +705,11 @@ pdfjs-views-manager-view-selector-button = .title = Views pdfjs-views-manager-view-selector-button-label = Views pdfjs-views-manager-pages-title = Pages -pdfjs-views-manager-outlines-title = Document outline +pdfjs-views-manager-outlines-title1 = Document outline + .title = Document outline (double-click to expand/collapse all items) pdfjs-views-manager-attachments-title = Attachments -pdfjs-views-manager-layers-title = Layers +pdfjs-views-manager-layers-title1 = Layers + .title = Layers (double-click to reset all layers to the default state) pdfjs-views-manager-pages-option-label = Pages pdfjs-views-manager-outlines-option-label = Document outline diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index d65e81ee9..8125a6128 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -1687,4 +1687,101 @@ describe("PDF viewer", () => { ); }); }); + + describe("Double-click on title collapses/expands all outline items", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "nested_outline.pdf", + "#viewsManagerToggleButton" + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should collapse all outline items on first double-click and expand them on second", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await showViewsManager(page); + + await page.click("#viewsManagerSelectorButton"); + await page.waitForSelector("#outlinesViewMenu", { visible: true }); + await page.click("#outlinesViewMenu"); + await page.waitForSelector("#outlinesView.withNesting"); + + // Initially all togglers must be expanded (none hidden). + const initialHiddenCount = await page.$$eval( + "#outlinesView .treeItemToggler", + togglers => + togglers.filter(t => t.classList.contains("treeItemsHidden")) + .length + ); + expect(initialHiddenCount).withContext(`In ${browserName}`).toBe(0); + + // Double-click the title label (not on a button) to collapse all. + await page.click("#viewsManagerHeaderLabel", { count: 2 }); + await page.waitForFunction( + () => + document.querySelectorAll( + "#outlinesView .treeItemToggler:not(.treeItemsHidden)" + ).length === 0 + ); + + // Double-click again to expand all. + await page.click("#viewsManagerHeaderLabel", { count: 2 }); + await page.waitForFunction( + () => + document.querySelectorAll( + "#outlinesView .treeItemToggler.treeItemsHidden" + ).length === 0 + ); + }) + ); + }); + }); + + describe("Double-click on title resets all layer checkboxes", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("issue17679.pdf", "#viewsManagerToggleButton"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should restore all layer checkboxes to checked after unchecking them", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await showViewsManager(page); + + await page.click("#viewsManagerSelectorButton"); + await page.waitForSelector("#layersViewMenu", { visible: true }); + await page.click("#layersViewMenu"); + await page.waitForSelector("#layersView input[type='checkbox']"); + + // Uncheck all checkboxes. + const checkboxes = await page.$$( + "#layersView input[type='checkbox']" + ); + for (const checkbox of checkboxes) { + await checkbox.click(); + } + await page.waitForSelector("#layersView:not(:has(:checked))"); + + // Double-click the title label to reset layers to their default + // state. + await page.click("#viewsManagerHeaderLabel", { count: 2 }); + + await page.waitForSelector( + `#layersView:not(:has(input[type="checkbox"]:not(:checked)))` + ); + }) + ); + }); + }); }); diff --git a/web/views_manager.js b/web/views_manager.js index b414d06ce..fc422701a 100644 --- a/web/views_manager.js +++ b/web/views_manager.js @@ -158,9 +158,9 @@ class ViewsManager extends Sidebar { ViewsManager.#l10nDescription ||= Object.freeze({ pagesTitle: "pdfjs-views-manager-pages-title", - outlinesTitle: "pdfjs-views-manager-outlines-title", + outlinesTitle: "pdfjs-views-manager-outlines-title1", attachmentsTitle: "pdfjs-views-manager-attachments-title", - layersTitle: "pdfjs-views-manager-layers-title", + layersTitle: "pdfjs-views-manager-layers-title1", notificationButton: "pdfjs-toggle-views-manager-notification-button", toggleButton: "pdfjs-toggle-views-manager-button", }); @@ -424,6 +424,16 @@ class ViewsManager extends Sidebar { }); } + this.viewsManagerHeaderLabel.addEventListener("dblclick", e => { + if (this.active === SidebarView.OUTLINE) { + eventBus.dispatch("toggleoutlinetree", { source: this }); + return; + } + if (this.active === SidebarView.LAYERS) { + eventBus.dispatch("resetlayers", { source: this }); + } + }); + // Buttons for switching views. this.thumbnailButton.addEventListener("click", () => { this.switchView(SidebarView.THUMBS); @@ -432,9 +442,6 @@ class ViewsManager extends Sidebar { this.outlineButton.addEventListener("click", () => { this.switchView(SidebarView.OUTLINE); }); - this.outlineButton.addEventListener("dblclick", () => { - eventBus.dispatch("toggleoutlinetree", { source: this }); - }); this.attachmentsButton.addEventListener("click", () => { this.switchView(SidebarView.ATTACHMENTS); @@ -443,9 +450,6 @@ class ViewsManager extends Sidebar { this.layersButton.addEventListener("click", () => { this.switchView(SidebarView.LAYERS); }); - this.layersButton.addEventListener("dblclick", () => { - eventBus.dispatch("resetlayers", { source: this }); - }); // Buttons for view-specific options. this.viewsManagerCurrentOutlineButton.addEventListener("click", () => {