330 lines
7.8 KiB
JavaScript
330 lines
7.8 KiB
JavaScript
/* Copyright 2025 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { noContextMenu, stopEvent } from "../display_utils.js";
|
|
|
|
class Comment {
|
|
#commentStandaloneButton = null;
|
|
|
|
#commentToolbarButton = null;
|
|
|
|
#commentWasFromKeyBoard = false;
|
|
|
|
#editor = null;
|
|
|
|
#initialText = null;
|
|
|
|
#richText = null;
|
|
|
|
#text = null;
|
|
|
|
#date = null;
|
|
|
|
#deleted = false;
|
|
|
|
#popupPosition = null;
|
|
|
|
constructor(editor) {
|
|
this.#editor = editor;
|
|
}
|
|
|
|
renderForToolbar() {
|
|
const button = (this.#commentToolbarButton =
|
|
document.createElement("button"));
|
|
button.className = "comment";
|
|
return this.#render(button, false);
|
|
}
|
|
|
|
renderForStandalone() {
|
|
const button = (this.#commentStandaloneButton =
|
|
document.createElement("button"));
|
|
button.className = "annotationCommentButton";
|
|
|
|
const position = this.#editor.commentButtonPosition;
|
|
if (position) {
|
|
const { style } = button;
|
|
style.insetInlineEnd = `calc(${
|
|
100 *
|
|
(this.#editor._uiManager.direction === "ltr"
|
|
? 1 - position[0]
|
|
: position[0])
|
|
}% - var(--comment-button-dim))`;
|
|
style.top = `calc(${100 * position[1]}% - var(--comment-button-dim))`;
|
|
const color = this.#editor.commentButtonColor;
|
|
if (color) {
|
|
style.backgroundColor = color;
|
|
}
|
|
}
|
|
|
|
return this.#render(button, true);
|
|
}
|
|
|
|
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;
|
|
return [(x - parentX) / parentWidth, (y + height - 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.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) {
|
|
return 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,
|
|
});
|
|
|
|
const onClick = event => {
|
|
event.preventDefault();
|
|
if (comment === this.#commentToolbarButton) {
|
|
this.edit();
|
|
} else {
|
|
this.#editor.toggleComment(/* isSelected = */ true);
|
|
}
|
|
};
|
|
comment.addEventListener("click", onClick, { capture: true, signal });
|
|
comment.addEventListener(
|
|
"keydown",
|
|
event => {
|
|
if (event.target === comment && event.key === "Enter") {
|
|
this.#commentWasFromKeyBoard = true;
|
|
onClick(event);
|
|
}
|
|
},
|
|
{ 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(options) {
|
|
const position = this.commentPopupPositionInLayer;
|
|
let posX, posY;
|
|
if (position) {
|
|
[posX, posY] = position;
|
|
} else {
|
|
// 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;
|
|
}
|
|
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() {
|
|
if (!this.#commentToolbarButton) {
|
|
return;
|
|
}
|
|
this.#commentToolbarButton.focus({
|
|
focusVisible: this.#commentWasFromKeyBoard,
|
|
});
|
|
this.#commentWasFromKeyBoard = false;
|
|
}
|
|
|
|
isDeleted() {
|
|
return this.#deleted || this.#text === "";
|
|
}
|
|
|
|
isEmpty() {
|
|
return this.#text === null;
|
|
}
|
|
|
|
hasBeenEdited() {
|
|
return this.isDeleted() || this.#text !== this.#initialText;
|
|
}
|
|
|
|
serialize() {
|
|
return this.data;
|
|
}
|
|
|
|
get data() {
|
|
return {
|
|
text: this.#text,
|
|
richText: this.#richText,
|
|
date: this.#date,
|
|
deleted: this.isDeleted(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Set the comment data.
|
|
*/
|
|
set data(text) {
|
|
if (text !== this.#text) {
|
|
this.#richText = null;
|
|
}
|
|
if (text === null) {
|
|
this.#text = "";
|
|
this.#deleted = true;
|
|
return;
|
|
}
|
|
this.#text = text;
|
|
this.#date = new Date();
|
|
this.#deleted = false;
|
|
}
|
|
|
|
setInitialText(text, richText = null) {
|
|
this.#initialText = text;
|
|
this.data = text;
|
|
this.#date = null;
|
|
this.#richText = richText;
|
|
}
|
|
|
|
shown() {}
|
|
|
|
destroy() {
|
|
this.#commentToolbarButton?.remove();
|
|
this.#commentToolbarButton = null;
|
|
this.#commentStandaloneButton?.remove();
|
|
this.#commentStandaloneButton = null;
|
|
this.#text = "";
|
|
this.#richText = null;
|
|
this.#date = null;
|
|
this.#editor = null;
|
|
this.#commentWasFromKeyBoard = false;
|
|
this.#deleted = false;
|
|
}
|
|
}
|
|
|
|
export { Comment };
|