mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-02-08 00:21:11 +01:00
Merge pull request #20586 from marco-c/commentundo
Bug 1999154 - Add the ability to undo comment deletion
This commit is contained in:
commit
48df8a5ea2
@ -561,6 +561,7 @@ pdfjs-editor-undo-bar-message-freetext = Text removed
|
||||
pdfjs-editor-undo-bar-message-ink = Drawing removed
|
||||
pdfjs-editor-undo-bar-message-stamp = Image removed
|
||||
pdfjs-editor-undo-bar-message-signature = Signature removed
|
||||
pdfjs-editor-undo-bar-message-comment = Comment removed
|
||||
# Variables:
|
||||
# $count (Number) - the number of removed annotations.
|
||||
pdfjs-editor-undo-bar-message-multiple =
|
||||
|
||||
@ -314,6 +314,20 @@ class Comment {
|
||||
this.#deleted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the comment data (used for undo).
|
||||
* @param {Object} data - The comment data to restore.
|
||||
* @param {string} data.text - The comment text.
|
||||
* @param {string|null} data.richText - The rich text content.
|
||||
* @param {Date|null} data.date - The original date.
|
||||
*/
|
||||
restoreData({ text, richText, date }) {
|
||||
this.#text = text;
|
||||
this.#richText = richText;
|
||||
this.#date = date;
|
||||
this.#deleted = false;
|
||||
}
|
||||
|
||||
setInitialText(text, richText = null) {
|
||||
this.#initialText = text;
|
||||
this.data = text;
|
||||
|
||||
@ -1220,9 +1220,14 @@ class AnnotationEditor {
|
||||
};
|
||||
}
|
||||
|
||||
set comment(text) {
|
||||
set comment(value) {
|
||||
this.#comment ||= new Comment(this);
|
||||
this.#comment.data = text;
|
||||
if (typeof value === "object" && value !== null) {
|
||||
// Restore full comment data (used for undo).
|
||||
this.#comment.restoreData(value);
|
||||
} else {
|
||||
this.#comment.data = value;
|
||||
}
|
||||
if (this.hasComment) {
|
||||
this.removeCommentButtonFromToolbar();
|
||||
this.addStandaloneCommentButton();
|
||||
|
||||
@ -1177,6 +1177,23 @@ class AnnotationEditorUIManager {
|
||||
this.#commentManager?.removeComments([editor.uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment from an editor with undo support.
|
||||
* @param {AnnotationEditor} editor - The editor whose comment to delete.
|
||||
* @param {Object} savedData - The comment data to save for undo.
|
||||
*/
|
||||
deleteComment(editor, savedData) {
|
||||
const undo = () => {
|
||||
editor.comment = savedData;
|
||||
};
|
||||
const cmd = () => {
|
||||
this._editorUndoBar?.show(undo, "comment");
|
||||
this.toggleComment(/* editor = */ null);
|
||||
editor.comment = null;
|
||||
};
|
||||
this.addCommands({ cmd, undo, mustExec: true });
|
||||
}
|
||||
|
||||
toggleComment(editor, isSelected, visibility = undefined) {
|
||||
this.#commentManager?.toggleCommentPopup(editor, isSelected, visibility);
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ import {
|
||||
highlightSpan,
|
||||
kbModifierDown,
|
||||
kbModifierUp,
|
||||
kbRedo,
|
||||
kbUndo,
|
||||
loadAndWait,
|
||||
scrollIntoView,
|
||||
selectEditor,
|
||||
@ -965,4 +967,213 @@ describe("Comment", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Undo deletion popup for comments (bug 1999154)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
".annotationEditorLayer",
|
||||
"page-fit",
|
||||
null,
|
||||
{ enableComment: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that deleting a comment can be undone using the undo button", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
await highlightSpan(page, 1, "Abstract");
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const comment = "Test comment for undo";
|
||||
await editComment(page, editorSelector, comment);
|
||||
|
||||
// Stay in highlight mode - don't disable it
|
||||
await waitAndClick(
|
||||
page,
|
||||
`${editorSelector} .annotationCommentButton`
|
||||
);
|
||||
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
|
||||
// Capture the date before deletion
|
||||
const dateBefore = await page.evaluate(
|
||||
() =>
|
||||
document.querySelector("#commentPopup .commentPopupTime")
|
||||
?.textContent
|
||||
);
|
||||
|
||||
await waitAndClick(page, "button.commentPopupDelete");
|
||||
|
||||
await page.waitForSelector("#editorUndoBar", { visible: true });
|
||||
await page.waitForSelector("#editorUndoBarUndoButton", {
|
||||
visible: true,
|
||||
});
|
||||
await page.click("#editorUndoBarUndoButton");
|
||||
|
||||
// Check that the comment is restored by hovering to show the popup
|
||||
await page.hover(`${editorSelector} .annotationCommentButton`);
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
const popupText = await page.evaluate(
|
||||
() =>
|
||||
document.querySelector("#commentPopup .commentPopupText")
|
||||
?.textContent
|
||||
);
|
||||
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
||||
|
||||
// Check that the date is preserved
|
||||
const dateAfter = await page.evaluate(
|
||||
() =>
|
||||
document.querySelector("#commentPopup .commentPopupTime")
|
||||
?.textContent
|
||||
);
|
||||
expect(dateAfter)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(dateBefore);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the undo deletion popup displays 'Comment removed' message", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
await highlightSpan(page, 1, "Abstract");
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await editComment(page, editorSelector, "Test comment");
|
||||
|
||||
// Stay in highlight mode - don't disable it
|
||||
await waitAndClick(
|
||||
page,
|
||||
`${editorSelector} .annotationCommentButton`
|
||||
);
|
||||
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
await waitAndClick(page, "button.commentPopupDelete");
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
const messageElement = document.querySelector(
|
||||
"#editorUndoBarMessage"
|
||||
);
|
||||
return messageElement && messageElement.textContent.trim() !== "";
|
||||
});
|
||||
const message = await page.waitForSelector("#editorUndoBarMessage");
|
||||
const messageText = await page.evaluate(
|
||||
el => el.textContent,
|
||||
message
|
||||
);
|
||||
expect(messageText)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toContain("Comment removed");
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the undo bar closes when clicking the close button", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
await highlightSpan(page, 1, "Abstract");
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await editComment(page, editorSelector, "Test comment");
|
||||
|
||||
// Stay in highlight mode - don't disable it
|
||||
await waitAndClick(
|
||||
page,
|
||||
`${editorSelector} .annotationCommentButton`
|
||||
);
|
||||
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
await waitAndClick(page, "button.commentPopupDelete");
|
||||
|
||||
await page.waitForSelector("#editorUndoBar", { visible: true });
|
||||
await waitAndClick(page, "#editorUndoBarCloseButton");
|
||||
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that deleting a comment can be undone using Ctrl+Z", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
await highlightSpan(page, 1, "Abstract");
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const comment = "Test comment for Ctrl+Z undo";
|
||||
await editComment(page, editorSelector, comment);
|
||||
|
||||
// Stay in highlight mode - don't disable it
|
||||
await waitAndClick(
|
||||
page,
|
||||
`${editorSelector} .annotationCommentButton`
|
||||
);
|
||||
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
await waitAndClick(page, "button.commentPopupDelete");
|
||||
|
||||
await page.waitForSelector("#editorUndoBar", { visible: true });
|
||||
|
||||
// Use Ctrl+Z to undo
|
||||
await kbUndo(page);
|
||||
|
||||
// The undo bar should be hidden after undo
|
||||
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||
|
||||
// Check that the comment is restored by hovering to show the popup
|
||||
await page.hover(`${editorSelector} .annotationCommentButton`);
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
const popupText = await page.evaluate(
|
||||
() =>
|
||||
document.querySelector("#commentPopup .commentPopupText")
|
||||
?.textContent
|
||||
);
|
||||
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the comment popup is hidden after redo", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
await highlightSpan(page, 1, "Abstract");
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const comment = "Test comment for redo";
|
||||
await editComment(page, editorSelector, comment);
|
||||
|
||||
// Show the popup by clicking the comment button
|
||||
await waitAndClick(
|
||||
page,
|
||||
`${editorSelector} .annotationCommentButton`
|
||||
);
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
|
||||
// Delete the comment
|
||||
await waitAndClick(page, "button.commentPopupDelete");
|
||||
await page.waitForSelector("#editorUndoBar", { visible: true });
|
||||
|
||||
// Undo the deletion
|
||||
await kbUndo(page);
|
||||
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||
|
||||
// Show the popup again by clicking the comment button
|
||||
await waitAndClick(
|
||||
page,
|
||||
`${editorSelector} .annotationCommentButton`
|
||||
);
|
||||
await page.waitForSelector("#commentPopup", { visible: true });
|
||||
|
||||
// Redo the deletion - popup should be hidden
|
||||
await kbRedo(page);
|
||||
await page.waitForSelector("#commentPopup", { hidden: true });
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -982,9 +982,15 @@ class CommentPopup {
|
||||
},
|
||||
},
|
||||
});
|
||||
this.#editor.comment = null;
|
||||
this.#editor.focus();
|
||||
const editor = this.#editor;
|
||||
const savedData = editor.comment;
|
||||
this.destroy();
|
||||
if (savedData?.text) {
|
||||
editor._uiManager.deleteComment(editor, savedData);
|
||||
} else {
|
||||
editor.comment = null;
|
||||
}
|
||||
editor.focus();
|
||||
});
|
||||
del.addEventListener("contextmenu", noContextMenu);
|
||||
buttons.append(edit, del);
|
||||
|
||||
@ -40,6 +40,7 @@ class EditorUndoBar {
|
||||
stamp: "pdfjs-editor-undo-bar-message-stamp",
|
||||
ink: "pdfjs-editor-undo-bar-message-ink",
|
||||
signature: "pdfjs-editor-undo-bar-message-signature",
|
||||
comment: "pdfjs-editor-undo-bar-message-comment",
|
||||
_multiple: "pdfjs-editor-undo-bar-message-multiple",
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user