Merge pull request #20303 from calixteman/bug1990544

[Editor] Make the comment sidebar resizable (bug 1990544)
This commit is contained in:
calixteman 2025-09-25 16:27:24 +02:00 committed by GitHub
commit 007148e2c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 170 additions and 9 deletions

View File

@ -29,6 +29,7 @@ import {
const switchToHighlight = switchToEditor.bind(null, "Highlight"); const switchToHighlight = switchToEditor.bind(null, "Highlight");
const switchToStamp = switchToEditor.bind(null, "Stamp"); const switchToStamp = switchToEditor.bind(null, "Stamp");
const switchToComment = switchToEditor.bind(null, "Comment");
describe("Comment", () => { describe("Comment", () => {
describe("Comment edit dialog must be visible in ltr", () => { describe("Comment edit dialog must be visible in ltr", () => {
@ -236,4 +237,55 @@ describe("Comment", () => {
); );
}); });
}); });
describe("Comment sidebar", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"comments.pdf",
".annotationEditorLayer",
"page-width",
null,
{ enableComment: true }
);
});
afterEach(async () => {
await closePages(pages);
});
it("must check that the comment sidebar is resizable", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToComment(page);
const sidebarSelector = "#editorCommentParamsToolbar";
for (const extraWidth of [100, -100]) {
const rect = await getRect(page, sidebarSelector);
const resizerRect = await getRect(
page,
"#editorCommentsSidebarResizer"
);
const startX = resizerRect.x + resizerRect.width / 2;
const startY = resizerRect.y + 2;
await page.mouse.move(startX, startY);
await page.mouse.down();
const steps = 20;
await page.mouse.move(startX - extraWidth, startY, { steps });
await page.mouse.up();
const rectAfter = await getRect(page, sidebarSelector);
expect(Math.abs(rectAfter.width - (rect.width + extraWidth)))
.withContext(`In ${browserName}`)
.toBeLessThanOrEqual(1);
expect(Math.abs(rectAfter.x - (rect.x - extraWidth)))
.withContext(`In ${browserName}`)
.toBeLessThanOrEqual(1);
}
})
);
});
});
}); });

View File

