browser/src/canvas/sections/CommentListSection.ts | 17 +++
browser/src/canvas/sections/CommentMarkerSubSection.ts | 3
browser/src/canvas/sections/CommentSection.ts | 50 ++++++----
cypress_test/integration_tests/multiuser/writer/annotation_spec.js | 18 +++
4 files changed, 69 insertions(+), 19 deletions(-)
New commits:
commit 7a94e7f5ba4cae6c46d92180d01f7820d7e5f86a
Author: Jaume Pujantell <
jaume.p...@collabora.com>
AuthorDate: Thu Apr 23 10:48:18 2026 +0000
Commit: Miklos Vajna <
vmi...@collabora.com>
CommitDate: Tue May 19 11:02:40 2026 +0000
comments: only the author can modify
Before this change, the rule "only the author can modify their comment"
was active only in the readonly-with-comments mode. In normal editing
mode, any user could edit or delete any comment.
Now the comment rules are:
- Modify and promote: only the author of the comment.
- Remove, resolve and unresolve: the author, or anyone who can edit
the document.
The context menu hides the entries that the current user is not allowed
to use.
Signed-off-by: Jaume Pujantell <
jaume.p...@collabora.com>
Change-Id: I0c9f6cbf66adcdc56e69d0f57d008fa184f9aa8d
Reviewed-on:
https://gerrit.collaboraoffice.com/c/online/+/1593
Reviewed-by: Miklos Vajna <
vmi...@collabora.com>
Tested-by: Jenkins CPCI <
rel...@collaboraoffice.com>
diff --git a/browser/src/canvas/sections/CommentListSection.ts b/browser/src/canvas/sections/CommentListSection.ts
index 29ecf02c3b75..161ddd14bbd8 100644
--- a/browser/src/canvas/sections/CommentListSection.ts
+++ b/browser/src/canvas/sections/CommentListSection.ts
@@ -1137,6 +1137,8 @@ export class CommentSection extends CanvasSectionObject {
}
public modify (annotation: any): void {
+ if (!annotation.isAuthor())
+ return;
if (cool.Comment.isAnyEdit()) {
this.navigateAndFocusComment(cool.Comment.isAnyEdit());
return;
@@ -1453,6 +1455,10 @@ export class CommentSection extends CanvasSectionObject {
}
public remove (id: any): void {
+ var removedComment = this.getComment(id);
+ if (removedComment && !removedComment.canRemove())
+ return;
+
const comment = {
Id: {
type: 'string',
@@ -1460,7 +1466,6 @@ export class CommentSection extends CanvasSectionObject {
}
};
- var removedComment = this.getComment(id);
if (removedComment) {
removedComment.sectionProperties.selfRemoved = true;
}
@@ -1482,6 +1487,10 @@ export class CommentSection extends CanvasSectionObject {
}
public removeThread (id: any): void {
+ const rootComment = this.getComment(id);
+ if (rootComment && !rootComment.canRemove())
+ return;
+
const comment = {
Id: {
type: 'string',
@@ -1494,6 +1503,8 @@ export class CommentSection extends CanvasSectionObject {
}
public resolve (annotation: any): void {
+ if (!annotation.canModerate())
+ return;
const comment = {
Id: {
type: 'string',
@@ -1504,6 +1515,8 @@ export class CommentSection extends CanvasSectionObject {
}
public resolveThread (annotation: any): void {
+ if (!annotation.canModerate())
+ return;
const comment = {
Id: {
type: 'string',
@@ -1514,6 +1527,8 @@ export class CommentSection extends CanvasSectionObject {
}
public promote(annotation: any): void {
+ if (!annotation.isAuthor())
+ return;
const comment = {
Id: {
type: 'string',
diff --git a/browser/src/canvas/sections/CommentMarkerSubSection.ts b/browser/src/canvas/sections/CommentMarkerSubSection.ts
index 5bd58a313d35..916c2680b803 100644
--- a/browser/src/canvas/sections/CommentMarkerSubSection.ts
+++ b/browser/src/canvas/sections/CommentMarkerSubSection.ts
@@ -69,6 +69,7 @@ class CommentMarkerSubSection extends HTMLObjectSection {
e: MouseEvent,
): void {
if (this.sectionProperties.parentSection === null) return;
+ if (!this.sectionProperties.parentSection.isAuthor()) return;
if (app.sectionContainer.isDraggingSomething()) {
if (this.sectionProperties.parent === null) return;
@@ -86,6 +87,8 @@ class CommentMarkerSubSection extends HTMLObjectSection {
onDragEnd(): void {
this.sectionProperties.dragStartPosition = null;
+ if (!this.sectionProperties.parentSection.isAuthor()) return;
+
const twips = [
this.position[0] * app.pixelsToTwips,
this.position[1] * app.pixelsToTwips,
diff --git a/browser/src/canvas/sections/CommentSection.ts b/browser/src/canvas/sections/CommentSection.ts
index fb077490f6ed..60e1db5da432 100644
--- a/browser/src/canvas/sections/CommentSection.ts
+++ b/browser/src/canvas/sections/CommentSection.ts
@@ -368,13 +368,15 @@ export class Comment extends CanvasSectionObject {
private createMenu (): void {
var tdMenu = window.L.DomUtil.create('td', 'cool-annotation-menubar', this.sectionProperties.authorRow);
this.sectionProperties.menuBarCell = tdMenu;
- const edit = window.L.DomUtil.create('div', 'cool-annotation-menu-edit', tdMenu);
-
edit.id = 'comment-annotation-menu-edit-' +
this.sectionProperties.data.id;
- edit.tabIndex = 0;
- edit.onclick = this.onEditComment.bind(this);
- edit.onkeypress = this.editOnKeyPress.bind(this);
- edit.dataset.title = Comment.editCommentLabel;
- edit.setAttribute('aria-label', Comment.editCommentLabel);
+ if (this.isAuthor()) {
+ const edit = window.L.DomUtil.create('div', 'cool-annotation-menu-edit', tdMenu);
+
edit.id = 'comment-annotation-menu-edit-' +
this.sectionProperties.data.id;
+ edit.tabIndex = 0;
+ edit.onclick = this.onEditComment.bind(this);
+ edit.onkeypress = this.editOnKeyPress.bind(this);
+ edit.dataset.title = Comment.editCommentLabel;
+ edit.setAttribute('aria-label', Comment.editCommentLabel);
+ }
this.sectionProperties.menu = window.L.DomUtil.create('div', this.sectionProperties.data.trackchange ? 'cool-annotation-menu-redline' : 'cool-annotation-menu', tdMenu);
this.sectionProperties.menu.id = 'comment-annotation-menu-' +
this.sectionProperties.data.id;
@@ -1089,6 +1091,18 @@ export class Comment extends CanvasSectionObject {
this.hidden = true;
}
+ public isAuthor(): boolean {
+ return this.map.getViewName(app.map._docLayer._viewId) === this.sectionProperties.data.author;
+ }
+
+ public canRemove(): boolean {
+ return this.isAuthor() || !this.map.isReadOnlyMode();
+ }
+
+ public canModerate(): boolean {
+ return this.isAuthor() || app.isCommentEditingAllowed();
+ }
+
// check if this is "our" autosaved comment
// core is not aware it's autosaved one so use this simplified detection based on content
public isAutoSaved (): boolean {
@@ -1096,8 +1110,7 @@ export class Comment extends CanvasSectionObject {
if (!autoSavedComment)
return false;
- var authorMatch = this.sectionProperties.data.author === this.map.getViewName(app.map._docLayer._viewId);
- return authorMatch;
+ return this.isAuthor();
}
public hide (): void {
@@ -1159,36 +1172,37 @@ export class Comment extends CanvasSectionObject {
if (data.trackchange) {
entries.push({ text: _('Comment'), type: 'action', id: 'modify', pos: String(pos++) });
} else {
- const blockChangeFromDifferentAuthor = this.map.isReadOnlyMode()
- && docLayer._docType === 'text'
- && this.map.getViewName(docLayer._viewId) !== data.author;
+ const isAuthor = this.isAuthor();
+ const canRemove = this.canRemove();
+ const canModerate = this.canModerate();
- if (!blockChangeFromDifferentAuthor)
+ if (isAuthor)
entries.push({ text: _('Modify'), type: 'action', id: 'modify', pos: String(pos++) });
if (docLayer._docType === 'text')
entries.push({ text: _('Reply'), type: 'action', id: 'reply', pos: String(pos++) });
- if (!blockChangeFromDifferentAuthor)
+ if (canRemove)
entries.push({ text: _('Remove'), type: 'action', id: 'remove', pos: String(pos++) });
- if (docLayer._docType === 'text' && this.isRootComment() && !blockChangeFromDifferentAuthor)
+ if (docLayer._docType === 'text' && this.isRootComment() && canRemove)
entries.push({ text: _('Remove Thread'), type: 'action', id: 'removeThread', pos: String(pos++) });
const isNonWriterComponent = ['spreadsheet', 'drawing', 'presentation'].includes(docLayer._docType);
- if (docLayer._docType === 'text' || (isNonWriterComponent && data.threaded))
+ if (canModerate
+ && (docLayer._docType === 'text' || (isNonWriterComponent && data.threaded)))
entries.push({
text: data.resolved === 'false' ? _('Resolve') : _('Unresolve'),
type: 'action', id: 'resolve', pos: String(pos++),
});
- if (docLayer._docType === 'text' && this.isRootComment())
+ if (docLayer._docType === 'text' && this.isRootComment() && canModerate)
entries.push({
text: listSection.isThreadResolved(this) ? _('Unresolve Thread') : _('Resolve Thread'),
type: 'action', id: 'resolveThread', pos: String(pos++),
});
- if (docLayer._docType === 'text' && !this.isRootComment() && !blockChangeFromDifferentAuthor)
+ if (docLayer._docType === 'text' && !this.isRootComment() && isAuthor)
entries.push({ text: _('Promote to top comment'), type: 'action', id: 'promote', pos: String(pos++) });
if (docLayer._docType === 'text' && !window.mode.isSmallScreenDevice()) {
diff --git a/cypress_test/integration_tests/multiuser/writer/annotation_spec.js b/cypress_test/integration_tests/multiuser/writer/annotation_spec.js
index b59b511a9a64..44d5e35e440f 100644
--- a/cypress_test/integration_tests/multiuser/writer/annotation_spec.js
+++ b/cypress_test/integration_tests/multiuser/writer/annotation_spec.js
@@ -80,6 +80,24 @@ describe(['tagmultiuser'], 'Multiuser Annotation Tests', function () {
cy.cSetActiveFrame('#iframe2');
cy.cGet('.cool-annotation-content-wrapper').should('not.exist');
});
+
+ it('Only author sees Modify', function() {
+ cy.cSetActiveFrame('#iframe1');
+ desktopHelper.insertComment();
+ cy.cGet('.cool-annotation-content-wrapper').should('exist');
+ cy.cGet('#comment-annotation-menu-edit-1').should('exist');
+ cy.cGet('#comment-annotation-menu-1').click();
+ cy.cGet('body').contains('.ui-combobox-entry.jsdialog.ui-grid-cell', 'Modify').should('exist');
+ cy.cGet('body').contains('.ui-combobox-entry.jsdialog.ui-grid-cell', 'Reply').should('exist');
+ cy.cGet('body').type('{esc}');
+
+ cy.cSetActiveFrame('#iframe2');
+ cy.cGet('.cool-annotation-content-wrapper').should('exist');
+ cy.cGet('#comment-annotation-menu-edit-1').should('not.exist');
+ cy.cGet('#comment-annotation-menu-1').click();
+ cy.cGet('body').contains('.ui-combobox-entry.jsdialog.ui-grid-cell', 'Modify').should('not.exist');
+ cy.cGet('body').contains('.ui-combobox-entry.jsdialog.ui-grid-cell', 'Reply').should('exist');
+ });
});
describe(['tagmultiuser'], 'Collapsed Annotation Tests', function() {