Merge pull request #20249 from calixteman/new_comment_popup_2

[Editor] Add a new popup for comments (bug 1987425)
This commit is contained in:
calixteman 2025-09-15 08:27:17 +02:00 committed by GitHub
commit 3432c1933e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1337 additions and 703 deletions

View File

@ -71,6 +71,10 @@
"type": "string",
"default": ""
},
"commentLearnMoreUrl": {
"type": "string",
"default": ""
},
"enableSignatureEditor": {
"type": "boolean",
"default": false

View File

@ -414,7 +414,8 @@ pdfjs-editor-comments-sidebar-close-button =
pdfjs-editor-comments-sidebar-close-button-label = Close the sidebar
# Instructional copy to add a comment by selecting text or an annotations.
pdfjs-editor-comments-sidebar-no-comments = Add a comment by selecting text or an annotation.
pdfjs-editor-comments-sidebar-no-comments1 = See something noteworthy? Highlight it and leave a comment.
pdfjs-editor-comments-sidebar-no-comments-link = Learn more
## Alt-text dialog
@ -670,21 +671,28 @@ pdfjs-editor-edit-signature-dialog-title = Edit description
pdfjs-editor-edit-signature-update-button = Update
## Comment popup
pdfjs-editor-edit-comment-popup-button-label = Edit comment
pdfjs-editor-edit-comment-popup-button =
.title = Edit comment
pdfjs-editor-delete-comment-popup-button-label = Remove comment
pdfjs-editor-delete-comment-popup-button =
.title = Remove comment
## Edit a comment dialog
pdfjs-editor-edit-comment-actions-button-label = Actions
pdfjs-editor-edit-comment-actions-button =
.title = Actions
pdfjs-editor-edit-comment-close-button-label = Close
pdfjs-editor-edit-comment-close-button =
.title = Close
pdfjs-editor-edit-comment-actions-edit-button-label = Edit
pdfjs-editor-edit-comment-actions-delete-button-label = Delete
pdfjs-editor-edit-comment-manager-text-input =
.placeholder = Enter your comment
# An existing comment is edited
pdfjs-editor-edit-comment-dialog-title-when-editing = Edit comment
pdfjs-editor-edit-comment-manager-cancel-button = Cancel
pdfjs-editor-edit-comment-manager-save-button = Save
# No existing comment
pdfjs-editor-edit-comment-dialog-title-when-adding = Add comment
pdfjs-editor-edit-comment-dialog-text-input =
.placeholder = Start typing…
pdfjs-editor-edit-comment-dialog-cancel-button = Cancel
pdfjs-editor-edit-comment-dialog-save-button = Save
## Edit a comment button in the editor toolbar

View File

@ -334,7 +334,7 @@ class AnnotationEditorLayer {
if (editor?.annotationElementId === null) {
e.stopPropagation();
e.preventDefault();
editor.dblclick();
editor.dblclick(e);
}
},
{ signal, capture: true }

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
import { noContextMenu } from "../display_utils.js";
import { noContextMenu, stopEvent } from "../display_utils.js";
class Comment {
#commentStandaloneButton = null;
@ -34,6 +34,8 @@ class Comment {
#deleted = false;
#popupPosition = null;
constructor(editor) {
this.#editor = editor;
}
@ -42,7 +44,7 @@ class Comment {
const button = (this.#commentToolbarButton =
document.createElement("button"));
button.className = "comment";
return this.#render(button);
return this.#render(button, false);
}
renderForStandalone() {
@ -66,16 +68,87 @@ class Comment {
}
}
return this.#render(button);
return this.#render(button, true);
}
#render(comment) {
onUpdatedColor() {
if (!this.#commentStandaloneButton) {
return;
}
const color = this.#editor.commentButtonColor;
if (color) {
this.#commentStandaloneButton.style.backgroundColor = color;
}
this.#editor._uiManager.updatePopupColor(this.#editor);
}
get commentButtonWidth() {
return (
(this.#commentStandaloneButton?.getBoundingClientRect().width ?? 0) /
this.#editor.parent.boundingClientRect.width
);
}
get commentPopupPositionInLayer() {
if (this.#popupPosition) {
return this.#popupPosition;
}
if (!this.#commentStandaloneButton) {
return null;
}
const { x, y, height } =
this.#commentStandaloneButton.getBoundingClientRect();
const {
x: parentX,
y: parentY,
width: parentWidth,
height: parentHeight,
} = this.#editor.parent.boundingClientRect;
const OFFSET_UNDER_BUTTON = 2;
return [
(x - parentX) / parentWidth,
(y + height + OFFSET_UNDER_BUTTON - parentY) / parentHeight,
];
}
set commentPopupPositionInLayer(pos) {
this.#popupPosition = pos;
}
removeStandaloneCommentButton() {
this.#commentStandaloneButton?.remove();
this.#commentStandaloneButton = null;
}
removeToolbarCommentButton() {
this.#commentToolbarButton?.remove();
this.#commentToolbarButton = null;
}
setCommentButtonStates({ selected, hasPopup }) {
if (!this.#commentStandaloneButton) {
return;
}
this.#commentStandaloneButton.classList.toggle("selected", selected);
this.#commentStandaloneButton.ariaExpanded = hasPopup;
}
#render(comment, isStandalone) {
if (!this.#editor._uiManager.hasCommentManager()) {
return null;
}
comment.tabIndex = "0";
comment.setAttribute("data-l10n-id", "pdfjs-editor-edit-comment-button");
comment.ariaHasPopup = "dialog";
if (isStandalone) {
comment.ariaControls = "commentPopup";
} else {
comment.ariaControlsElements = [
this.#editor._uiManager.getCommentDialogElement(),
];
comment.setAttribute("data-l10n-id", "pdfjs-editor-edit-comment-button");
}
const signal = this.#editor._uiManager._signal;
if (!(signal instanceof AbortSignal) || signal.aborted) {
@ -83,6 +156,30 @@ class Comment {
}
comment.addEventListener("contextmenu", noContextMenu, { signal });
if (isStandalone) {
comment.addEventListener(
"focusin",
e => {
this.#editor._focusEventsAllowed = false;
stopEvent(e);
},
{
capture: true,
signal,
}
);
comment.addEventListener(
"focusout",
e => {
this.#editor._focusEventsAllowed = true;
stopEvent(e);
},
{
capture: true,
signal,
}
);
}
comment.addEventListener("pointerdown", event => event.stopPropagation(), {
signal,
});
@ -92,7 +189,7 @@ class Comment {
if (comment === this.#commentToolbarButton) {
this.edit();
} else {
this.#editor._uiManager.toggleComment(this.#editor);
this.#editor.toggleComment(/* isSelected = */ true);
}
};
comment.addEventListener("click", onClick, { capture: true, signal });
@ -107,18 +204,55 @@ class Comment {
{ signal }
);
comment.addEventListener(
"pointerenter",
() => {
this.#editor.toggleComment(
/* isSelected = */ false,
/* visibility = */ true
);
},
{ signal }
);
comment.addEventListener(
"pointerleave",
() => {
this.#editor.toggleComment(
/* isSelected = */ false,
/* visibility = */ false
);
},
{ signal }
);
return comment;
}
edit() {
const { bottom, left, right } = this.#editor.getClientDimensions();
const position = { top: bottom };
if (this.#editor._uiManager.direction === "ltr") {
position.right = right;
edit(options) {
const position = this.commentPopupPositionInLayer;
let posX, posY;
if (position) {
[posX, posY] = position;
} else {
position.left = left;
// The position is in the editor coordinates.
[posX, posY] = this.#editor.commentButtonPosition;
const { width, height, x, y } = this.#editor;
posX = x + posX * width;
posY = y + posY * height;
}
this.#editor._uiManager.editComment(this.#editor, position);
const parentDimensions = this.#editor.parent.boundingClientRect;
const {
x: parentX,
y: parentY,
width: parentWidth,
height: parentHeight,
} = parentDimensions;
this.#editor._uiManager.editComment(
this.#editor,
parentX + posX * parentWidth,
parentY + posY * parentHeight,
{ ...options, parentDimensions }
);
}
finish() {

View File

@ -98,6 +98,12 @@ class DrawingEditor extends AnnotationEditor {
this._addOutlines(params);
}
/** @inheritdoc */
onUpdatedColor() {
this._colorPicker?.update(this.color);
super.onUpdatedColor();
}
_addOutlines(params) {
if (params.drawOutlines) {
this.#createDrawOutlines(params);
@ -243,7 +249,7 @@ class DrawingEditor extends AnnotationEditor {
options.toSVGProperties()
);
if (type === this.colorType) {
this._colorPicker?.update(val);
this.onUpdatedColor();
}
};
this.addCommands({

View File

@ -22,19 +22,13 @@ import {
ColorManager,
KeyboardManager,
} from "./tools.js";
import {
applyOpacity,
CSSConstants,
findContrastColor,
noContextMenu,
stopEvent,
} from "../display_utils.js";
import {
FeatureTest,
MathClamp,
shadow,
unreachable,
} from "../../shared/util.js";
import { noContextMenu, stopEvent } from "../display_utils.js";
import { AltText } from "./alt_text.js";
import { Comment } from "./comment.js";
import { EditorToolbar } from "./toolbar.js";
@ -1098,28 +1092,28 @@ class AnnotationEditor {
await this._editToolbar.addButton(name, tool);
}
}
this._editToolbar.addButton("comment", this.addCommentButton());
if (!this.hasComment) {
this._editToolbar.addButton("comment", this.addCommentButton());
}
this._editToolbar.addButton("delete");
return this._editToolbar;
}
addCommentButtonInToolbar() {
if (!this._editToolbar) {
return;
}
this._editToolbar.addButtonBefore(
this._editToolbar?.addButtonBefore(
"comment",
this.addCommentButton(),
".deleteButton"
);
}
removeCommentButtonFromToolbar() {
this._editToolbar?.removeButton("comment");
}
removeEditToolbar() {
if (!this._editToolbar) {
return;
}
this._editToolbar.remove();
this._editToolbar?.remove();
this._editToolbar = null;
// We destroy the alt text but we don't null it because we want to be able
@ -1195,8 +1189,11 @@ class AnnotationEditor {
}
addStandaloneCommentButton() {
this.#comment ||= new Comment(this);
if (this.#commentStandaloneButton) {
this.#commentStandaloneButton.classList.remove("hidden");
return;
}
if (!this.hasComment) {
return;
}
this.#commentStandaloneButton = this.#comment.renderForStandalone();
@ -1204,12 +1201,12 @@ class AnnotationEditor {
}
removeStandaloneCommentButton() {
this.#commentStandaloneButton?.remove();
this.#comment.removeStandaloneCommentButton();
this.#commentStandaloneButton = null;
}
get commentColor() {
return null;
hideStandaloneCommentButton() {
this.#commentStandaloneButton?.classList.add("hidden");
}
get comment() {
@ -1221,7 +1218,8 @@ class AnnotationEditor {
richText,
date,
deleted,
color: this.commentColor,
color: this.getNonHCMColor(),
opacity: this.opacity ?? 1,
};
}
@ -1229,17 +1227,18 @@ class AnnotationEditor {
this.#comment ||= new Comment(this);
this.#comment.data = text;
if (this.hasComment) {
this.removeCommentButtonFromToolbar();
this.addStandaloneCommentButton();
this._uiManager.updateComment(this);
} else {
this.addCommentButtonInToolbar();
this.removeStandaloneCommentButton();
this._uiManager.removeComment(this);
}
}
setCommentData({ comment, richText }) {
if (!this.#comment) {
this.#comment = new Comment(this);
}
this.#comment ||= new Comment(this);
this.#comment.setInitialText(comment, richText);
}
@ -1253,14 +1252,20 @@ class AnnotationEditor {
);
}
async editComment() {
if (!this.#comment) {
this.#comment = new Comment(this);
}
this.#comment.edit();
async editComment(options) {
this.#comment ||= new Comment(this);
this.#comment.edit(options);
}
showComment() {}
toggleComment(isSelected, visibility = undefined) {
if (this.hasComment) {
this._uiManager.toggleComment(this, isSelected, visibility);
}
}
setSelectedCommentButton(selected) {
this.#comment.setSelectedButton(selected);
}
addComment(serialized) {
if (this.hasEditedComment) {
@ -1280,6 +1285,10 @@ class AnnotationEditor {
}
}
get parentBoundingClientRect() {
return this.parent.boundingClientRect;
}
/**
* Render this editor in a div.
* @returns {HTMLDivElement | null}
@ -1327,6 +1336,7 @@ class AnnotationEditor {
});
}
this.addStandaloneCommentButton();
this._uiManager._editorUndoBar?.hide();
return div;
@ -1467,6 +1477,11 @@ class AnnotationEditor {
e => {
if (!hasDraggingStarted) {
hasDraggingStarted = true;
this._uiManager.toggleComment(
this,
/* isSelected = */ true,
/* visibility = */ false
);
this._onStartDragging();
}
const { clientX: x, clientY: y, pointerId } = e;
@ -1632,9 +1647,25 @@ class AnnotationEditor {
return this.getRect(0, 0);
}
getNonHCMColor() {
return (
this.color &&
AnnotationEditor._colorManager.convert(
this._uiManager.getNonHCMColor(this.color)
)
);
}
/**
* The color has been changed.
*/
onUpdatedColor() {
this.#comment?.onUpdatedColor();
}
getData() {
const {
comment: { text: str, date, deleted, richText },
comment: { text: str, color, date, opacity, deleted, richText },
uid: id,
pageIndex,
creationDate,
@ -1649,6 +1680,8 @@ class AnnotationEditor {
creationDate,
modificationDate: date || modificationDate,
popupRef: !deleted,
color,
opacity,
};
}
@ -1903,18 +1936,32 @@ class AnnotationEditor {
}
get commentButtonColor() {
if (!this.color) {
return null;
}
const [r, g, b] = AnnotationEditor._colorManager.convert(
this._uiManager.getNonHCMColor(this.color)
);
return findContrastColor(
applyOpacity(r, g, b, this.opacity),
CSSConstants.commentForegroundColor
return this._uiManager.makeCommentColor(
this.getNonHCMColor(),
this.opacity
);
}
get commentPopupPosition() {
return this.#comment.commentPopupPositionInLayer;
}
set commentPopupPosition(pos) {
this.#comment.commentPopupPositionInLayer = pos;
}
get commentButtonWidth() {
return this.#comment.commentButtonWidth;
}
get elementBeforePopup() {
return this.div;
}
setCommentButtonStates(options) {
this.#comment.setCommentButtonStates(options);
}
/**
* onkeydown callback.
* @param {KeyboardEvent} event
@ -2047,6 +2094,7 @@ class AnnotationEditor {
*/
select() {
if (this.isSelected && this._editToolbar) {
this._editToolbar.show();
return;
}
this.isSelected = true;
@ -2086,6 +2134,13 @@ class AnnotationEditor {
}
this._editToolbar?.hide();
this.#altText?.toggleAltTextBadge(true);
if (this.hasComment) {
this._uiManager.toggleComment(
this,
/* isSelected = */ false,
/* visibility = */ false
);
}
}
/**
@ -2133,6 +2188,10 @@ class AnnotationEditor {
* @param {MouseEvent} event
*/
dblclick(event) {
if (event.target.nodeName === "BUTTON") {
// Avoid entering in edit mode when clicking on the comment button.
return;
}
this.enterInEditMode();
this.parent.updateToolbar({
mode: this.constructor._editorType,

View File

@ -236,14 +236,21 @@ class FreeTextEditor extends AnnotationEditor {
});
}
/** @inheritdoc */
onUpdatedColor() {
this.editorDiv.style.color = this.color;
this._colorPicker?.update(this.color);
super.onUpdatedColor();
}
/**
* Update the color and make this action undoable.
* @param {string} color
*/
#updateColor(color) {
const setColor = col => {
this.color = this.editorDiv.style.color = col;
this._colorPicker?.update(col);
this.color = col;
this.onUpdatedColor();
};
const savedColor = this.color;
this.addCommands({

View File

@ -348,6 +348,18 @@ class HighlightEditor extends AnnotationEditor {
];
}
/** @inheritdoc */
onUpdatedColor() {
this.parent?.drawLayer.updateProperties(this.#id, {
root: {
fill: this.color,
"fill-opacity": this.opacity,
},
});
this.#colorPicker?.updateColor(this.color);
super.onUpdatedColor();
}
/**
* Update the color and make this action undoable.
* @param {string} color
@ -356,13 +368,7 @@ class HighlightEditor extends AnnotationEditor {
const setColorAndOpacity = (col, opa) => {
this.color = col;
this.opacity = opa;
this.parent?.drawLayer.updateProperties(this.#id, {
root: {
fill: col,
"fill-opacity": opa,
},
});
this.#colorPicker?.updateColor(col);
this.onUpdatedColor();
};
const savedColor = this.color;
const savedOpacity = this.opacity;

View File

@ -28,6 +28,8 @@ class EditorToolbar {
#comment = null;
#commentButtonDivider = null;
#signatureDescriptionButton = null;
static #l10nRemove = null;
@ -167,11 +169,12 @@ class EditorToolbar {
return;
}
this.#addListenersToElement(button);
const divider = (this.#commentButtonDivider = this.#divider);
if (!beforeElement) {
this.#buttons.append(button, this.#divider);
this.#buttons.append(button, divider);
} else {
this.#buttons.insertBefore(button, beforeElement);
this.#buttons.insertBefore(this.#divider, beforeElement);
this.#buttons.insertBefore(divider, beforeElement);
}
this.#comment = comment;
comment.toolbar = this;
@ -194,6 +197,17 @@ class EditorToolbar {
this.#buttons.append(button, this.#divider);
}
removeButton(name) {
switch (name) {
case "comment":
this.#comment?.removeToolbarCommentButton();
this.#comment = null;
this.#commentButtonDivider?.remove();
this.#commentButtonDivider = null;
break;
}
}
async addButton(name, tool) {
switch (name) {
case "colorPicker":

View File

@ -1068,14 +1068,40 @@ class AnnotationEditorUIManager {
return !!this.#commentManager;
}
editComment(editor, position) {
this.#commentManager?.open(this, editor, position);
editComment(editor, posX, posY, options) {
this.#commentManager?.showDialog(this, editor, posX, posY, options);
}
showComment(pageIndex, uid) {
selectComment(pageIndex, uid) {
const layer = this.#allLayers.get(pageIndex);
const editor = layer?.getEditorByUID(uid);
editor?.showComment();
editor?.toggleComment(/* isSelected */ true, /* visibility */ true);
}
updateComment(editor) {
this.#commentManager?.updateComment(editor.getData());
}
updatePopupColor(editor) {
this.#commentManager?.updatePopupColor(editor);
}
removeComment(editor) {
this.#commentManager?.removeComments([editor.uid]);
}
toggleComment(editor, isSelected, visibility = undefined) {
this.#commentManager?.toggleCommentPopup(editor, isSelected, visibility);
}
makeCommentColor(color, opacity) {
return (
(color && this.#commentManager?.makeCommentColor(color, opacity)) || null
);
}
getCommentDialogElement() {
return this.#commentManager?.dialogElement || null;
}
async waitForEditorsRendered(pageNumber) {
@ -1821,22 +1847,28 @@ class AnnotationEditorUIManager {
if (this.#mode === AnnotationEditorType.POPUP) {
this.#commentManager?.hideSidebar();
for (const editor of this.#allEditors.values()) {
editor.removeStandaloneCommentButton();
}
}
this.#commentManager?.destroyPopup();
this.#mode = mode;
if (mode === AnnotationEditorType.NONE) {
this.setEditingState(false);
this.#disableAll();
for (const editor of this.#allEditors.values()) {
editor.hideStandaloneCommentButton();
}
this._editorUndoBar?.hide();
this.toggleComment(/* editor = */ null);
this.#updateModeCapability.resolve();
return;
}
for (const editor of this.#allEditors.values()) {
editor.addStandaloneCommentButton();
}
if (mode === AnnotationEditorType.SIGNATURE) {
await this.#signatureManager?.loadSignatures();
}
@ -1862,7 +1894,6 @@ class AnnotationEditorUIManager {
}
if (hasComment && !deleted) {
allComments.push(editor.getData());
editor.addStandaloneCommentButton();
}
}
for (const annotation of this.#allEditableAnnotations) {
@ -1891,7 +1922,7 @@ class AnnotationEditorUIManager {
}
for (const editor of this.#allEditors.values()) {
if (editor.annotationElementId === editId || editor.id === editId) {
if (editor.uid === editId) {
this.setSelected(editor);
if (editComment) {
editor.editComment();

View File

@ -142,7 +142,13 @@
pointer-events: none;
&.highlightEditing
:is(.freeTextEditor, .inkEditor, .stampEditor, .signatureEditor) {
:is(
.freeTextEditor,
.inkEditor,
.stampEditor,
.signatureEditor,
.commentPopup
) {
pointer-events: auto;
}
}

View File

@ -491,11 +491,16 @@ const PDFViewerApplication = {
eventBus
)
: null;
const ltr = appConfig.viewerContainer
? getComputedStyle(appConfig.viewerContainer).direction === "ltr"
: true;
const commentManager =
AppOptions.get("enableComment") && appConfig.editCommentDialog
? new CommentManager(
appConfig.editCommentDialog,
{
learnMoreUrl: AppOptions.get("commentLearnMoreUrl"),
sidebar:
appConfig.annotationEditorParams?.editorCommentsSidebar || null,
commentsList:
@ -515,7 +520,8 @@ const PDFViewerApplication = {
},
eventBus,
linkService,
overlayManager
overlayManager,
ltr
)
: null;

View File

@ -173,6 +173,14 @@ const defaultOptions = {
value: 200,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
commentLearnMoreUrl: {
/** @type {string} */
value:
typeof PDFJSDev === "undefined" || PDFJSDev.test("MOZCENTRAL")
? "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-comment"
: "",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
cursorToolOnLoad: {
/** @type {number} */
value: 0,

View File

@ -13,75 +13,23 @@
* limitations under the License.
*/
.commentPopup,
#commentManagerDialog {
width: 360px;
max-width: 100%;
min-width: 200px;
position: absolute;
padding: 8px 16px 16px;
margin: 0;
box-sizing: border-box;
border-radius: 8px;
}
#commentManagerDialog {
--comment-actions-button-icon: url(images/comment-actionsButton.svg);
--comment-close-button-icon: url(images/comment-closeButton.svg);
--default-dialog-bg-color: #ffff98;
--dialog-base-color: var(--default-dialog-bg-color);
--dialog-bg-color: color-mix(in srgb, var(--dialog-base-color), white 30%);
--dialog-border-color: var(--dialog-base-color);
--menuitem-bg-color: transparent;
--menuitem-fg-color: black;
--menuitem-hover-bg-color: #3383e7;
--menuitem-hover-fg-color: white;
--comment-text-input-bg: white;
--comment-text-input-fg: black;
--comment-text-input-border: #0060df;
--comment-focus-outline-color: #0060df;
--hover-filter: brightness(0.9);
--text-primary-color: #15141a;
--button-secondary-bg-color: #f0f0f4;
--button-secondary-active-bg-color: color-mix(
in srgb,
var(--button-secondary-bg-color),
black 14%
);
--button-secondary-hover-bg-color: color-mix(
in srgb,
var(--button-secondary-bg-color),
black 7%
);
--button-primary-bg-color: #0060df;
--button-primary-fg-color: #fbfbfe;
--button-primary-active-bg-color: #0050c0;
--button-primary-hover-bg-color: #0250bb;
--menu-bg-color: rgb(253 250 244);
--menu-button-border-color: transparent;
--menu-button-focus-outline-color: var(--comment-text-input-border);
@media screen and (forced-colors: active) {
--hover-filter: none;
--text-primary-color: CanvasText;
--button-secondary-bg-color: HighlightText;
--button-secondary-active-bg-color: HighlightText;
--button-secondary-hover-bg-color: HighlightText;
--button-primary-bg-color: ButtonText;
--button-primary-fg-color: HighlightText;
--button-primary-active-bg-color: SelectedItem;
--button-primary-hover-bg-color: SelectedItem;
--menu-button-border-color: Canvas;
--menu-button-focus-outline-color: CanvasText;
}
width: 308px;
padding: 8px 16px 16px;
overflow: visible;
position: absolute;
margin: 0;
border-radius: 4px;
border: 1px solid var(--dialog-border-color);
background: var(--dialog-bg-color);
box-shadow: 0 2px 14px 0 rgb(58 57 68 / 0.2);
.mainContainer {
width: 100%;
height: auto;
@ -97,156 +45,20 @@
#commentManagerToolbar {
width: 100%;
height: 32px;
display: flex;
justify-content: flex-end;
justify-content: flex-start;
align-items: flex-start;
gap: 8px;
align-self: stretch;
cursor: move;
> button {
color-scheme: light;
width: 24px;
height: 24px;
padding: 0;
border: none;
cursor: pointer;
&::before {
content: "";
display: inline-block;
width: 100%;
height: 100%;
mask-repeat: no-repeat;
mask-position: center;
}
&#commentActionsButton::before {
mask-image: var(--comment-actions-button-icon);
}
&#commentCloseButton::before {
mask-image: var(--comment-close-button-icon);
}
> span {
display: inline-block;
width: 0;
height: 0;
overflow: hidden;
}
}
menu {
width: max-content;
min-width: 90px;
display: flex;
flex-direction: column;
align-items: center;
gap: 1px;
padding: 5px 6px;
cursor: auto;
z-index: 1;
margin: 0;
position: absolute;
top: 8px;
right: -6.5px;
border-radius: 6px;
border: 0.5px solid #b4b4b6;
background-color: var(--menu-bg-color);
box-shadow:
1px -1px 0 0 #fff inset,
-1px 1px 0 0 #fff inset,
-1px -1px 0 0 #fff inset,
1px 1px 0 0 #fff inset,
0 0 15px 0 rgb(0 0 0 / 0.25);
button {
background-color: var(--menuitem-bg-color);
width: 100%;
height: 24px;
padding: 0;
box-sizing: border-box;
display: flex;
border: 2px solid var(--menu-button-border-color);
color: var(--menuitem-fg-color);
&:hover {
background-color: var(--menuitem-hover-bg-color);
color: var(--menuitem-hover-fg-color);
}
&:is(:focus-visible, :focus) {
outline: none;
border: 2px solid var(--menu-button-focus-outline-color);
}
&:disabled {
opacity: 0.5;
pointer-events: none;
}
span {
align-content: center;
width: 100%;
max-width: min-content;
padding-inline: 8px;
color: inherit;
text-align: start;
font: menu;
font-size: 15px;
font-weight: 400;
line-height: normal;
}
}
}
}
#commentManagerTextInput {
width: 100%;
min-height: 132px;
resize: none;
box-sizing: border-box;
margin-bottom: 12px;
border-radius: 4px;
border: 2px solid var(--comment-text-input-border);
background-color: var(--comment-text-input-bg);
color: var(--comment-text-input-fg);
}
#commentManagerTextView {
width: 100%;
height: max-content;
resize: none;
box-sizing: border-box;
margin-bottom: 12px;
border: none;
background-color: transparent;
color: var(--comment-text-input-fg);
}
.dialogButtonsGroup {
gap: 8px;
#commentManagerSaveButton:disabled {
background-color: color-mix(
in srgb,
var(--button-primary-disabled-bg-color),
transparent 50%
);
border-color: color-mix(
in srgb,
var(--button-primary-disabled-border-color),
transparent 50%
);
opacity: 1;
}
}
}
}
@ -279,12 +91,14 @@
@media screen and (forced-colors: active) {
--comment-button-bg: Canvas;
--comment-button-fg: CanvasText;
--comment-button-hover-bg: Highlight;
--comment-button-hover-fg: ButtonFace;
--comment-button-active-bg: Highlight;
--comment-button-active-fg: ButtonFace;
--comment-button-fg: ButtonText;
--comment-button-hover-bg: Canvas;
--comment-button-hover-fg: Highlight;
--comment-button-active-bg: Canvas;
--comment-button-active-fg: Highlight;
--comment-button-border-color: ButtonBorder;
--comment-button-active-border-color: ButtonBorder;
--comment-button-hover-border-color: Highlight;
--comment-button-box-shadow: none;
--comment-button-focus-outline-color: CanvasText;
--comment-button-selected-bg: ButtonBorder;
@ -358,36 +172,54 @@
}
}
.comment.sidebar {
#editorCommentsSidebar,
.commentPopup {
--comment-close-button-icon: url(images/comment-closeButton.svg);
--comment-popup-edit-button-icon: url(images/comment-popup-editButton.svg);
--comment-popup-delete-button-icon: url(images/editor-toolbar-delete.svg);
--comment-date-fg-color: light-dark(
rgb(21 20 26 / 0.69),
rgb(251 251 254 / 0.69)
);
--comment-bg-color: light-dark(#f9f9fb, #1c1b22);
--comment-hover-bg-color: light-dark(
rgb(21 20 26 / 0.14),
rgb(251 251 254 / 0.14)
);
--comment-active-bg-color: light-dark(
rgb(21 20 26 / 0.21),
rgb(251 251 254 / 0.21)
);
--comment-hover-bg-color: light-dark(#e0e0e6, #2c2b33);
--comment-active-bg-color: light-dark(#d1d1d9, #3a3944);
--comment-hover-brightness: 0.89;
--comment-hover-filter: brightness(var(--comment-hover-brightness));
--comment-active-brightness: 0.825;
--comment-active-filter: brightness(var(--comment-active-brightness));
--comment-border-color: light-dark(#f0f0f4, #52525e);
--comment-focus-outline-color: light-dark(#0062fa, #00cadb);
--comment-fg-color: light-dark(#15141a, #fbfbfe);
--comment-count-bg-color: light-dark(#e2f7ff, #00317e);
--comment-indicator-active-fg-color: light-dark(#0041a4, #a6ecf4);
--comment-indicator-active-filter: brightness(
calc(1 / var(--comment-active-brightness))
);
--comment-indicator-focus-fg-color: light-dark(#5b5b66, #fbfbfe);
--comment-indicator-hover-fg-color: light-dark(#0053cb, #61dce9);
--comment-indicator-hover-filter: brightness(
calc(1 / var(--comment-hover-brightness))
);
--comment-indicator-selected-fg-color: light-dark(#0062fa, #00cadb);
--button-comment-bg: transparent;
--button-comment-color: var(--main-color);
--button-comment-active-bg: light-dark(#cfcfd8, #5b5b66);
--button-comment-active-border: none;
--button-comment-active-color: var(--button-comment-color);
--button-comment-border: none;
--button-comment-hover-bg: light-dark(#e0e0e6, #52525e);
--button-comment-hover-color: var(--button-comment-color);
@media screen and (forced-colors: active) {
--comment-date-fg-color: CanvasText;
--comment-bg-color: Canvas;
--comment-hover-bg-color: SelectedItemText;
--comment-hover-filter: none;
--comment-active-bg-color: SelectedItemText;
--comment-active-filter: none;
--comment-border-color: CanvasText;
--comment-fg-color: CanvasText;
--comment-count-bg-color: Canvas;
@ -395,8 +227,17 @@
--comment-indicator-focus-fg-color: CanvasText;
--comment-indicator-hover-fg-color: CanvasText;
--comment-indicator-selected-fg-color: SelectedItem;
--button-comment-bg: HighlightText;
--button-comment-color: ButtonText;
--button-comment-active-bg: ButtonText;
--button-comment-active-color: HighlightText;
--button-comment-border: 1px solid ButtonText;
--button-comment-hover-bg: Highlight;
--button-comment-hover-color: HighlightText;
}
}
#editorCommentsSidebar {
display: flex;
width: 239px;
height: auto;
@ -449,6 +290,7 @@
width: 32px;
height: 32px;
padding: 8px;
border-radius: 4px;
border: none;
background: none;
cursor: pointer;
@ -472,6 +314,10 @@
background-color: var(--comment-active-bg-color);
}
&:focus-visible {
outline: var(--focus-ring-outline);
}
> span {
display: inline-block;
width: 0;
@ -483,18 +329,18 @@
#editorCommentsSidebarListContainer {
overflow: scroll;
width: 100%;
#editorCommentsSidebarList {
display: flex;
width: auto;
padding: 1px 16px 0;
padding: 4px 16px;
gap: 10px;
flex: 1 0 0;
align-self: stretch;
align-items: flex-start;
flex-direction: column;
list-style-type: none;
overflow: scroll;
.sidebarComment {
display: flex;
@ -511,20 +357,28 @@
&:not(.noComments) {
&:hover {
background-color: var(--comment-hover-bg-color);
@media screen and (forced-colors: active) {
background-color: var(--comment-hover-bg-color);
}
filter: var(--comment-hover-filter);
time::after {
display: inline-block;
background-color: var(--comment-indicator-hover-fg-color);
filter: var(--comment-indicator-hover-filter);
}
}
&:active {
background-color: var(--comment-active-bg-color);
@media screen and (forced-colors: active) {
background-color: var(--comment-active-bg-color);
}
filter: var(--comment-active-filter);
time::after {
display: inline-block;
background-color: var(--comment-indicator-active-fg-color);
filter: var(--comment-indicator-active-filter);
}
}
@ -565,12 +419,34 @@
-webkit-line-clamp: 2;
overflow: hidden;
overflow-wrap: break-word;
.richText {
--total-scale-factor: 1.5;
}
}
&.noComments .sidebarCommentText {
max-height: fit-content;
-webkit-line-clamp: unset;
user-select: none;
&.noComments {
.sidebarCommentText {
max-height: fit-content;
-webkit-line-clamp: unset;
user-select: none;
}
a {
font: menu;
font-style: normal;
font-weight: 400;
line-height: normal;
font-size: 15px;
width: 100%;
height: auto;
overflow-wrap: break-word;
margin-block-start: 15px;
&:focus-visible {
outline: var(--focus-ring-outline);
}
}
}
time {
@ -600,3 +476,163 @@
}
}
}
.commentPopup {
color-scheme: light dark;
--divider-color: light-dark(#cfcfd8, #3a3944);
--comment-shadow:
0 0.5px 2px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
0 4px 16px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
@media screen and (forced-colors: active) {
--divider-color: CanvasText;
--comment-shadow: none;
}
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
z-index: 100001; /* above selected annotation editor */
pointer-events: auto;
border: 0.5px solid var(--comment-border-color);
background: var(--comment-bg-color);
box-shadow: var(--comment-shadow);
&:focus-visible {
outline: none;
}
&.dragging {
cursor: move !important;
* {
cursor: move !important;
}
button {
pointer-events: none !important;
}
}
&:not(.selected) .commentPopupButtons {
visibility: hidden !important;
}
hr {
width: 100%;
height: 1px;
border: none;
border-top: 1px solid var(--divider-color);
margin: 0;
padding: 0;
}
.commentPopupTop {
display: flex;
width: 100%;
height: auto;
padding-bottom: 4px;
justify-content: space-between;
align-items: center;
align-self: stretch;
cursor: move;
user-select: none;
.commentPopupTime {
font: menu;
font-style: normal;
font-weight: 400;
line-height: normal;
font-size: 13px;
color: var(--comment-date-fg-color);
}
.commentPopupButtons {
display: flex;
align-items: center;
gap: 2px;
cursor: default;
> button {
width: 32px;
height: 32px;
padding: 8px;
border: none;
border-radius: 4px;
background-color: var(--button-comment-bg);
color: var(--button-comment-color);
&:hover {
background-color: var(--button-comment-hover-bg);
}
&:active {
border: var(--button-comment-active-border);
background-color: var(--button-comment-active-bg);
color: var(--button-comment-active-color);
&::before {
background-color: var(--button-comment-active-color);
}
}
&:focus-visible {
background-color: var(--button-comment-hover-bg);
outline: 2px solid var(--comment-focus-outline-color);
outline-offset: 0;
}
&::before {
content: "";
display: inline-block;
width: 100%;
height: 100%;
mask-repeat: no-repeat;
mask-position: center;
}
&.commentPopupEdit::before {
mask-image: var(--comment-popup-edit-button-icon);
}
&.commentPopupDelete::before {
mask-image: var(--comment-popup-delete-button-icon);
}
}
}
}
.commentPopupText {
width: 100%;
height: auto;
font: menu;
font-style: normal;
font-weight: 400;
line-height: normal;
font-size: 15px;
color: var(--comment-fg-color);
}
}
.commentPopupText,
.sidebarCommentText .richText {
margin-block: 0;
p:first-of-type {
margin-block: 0;
}
> * {
white-space: pre-wrap;
font-size: max(15px, calc(10px * var(--total-scale-factor)));
overflow-wrap: break-word;
}
span {
color: var(--comment-fg-color) !important;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.0189877 13.6645L0.612989 10.4635C0.687989 10.0545 0.884989 9.6805 1.18099 9.3825L9.98199 0.5805C10.756 -0.1925 12.015 -0.1945 12.792 0.5805L14.42 2.2085C15.194 2.9835 15.194 4.2435 14.42 5.0185L5.61599 13.8215C5.31999 14.1165 4.94599 14.3125 4.53799 14.3875L1.33599 14.9815C1.26599 14.9935 1.19799 15.0005 1.12999 15.0005C0.832989 15.0005 0.544988 14.8835 0.330988 14.6695C0.0679874 14.4055 -0.0490122 14.0305 0.0189877 13.6645ZM12.472 5.1965L13.632 4.0365L13.631 3.1885L11.811 1.3675L10.963 1.3685L9.80299 2.5285L12.472 5.1965ZM4.31099 13.1585C4.47099 13.1285 4.61799 13.0515 4.73399 12.9345L11.587 6.0815L8.91899 3.4135L2.06599 10.2655C1.94899 10.3835 1.87199 10.5305 1.84099 10.6915L1.36699 13.2485L1.75199 13.6335L4.31099 13.1585Z" fill="black"/>
</svg>
<!--path d="M0.0189877 14.1645L0.612989 10.9635C0.687989 10.5545 0.884989 10.1805 1.18099 9.8825L9.98199 1.0805C10.756 0.3075 12.015 0.3055 12.792 1.0805L14.42 2.7085C15.194 3.4835 15.194 4.7435 14.42 5.5185L5.61599 14.3215C5.31999 14.6165 4.94599 14.8125 4.53799 14.8875L1.33599 15.4815C1.26599 15.4935 1.19799 15.5005 1.12999 15.5005C0.832989 15.5005 0.544988 15.3835 0.330988 15.1695C0.0679874 14.9055 -0.0490122 14.5305 0.0189877 14.1645ZM12.472 5.6965L13.632 4.5365L13.631 3.6885L11.811 1.8675L10.963 1.8685L9.80299 3.0285L12.472 5.6965ZM4.31099 13.6585C4.47099 13.6285 4.61799 13.5515 4.73399 13.4345L11.587 6.5815L8.91899 3.9135L2.06599 10.7655C1.94899 10.8835 1.87199 11.0305 1.84099 11.1915L1.36699 13.7485L1.75199 14.1335L4.31099 13.6585Z" fill="black"/-->

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1057,6 +1057,7 @@ class PDFViewer {
enableHWA: this.#enableHWA,
enableAutoLinking: this.#enableAutoLinking,
minDurationToUpdateCanvas: this.#minDurationToUpdateCanvas,
commentManager: this.#commentManager,
});
this._pages.push(pageView);
}

View File

@ -249,7 +249,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<button id="editorCommentButton" class="toolbarButton" type="button" tabindex="0" disabled="disabled" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorCommentParamsToolbar" data-l10n-id="pdfjs-editor-comment-button">
<span data-l10n-id="pdfjs-editor-comment-button-label"></span>
</button>
<div class="editorParamsToolbar sidebar 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="editorCommentsSidebarHeader" role="heading" aria-level="2">
<span class="commentCount">
@ -260,7 +260,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<span data-l10n-id="pdfjs-editor-comments-sidebar-close-button-label"></span>
</button>
</div>
<div id="editorCommentsSidebarListContainer">
<div id="editorCommentsSidebarListContainer" tabindex="-1">
<ul id="editorCommentsSidebarList"></ul>
</div>
</div>
@ -794,30 +794,16 @@ See https://github.com/adobe-type-tools/cmap-resources
<dialog class="dialog commentManager" id="commentManagerDialog">
<div class="mainContainer">
<div id="commentManagerToolbar">
<button id="commentActionsButton" class="toolbarButton" type="button" aria-expanded="false" aria-haspopup="true" aria-controls="commentActionsMenu" tabindex="0" data-l10n-id="pdfjs-editor-edit-comment-actions-button">
<span data-l10n-id="pdfjs-editor-edit-comment-actions-button-label"></span>
</button>
<menu class="hidden" role="menu" id="commentActionsMenu">
<button id="commentActionsEditButton" role="menuitem" type="button" tabindex="0">
<span data-l10n-id="pdfjs-editor-edit-comment-actions-edit-button-label"></span>
</button>
<button id="commentActionsDeleteButton" role="menuitem" type="button" tabindex="0">
<span data-l10n-id="pdfjs-editor-edit-comment-actions-delete-button-label"></span>
</button>
</menu>
<button id="commentCloseButton" class="toolbarButton" type="button" tabindex="0" data-l10n-id="pdfjs-editor-edit-comment-close-button">
<span data-l10n-id="pdfjs-editor-edit-comment-close-button-label"></span>
</button>
<div class="title" id="commentManagerToolbar">
<span id="commentManagerTitle" role="sectionhead" data-l10n-id="pdfjs-editor-edit-comment-dialog-title-when-adding" tabindex="0"></span>
</div>
<textarea class="hidden" id="commentManagerTextInput" data-l10n-id="pdfjs-editor-edit-comment-manager-text-input"></textarea>
<div class="hidden" id="commentManagerTextView"></div>
<textarea id="commentManagerTextInput" data-l10n-id="pdfjs-editor-edit-comment-dialog-text-input"></textarea>
<div class="dialogButtonsGroup">
<button id="commentManagerCancelButton" type="button" class="secondaryButton" tabindex="0">
<span data-l10n-id="pdfjs-editor-edit-comment-manager-cancel-button"></span>
<span data-l10n-id="pdfjs-editor-edit-comment-dialog-cancel-button"></span>
</button>
<button id="commentManagerSaveButton" type="button" class="primaryButton" disabled tabindex="0">
<span data-l10n-id="pdfjs-editor-edit-comment-manager-save-button"></span>
<span data-l10n-id="pdfjs-editor-edit-comment-dialog-save-button"></span>
</button>
</div>
</div>

View File

@ -282,13 +282,8 @@ function getViewerConfiguration() {
editCommentDialog: {
dialog: document.getElementById("commentManagerDialog"),
toolbar: document.getElementById("commentManagerToolbar"),
actions: document.getElementById("commentActionsButton"),
menu: document.getElementById("commentActionsMenu"),
editMenuItem: document.getElementById("commentActionsEditButton"),
deleteMenuItem: document.getElementById("commentActionsDeleteButton"),
closeButton: document.getElementById("commentCloseButton"),
title: document.getElementById("commentManagerTitle"),
textInput: document.getElementById("commentManagerTextInput"),
textView: document.getElementById("commentManagerTextView"),
cancelButton: document.getElementById("commentManagerCancelButton"),
saveButton: document.getElementById("commentManagerSaveButton"),
},