@ -506,6 +506,9 @@ const PDFViewerApplication = {
learnMoreUrl: AppOptions.get("commentLearnMoreUrl"), learnMoreUrl: AppOptions.get("commentLearnMoreUrl"),
sidebar: sidebar:
appConfig.annotationEditorParams?.editorCommentsSidebar || null, appConfig.annotationEditorParams?.editorCommentsSidebar || null,
sidebarResizer:
appConfig.annotationEditorParams
?.editorCommentsSidebarResizer || null,
commentsList: commentsList:
appConfig.annotationEditorParams?.editorCommentsSidebarList || appConfig.annotationEditorParams?.editorCommentsSidebarList ||
null, null,

View File

@ -234,10 +234,7 @@
#editorCommentsSidebar { #editorCommentsSidebar {
display: flex; display: flex;
width: 239px;
height: auto; height: auto;
min-width: 180px;
max-width: 632px;
padding-bottom: 16px; padding-bottom: 16px;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@ -331,8 +328,6 @@
width: auto; width: auto;
padding: 4px 16px; padding: 4px 16px;
gap: 10px; gap: 10px;
flex: 1 0 0;
align-self: stretch;
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
list-style-type: none; list-style-type: none;

View File

@ -18,6 +18,7 @@ import {
applyOpacity, applyOpacity,
CSSConstants, CSSConstants,
findContrastColor, findContrastColor,
MathClamp,
noContextMenu, noContextMenu,
PDFDateString, PDFDateString,
renderRichText, renderRichText,
@ -56,7 +57,8 @@ class CommentManager {
eventBus, eventBus,
linkService, linkService,
this.#popup, this.#popup,
dateFormat dateFormat,
ltr
); );
this.#popup.sidebar = this.#sidebar; this.#popup.sidebar = this.#sidebar;
CommentManager.#hasForcedColors = hasForcedColors; CommentManager.#hasForcedColors = hasForcedColors;
@ -160,10 +162,21 @@ class CommentSidebar {
#uiManager = null; #uiManager = null;
#minWidth = 0;
#maxWidth = 0;
#initialWidth = 0;
#width = 0;
#ltr;
constructor( constructor(
{ {
learnMoreUrl, learnMoreUrl,
sidebar, sidebar,
sidebarResizer,
commentsList, commentsList,
commentCount, commentCount,
sidebarTitle, sidebarTitle,
@ -173,7 +186,8 @@ class CommentSidebar {
eventBus, eventBus,
linkService, linkService,
popup, popup,
dateFormat dateFormat,
ltr
) { ) {
this.#sidebar = sidebar; this.#sidebar = sidebar;
this.#sidebarTitle = sidebarTitle; this.#sidebarTitle = sidebarTitle;
@ -184,7 +198,16 @@ class CommentSidebar {
this.#closeButton = closeButton; this.#closeButton = closeButton;
this.#popup = popup; this.#popup = popup;
this.#dateFormat = dateFormat; this.#dateFormat = dateFormat;
this.#ltr = ltr;
const style = window.getComputedStyle(sidebar);
this.#minWidth = parseFloat(style.getPropertyValue("--sidebar-min-width"));
this.#maxWidth = parseFloat(style.getPropertyValue("--sidebar-max-width"));
this.#initialWidth = this.#width = parseFloat(
style.getPropertyValue("--sidebar-width")
);
this.#makeSidebarResizable(sidebarResizer);
closeButton.addEventListener("click", () => { closeButton.addEventListener("click", () => {
eventBus.dispatch("switchannotationeditormode", { eventBus.dispatch("switchannotationeditormode", {
source: this, source: this,
@ -205,6 +228,63 @@ class CommentSidebar {
this.#sidebar.hidden = true; this.#sidebar.hidden = true;
} }
#makeSidebarResizable(resizer) {
let pointerMoveAC;
const cancelResize = () => {
this.#width = MathClamp(this.#width, this.#minWidth, this.#maxWidth);
this.#sidebar.classList.remove("resizing");
pointerMoveAC?.abort();
pointerMoveAC = null;
};
resizer.addEventListener("pointerdown", e => {
if (pointerMoveAC) {
cancelResize();
return;
}
const { clientX } = e;
stopEvent(e);
let prevX = clientX;
pointerMoveAC = new AbortController();
const { signal } = pointerMoveAC;
const sign = this.#ltr ? -1 : 1;
const sidebar = this.#sidebar;
const sidebarStyle = sidebar.style;
sidebar.classList.add("resizing");
const parentStyle = sidebar.parentElement.style;
parentStyle.minWidth = 0;
window.addEventListener("contextmenu", noContextMenu, { signal });
window.addEventListener(
"pointermove",
ev => {
if (!pointerMoveAC) {
return;
}
stopEvent(ev);
const { clientX: x } = ev;
const newWidth = (this.#width += sign * (x - prevX));
prevX = x;
if (newWidth > this.#maxWidth || newWidth < this.#minWidth) {
return;
}
sidebarStyle.width = `${newWidth.toFixed(3)}px`;
parentStyle.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
},
{ signal, capture: true }
);
window.addEventListener("blur", cancelResize, { signal });
window.addEventListener(
"pointerup",
ev => {
if (pointerMoveAC) {
cancelResize();
stopEvent(ev);
}
},
{ signal }
);
});
}
setUIManager(uiManager) { setUIManager(uiManager) {
this.#uiManager = uiManager; this.#uiManager = uiManager;
} }

View File

@ -22,6 +22,12 @@
--sidebar-box-shadow: --sidebar-box-shadow:
0 0.25px 0.75px light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)), 0 0.25px 0.75px light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
0 2px 6px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4)); 0 2px 6px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
--sidebar-border-radius: 8px;
--sidebar-padding: 5px;
--sidebar-extra-width: 0px;
--sidebar-min-width: 180px;
--sidebar-max-width: 632px;
--sidebar-width: 239px;
@media screen and (forced-colors: active) { @media screen and (forced-colors: active) {
--sidebar-bg-color: Canvas; --sidebar-bg-color: Canvas;
@ -29,9 +35,31 @@
--sidebar-box-shadow: none; --sidebar-box-shadow: none;
} }
border-radius: 8px; border-radius: var(--sidebar-border-radius);
box-shadow: var(--sidebar-box-shadow); box-shadow: var(--sidebar-box-shadow);
border: 1px solid var(--sidebar-border-color); border: 1px solid var(--sidebar-border-color);
background-color: var(--sidebar-bg-color); background-color: var(--sidebar-bg-color);
inset-block-start: calc(100% + var(--doorhanger-height) - 2px); inset-block-start: calc(100% + var(--doorhanger-height) - 2px);
padding-block: var(--sidebar-padding);
width: var(--sidebar-width);
min-width: var(--sidebar-min-width);
max-width: var(--sidebar-max-width);
.sidebarResizer {
width: 4px;
background-color: transparent;
forced-color-adjust: none;
cursor: ew-resize;
position: absolute;
inset-block: calc(var(--sidebar-padding) + var(--sidebar-border-radius));
}
&.resizing {
cursor: ew-resize;
user-select: none;
:not(.sidebarResizer) {
pointer-events: none;
}
}
} }

View File

@ -1084,7 +1084,6 @@ dialog :link {
} }
.menuContainer { .menuContainer {
width: 100%;
height: auto; height: auto;
max-height: calc( max-height: calc(
var(--viewer-container-height) - var(--toolbar-height) - var(--viewer-container-height) - var(--toolbar-height) -

View File

@ -251,6 +251,7 @@ See https://github.com/adobe-type-tools/cmap-resources
</button> </button>
<div class="editorParamsToolbar hidden menu" id="editorCommentParamsToolbar"> <div class="editorParamsToolbar hidden menu" id="editorCommentParamsToolbar">
<div id="editorCommentsSidebar" class="menuContainer comment sidebar" role="landmark" aria-labelledby="editorCommentsSidebarHeader"> <div id="editorCommentsSidebar" class="menuContainer comment sidebar" role="landmark" aria-labelledby="editorCommentsSidebarHeader">
<div id="editorCommentsSidebarResizer" class="sidebarResizer"></div>
<div id="editorCommentsSidebarHeader" role="heading" aria-level="2"> <div id="editorCommentsSidebarHeader" role="heading" aria-level="2">
<span class="commentCount"> <span class="commentCount">
<span id="editorCommentsSidebarTitle" data-l10n-id="pdfjs-editor-comments-sidebar-title" data-l10n-args='{ "count": 0 }'></span> <span id="editorCommentsSidebarTitle" data-l10n-id="pdfjs-editor-comments-sidebar-title" data-l10n-args='{ "count": 0 }'></span>

View File

@ -258,6 +258,9 @@ function getViewerConfiguration() {
editorCommentsSidebarList: document.getElementById( editorCommentsSidebarList: document.getElementById(
"editorCommentsSidebarList" "editorCommentsSidebarList"
), ),
editorCommentsSidebarResizer: document.getElementById(
"editorCommentsSidebarResizer"
),
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"), editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
editorFreeTextColor: document.getElementById("editorFreeTextColor"), editorFreeTextColor: document.getElementById("editorFreeTextColor"),
editorInkColor: document.getElementById("editorInkColor"), editorInkColor: document.getElementById("editorInkColor"),