[Editor] Make the comment sidebar resizable (bug 1990544)

This commit is contained in:
Calixte Denizet 2025-09-25 15:28:50 +02:00
parent 8448d08345
commit a372294ea3
8 changed files with 170 additions and 9 deletions

View File

@ -29,6 +29,7 @@ import {
const switchToHighlight = switchToEditor.bind(null, "Highlight");
const switchToStamp = switchToEditor.bind(null, "Stamp");
const switchToComment = switchToEditor.bind(null, "Comment");
describe("Comment", () => {
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"),
sidebar:
appConfig.annotationEditorParams?.editorCommentsSidebar || null,
sidebarResizer:
appConfig.annotationEditorParams
?.editorCommentsSidebarResizer || null,
commentsList:
appConfig.annotationEditorParams?.editorCommentsSidebarList ||
null,

View File

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

View File

@ -18,6 +18,7 @@ import {
applyOpacity,
CSSConstants,
findContrastColor,
MathClamp,
noContextMenu,
PDFDateString,
renderRichText,
@ -56,7 +57,8 @@ class CommentManager {
eventBus,
linkService,
this.#popup,
dateFormat
dateFormat,
ltr
);
this.#popup.sidebar = this.#sidebar;
CommentManager.#hasForcedColors = hasForcedColors;
@ -160,10 +162,21 @@ class CommentSidebar {
#uiManager = null;
#minWidth = 0;
#maxWidth = 0;
#initialWidth = 0;
#width = 0;
#ltr;
constructor(
{
learnMoreUrl,
sidebar,
sidebarResizer,
commentsList,
commentCount,
sidebarTitle,
@ -173,7 +186,8 @@ class CommentSidebar {
eventBus,
linkService,
popup,
dateFormat
dateFormat,
ltr
) {
this.#sidebar = sidebar;
this.#sidebarTitle = sidebarTitle;
@ -184,7 +198,16 @@ class CommentSidebar {
this.#closeButton = closeButton;
this.#popup = popup;
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", () => {
eventBus.dispatch("switchannotationeditormode", {
source: this,
@ -205,6 +228,63 @@ class CommentSidebar {
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) {
this.#uiManager = uiManager;
}

View File

@ -22,6 +22,12 @@
--sidebar-box-shadow:
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));
--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) {
--sidebar-bg-color: Canvas;
@ -29,9 +35,31 @@
--sidebar-box-shadow: none;
}
border-radius: 8px;
border-radius: var(--sidebar-border-radius);
box-shadow: var(--sidebar-box-shadow);
border: 1px solid var(--sidebar-border-color);
background-color: var(--sidebar-bg-color);
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 {
width: 100%;
height: auto;
max-height: calc(
var(--viewer-container-height) - var(--toolbar-height) -

View File

@ -251,6 +251,7 @@ See https://github.com/adobe-type-tools/cmap-resources
</button>
<div class="editorParamsToolbar hidden menu" id="editorCommentParamsToolbar">
<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">
<span class="commentCount">
<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"
),
editorCommentsSidebarResizer: document.getElementById(
"editorCommentsSidebarResizer"
),
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
editorFreeTextColor: document.getElementById("editorFreeTextColor"),
editorInkColor: document.getElementById("editorInkColor"),