New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly.

Thanks to xet7 !

Fixes #1122,
fixes #6033,
fixes #3300
This commit is contained in:
Lauri Ojansivu 2025-12-22 21:45:09 +02:00
parent 21fb8e9164
commit c1168d181b
5 changed files with 190 additions and 4 deletions

View file

@ -809,6 +809,12 @@ template(name="changePermissionsPopup")
if isNormal
| ✅
span.sub-name {{_ 'normal-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal-assigned-only{{/if}}")
| {{_ 'normal-assigned-only'}}
if isNormalAssignedOnly
| ✅
span.sub-name {{_ 'normal-assigned-only-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-no-comments{{/if}}")
| {{_ 'no-comments'}}
@ -821,6 +827,24 @@ template(name="changePermissionsPopup")
if isCommentOnly
| ✅
span.sub-name {{_ 'comment-only-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-assigned-only{{/if}}")
| {{_ 'comment-assigned-only'}}
if isCommentAssignedOnly
| ✅
span.sub-name {{_ 'comment-assigned-only-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-only{{/if}}")
| {{_ 'read-only'}}
if isReadOnly
| ✅
span.sub-name {{_ 'read-only-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-assigned-only{{/if}}")
| {{_ 'read-assigned-only'}}
if isReadAssignedOnly
| ✅
span.sub-name {{_ 'read-assigned-only-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
| {{_ 'worker'}}

View file

@ -239,8 +239,20 @@ Template.memberPopup.helpers({
const commentOnly = currentBoard.hasCommentOnly(this.userId);
const noComments = currentBoard.hasNoComments(this.userId);
const worker = currentBoard.hasWorker(this.userId);
if (commentOnly) {
const normalAssignedOnly = currentBoard.hasNormalAssignedOnly(this.userId);
const commentAssignedOnly = currentBoard.hasCommentAssignedOnly(this.userId);
const readOnly = currentBoard.hasReadOnly(this.userId);
const readAssignedOnly = currentBoard.hasReadAssignedOnly(this.userId);
if (readAssignedOnly) {
return TAPi18n.__('read-assigned-only');
} else if (readOnly) {
return TAPi18n.__('read-only');
} else if (commentAssignedOnly) {
return TAPi18n.__('comment-assigned-only');
} else if (commentOnly) {
return TAPi18n.__('comment-only');
} else if (normalAssignedOnly) {
return TAPi18n.__('normal-assigned-only');
} else if (noComments) {
return TAPi18n.__('no-comments');
} else if (worker) {
@ -1925,7 +1937,7 @@ Template.removeBoardTeamPopup.helpers({
});
Template.changePermissionsPopup.events({
'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
'click .js-set-admin, click .js-set-normal, click .js-set-normal-assigned-only, click .js-set-no-comments, click .js-set-comment-only, click .js-set-comment-assigned-only, click .js-set-read-only, click .js-set-read-assigned-only, click .js-set-worker'(
event,
) {
const currentBoard = Utils.getCurrentBoard();
@ -1934,6 +1946,14 @@ Template.changePermissionsPopup.events({
const isCommentOnly = $(event.currentTarget).hasClass(
'js-set-comment-only',
);
const isNormalAssignedOnly = $(event.currentTarget).hasClass(
'js-set-normal-assigned-only',
);
const isCommentAssignedOnly = $(event.currentTarget).hasClass(
'js-set-comment-assigned-only',
);
const isReadOnly = $(event.currentTarget).hasClass('js-set-read-only');
const isReadAssignedOnly = $(event.currentTarget).hasClass('js-set-read-assigned-only');
const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
const isWorker = $(event.currentTarget).hasClass('js-set-worker');
currentBoard.setMemberPermission(
@ -1942,6 +1962,10 @@ Template.changePermissionsPopup.events({
isNoComments,
isCommentOnly,
isWorker,
isNormalAssignedOnly,
isCommentAssignedOnly,
isReadOnly,
isReadAssignedOnly,
);
Popup.back(1);
},
@ -1959,10 +1983,22 @@ Template.changePermissionsPopup.helpers({
!currentBoard.hasAdmin(this.userId) &&
!currentBoard.hasNoComments(this.userId) &&
!currentBoard.hasCommentOnly(this.userId) &&
!currentBoard.hasNormalAssignedOnly(this.userId) &&
!currentBoard.hasCommentAssignedOnly(this.userId) &&
!currentBoard.hasReadOnly(this.userId) &&
!currentBoard.hasReadAssignedOnly(this.userId) &&
!currentBoard.hasWorker(this.userId)
);
},
isNormalAssignedOnly() {
const currentBoard = Utils.getCurrentBoard();
return (
!currentBoard.hasAdmin(this.userId) &&
currentBoard.hasNormalAssignedOnly(this.userId)
);
},
isNoComments() {
const currentBoard = Utils.getCurrentBoard();
return (
@ -1979,6 +2015,30 @@ Template.changePermissionsPopup.helpers({
);
},
isCommentAssignedOnly() {
const currentBoard = Utils.getCurrentBoard();
return (
!currentBoard.hasAdmin(this.userId) &&
currentBoard.hasCommentAssignedOnly(this.userId)
);
},
isReadOnly() {
const currentBoard = Utils.getCurrentBoard();
return (
!currentBoard.hasAdmin(this.userId) &&
currentBoard.hasReadOnly(this.userId)
);
},
isReadAssignedOnly() {
const currentBoard = Utils.getCurrentBoard();
return (
!currentBoard.hasAdmin(this.userId) &&
currentBoard.hasReadAssignedOnly(this.userId)
);
},
isWorker() {
const currentBoard = Utils.getCurrentBoard();
return (

View file

@ -328,10 +328,16 @@
"comment-placeholder": "Write Comment",
"comment-only": "Comment only",
"comment-only-desc": "Can comment on cards only.",
"comment-assigned-only": "Comment only (Assigned Only)",
"comment-assigned-only-desc": "Can comment on assigned cards only.",
"comment-delete": "Are you sure you want to delete the comment?",
"deleteCommentPopup-title": "Delete comment?",
"no-comments": "No comments",
"no-comments-desc": "Can not see comments and activities.",
"read-only": "Read Only",
"read-only-desc": "Can view cards only. Can not comment or edit.",
"read-assigned-only": "Read Only (Assigned Only)",
"read-assigned-only-desc": "Can view assigned cards only. Can not comment or edit.",
"worker": "Worker",
"worker-desc": "Can only move cards, assign itself to card and comment.",
"computer": "Computer",
@ -568,6 +574,8 @@
"no-results": "No results",
"normal": "Normal",
"normal-desc": "Can view and edit cards. Can't change settings.",
"normal-assigned-only": "Normal (Assigned Only)",
"normal-assigned-only-desc": "Can view and edit only assigned cards. Can't change settings.",
"not-accepted-yet": "Invitation not accepted yet",
"notify-participate": "Receive updates to any cards you participate as creator or member",
"notify-watch": "Receive updates to any boards, lists, or cards youre watching",

View file

@ -225,6 +225,34 @@ Boards.attachSchema(
type: Boolean,
optional: true,
},
'members.$.isNormalAssignedOnly': {
/**
* Is the member only allowed to see assigned cards (Normal permission)
*/
type: Boolean,
optional: true,
},
'members.$.isCommentAssignedOnly': {
/**
* Is the member only allowed to comment on assigned cards
*/
type: Boolean,
optional: true,
},
'members.$.isReadOnly': {
/**
* Is the member only allowed to read the board (no comments, no editing)
*/
type: Boolean,
optional: true,
},
'members.$.isReadAssignedOnly': {
/**
* Is the member only allowed to read assigned cards (no comments, no editing)
*/
type: Boolean,
optional: true,
},
permission: {
/**
* visibility of the board
@ -979,6 +1007,44 @@ Boards.helpers({
});
},
hasNormalAssignedOnly(memberId) {
return !!_.findWhere(this.members, {
userId: memberId,
isActive: true,
isAdmin: false,
isNormalAssignedOnly: true,
isCommentAssignedOnly: false,
});
},
hasCommentAssignedOnly(memberId) {
return !!_.findWhere(this.members, {
userId: memberId,
isActive: true,
isAdmin: false,
isNormalAssignedOnly: false,
isCommentAssignedOnly: true,
});
},
hasReadOnly(memberId) {
return !!_.findWhere(this.members, {
userId: memberId,
isActive: true,
isAdmin: false,
isReadOnly: true,
});
},
hasReadAssignedOnly(memberId) {
return !!_.findWhere(this.members, {
userId: memberId,
isActive: true,
isAdmin: false,
isReadAssignedOnly: true,
});
},
hasAnyAllowsDate() {
const ret = this.allowsReceivedDate || this.allowsStartDate || this.allowsDueDate || this.allowsEndDate;
return ret;
@ -1416,6 +1482,10 @@ Boards.mutations({
isNoComments: false,
isCommentOnly: false,
isWorker: false,
isNormalAssignedOnly: false,
isCommentAssignedOnly: false,
isReadOnly: false,
isReadAssignedOnly: false,
},
},
};
@ -1449,6 +1519,10 @@ Boards.mutations({
isNoComments,
isCommentOnly,
isWorker,
isNormalAssignedOnly = false,
isCommentAssignedOnly = false,
isReadOnly = false,
isReadAssignedOnly = false,
currentUserId = Meteor.userId(),
) {
const memberIndex = this.memberIndex(memberId);
@ -1463,6 +1537,10 @@ Boards.mutations({
[`members.${memberIndex}.isNoComments`]: isNoComments,
[`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
[`members.${memberIndex}.isWorker`]: isWorker,
[`members.${memberIndex}.isNormalAssignedOnly`]: isNormalAssignedOnly,
[`members.${memberIndex}.isCommentAssignedOnly`]: isCommentAssignedOnly,
[`members.${memberIndex}.isReadOnly`]: isReadOnly,
[`members.${memberIndex}.isReadAssignedOnly`]: isReadAssignedOnly,
},
};
},
@ -2372,6 +2450,10 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
* @param {boolean} isNoComments NoComments capability
* @param {boolean} isCommentOnly CommentsOnly capability
* @param {boolean} isWorker Worker capability
* @param {boolean} isNormalAssignedOnly NormalAssignedOnly capability
* @param {boolean} isCommentAssignedOnly CommentAssignedOnly capability
* @param {boolean} isReadOnly ReadOnly capability
* @param {boolean} isReadAssignedOnly ReadAssignedOnly capability
*/
JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
req,
@ -2381,7 +2463,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
Authentication.checkUserId(req.userId);
const boardId = req.params.boardId;
const memberId = req.params.memberId;
const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body;
const { isAdmin, isNoComments, isCommentOnly, isWorker, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly } = req.body;
const board = ReactiveCache.getBoard(boardId);
function isTrue(data) {
try {
@ -2396,6 +2478,10 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
isTrue(isNoComments),
isTrue(isCommentOnly),
isTrue(isWorker),
isTrue(isNormalAssignedOnly),
isTrue(isCommentAssignedOnly),
isTrue(isReadOnly),
isTrue(isReadAssignedOnly),
req.userId,
);

View file

@ -2947,6 +2947,10 @@ if (Meteor.isServer) {
* @param {boolean} isNoComments disable comments
* @param {boolean} isCommentOnly only enable comments
* @param {boolean} isWorker is the user a board worker
* @param {boolean} isNormalAssignedOnly only see assigned cards (Normal permission)
* @param {boolean} isCommentAssignedOnly only comment on assigned cards
* @param {boolean} isReadOnly read-only access (no comments or editing)
* @param {boolean} isReadAssignedOnly read-only assigned cards only
* @return_type {_id: string,
* title: string}
*/
@ -2959,7 +2963,7 @@ if (Meteor.isServer) {
const userId = req.params.userId;
const boardId = req.params.boardId;
const action = req.body.action;
const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body;
const { isAdmin, isNoComments, isCommentOnly, isWorker, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly } = req.body;
let data = ReactiveCache.getUser(userId);
if (data !== undefined) {
if (action === 'add') {
@ -2978,6 +2982,10 @@ if (Meteor.isServer) {
isTrue(isNoComments),
isTrue(isCommentOnly),
isTrue(isWorker),
isTrue(isNormalAssignedOnly),
isTrue(isCommentAssignedOnly),
isTrue(isReadOnly),
isTrue(isReadAssignedOnly),
userId,
);
}