@dhis2/analytics 26.0.11 → 26.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/components/Interpretations/InterpretationModal/Comment.js +7 -4
- package/build/cjs/components/Interpretations/InterpretationModal/CommentDeleteButton.js +24 -5
- package/build/cjs/components/Interpretations/InterpretationModal/CommentUpdateForm.js +1 -1
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationModal.js +1 -1
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationThread.js +8 -5
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +1 -1
- package/build/cjs/components/Interpretations/common/Interpretation/Interpretation.js +26 -10
- package/build/cjs/components/Interpretations/common/Message/MessageIconButton.js +9 -6
- package/build/cjs/components/Interpretations/common/__tests__/getInterpretationAccess.spec.js +152 -0
- package/build/cjs/components/Interpretations/common/getInterpretationAccess.js +25 -0
- package/build/cjs/components/Interpretations/common/index.js +11 -0
- package/build/cjs/locales/en/translations.json +5 -1
- package/build/es/components/Interpretations/InterpretationModal/Comment.js +8 -5
- package/build/es/components/Interpretations/InterpretationModal/CommentDeleteButton.js +23 -6
- package/build/es/components/Interpretations/InterpretationModal/CommentUpdateForm.js +1 -1
- package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +1 -1
- package/build/es/components/Interpretations/InterpretationModal/InterpretationThread.js +9 -6
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +1 -1
- package/build/es/components/Interpretations/common/Interpretation/Interpretation.js +27 -11
- package/build/es/components/Interpretations/common/Message/MessageIconButton.js +9 -6
- package/build/es/components/Interpretations/common/__tests__/getInterpretationAccess.spec.js +150 -0
- package/build/es/components/Interpretations/common/getInterpretationAccess.js +17 -0
- package/build/es/components/Interpretations/common/index.js +2 -1
- package/build/es/locales/en/translations.json +5 -1
- package/package.json +1 -1
|
@@ -19,9 +19,11 @@ const Comment = _ref => {
|
|
|
19
19
|
comment,
|
|
20
20
|
currentUser,
|
|
21
21
|
interpretationId,
|
|
22
|
-
onThreadUpdated
|
|
22
|
+
onThreadUpdated,
|
|
23
|
+
canComment
|
|
23
24
|
} = _ref;
|
|
24
25
|
const [isUpdateMode, setIsUpdateMode] = (0, _react.useState)(false);
|
|
26
|
+
const commentAccess = (0, _index.getCommentAccess)(comment, canComment, currentUser);
|
|
25
27
|
return isUpdateMode ? /*#__PURE__*/_react.default.createElement(_CommentUpdateForm.CommentUpdateForm, {
|
|
26
28
|
close: () => setIsUpdateMode(false),
|
|
27
29
|
commentId: comment.id,
|
|
@@ -33,11 +35,11 @@ const Comment = _ref => {
|
|
|
33
35
|
text: comment.text,
|
|
34
36
|
created: comment.created,
|
|
35
37
|
username: comment.createdBy.displayName
|
|
36
|
-
}, /*#__PURE__*/_react.default.createElement(_index.MessageStatsBar, null,
|
|
38
|
+
}, commentAccess.edit && /*#__PURE__*/_react.default.createElement(_index.MessageStatsBar, null, /*#__PURE__*/_react.default.createElement(_index.MessageIconButton, {
|
|
37
39
|
iconComponent: _ui.IconEdit16,
|
|
38
40
|
tooltipContent: _d2I18n.default.t('Edit'),
|
|
39
41
|
onClick: () => setIsUpdateMode(true)
|
|
40
|
-
}),
|
|
42
|
+
}), commentAccess.delete && /*#__PURE__*/_react.default.createElement(_CommentDeleteButton.CommentDeleteButton, {
|
|
41
43
|
commentId: comment.id,
|
|
42
44
|
interpretationId: interpretationId,
|
|
43
45
|
onComplete: () => onThreadUpdated(true)
|
|
@@ -48,5 +50,6 @@ Comment.propTypes = {
|
|
|
48
50
|
comment: _propTypes.default.object.isRequired,
|
|
49
51
|
currentUser: _propTypes.default.object.isRequired,
|
|
50
52
|
interpretationId: _propTypes.default.string.isRequired,
|
|
51
|
-
onThreadUpdated: _propTypes.default.func.isRequired
|
|
53
|
+
onThreadUpdated: _propTypes.default.func.isRequired,
|
|
54
|
+
canComment: _propTypes.default.bool
|
|
52
55
|
};
|
|
@@ -4,12 +4,15 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.CommentDeleteButton = void 0;
|
|
7
|
+
var _style = _interopRequireDefault(require("styled-jsx/style"));
|
|
7
8
|
var _appRuntime = require("@dhis2/app-runtime");
|
|
8
9
|
var _d2I18n = _interopRequireDefault(require("@dhis2/d2-i18n"));
|
|
9
10
|
var _ui = require("@dhis2/ui");
|
|
10
11
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
11
|
-
var _react =
|
|
12
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
12
13
|
var _index = require("../common/index.js");
|
|
14
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
15
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
13
16
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
14
17
|
const mutation = {
|
|
15
18
|
resource: 'interpretations',
|
|
@@ -28,21 +31,37 @@ const CommentDeleteButton = _ref2 => {
|
|
|
28
31
|
interpretationId,
|
|
29
32
|
onComplete
|
|
30
33
|
} = _ref2;
|
|
34
|
+
const [deleteError, setDeleteError] = (0, _react.useState)(null);
|
|
31
35
|
const [remove, {
|
|
32
36
|
loading
|
|
33
37
|
}] = (0, _appRuntime.useDataMutation)(mutation, {
|
|
34
|
-
onComplete
|
|
38
|
+
onComplete: () => {
|
|
39
|
+
setDeleteError(null);
|
|
40
|
+
onComplete();
|
|
41
|
+
},
|
|
42
|
+
onError: () => setDeleteError(_d2I18n.default.t('Delete failed')),
|
|
35
43
|
variables: {
|
|
36
44
|
commentId,
|
|
37
45
|
interpretationId
|
|
38
46
|
}
|
|
39
47
|
});
|
|
40
|
-
|
|
48
|
+
const onDelete = () => {
|
|
49
|
+
setDeleteError(null);
|
|
50
|
+
remove();
|
|
51
|
+
};
|
|
52
|
+
return /*#__PURE__*/_react.default.createElement("div", {
|
|
53
|
+
className: _style.default.dynamic([["945681082", [_ui.colors.red500]]]) + " " + "delete-button-container"
|
|
54
|
+
}, /*#__PURE__*/_react.default.createElement(_index.MessageIconButton, {
|
|
41
55
|
tooltipContent: _d2I18n.default.t('Delete'),
|
|
42
56
|
iconComponent: _ui.IconDelete16,
|
|
43
|
-
onClick:
|
|
57
|
+
onClick: onDelete,
|
|
44
58
|
disabled: loading
|
|
45
|
-
})
|
|
59
|
+
}), deleteError && /*#__PURE__*/_react.default.createElement("span", {
|
|
60
|
+
className: _style.default.dynamic([["945681082", [_ui.colors.red500]]]) + " " + "delete-error"
|
|
61
|
+
}, deleteError), /*#__PURE__*/_react.default.createElement(_style.default, {
|
|
62
|
+
id: "945681082",
|
|
63
|
+
dynamic: [_ui.colors.red500]
|
|
64
|
+
}, [".delete-button-container.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px;}", `.delete-error.__jsx-style-dynamic-selector{color:${_ui.colors.red500};font-size:12px;line-height:12px;}`]));
|
|
46
65
|
};
|
|
47
66
|
exports.CommentDeleteButton = CommentDeleteButton;
|
|
48
67
|
CommentDeleteButton.propTypes = {
|
|
@@ -44,7 +44,7 @@ const CommentUpdateForm = _ref => {
|
|
|
44
44
|
close();
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
const errorText = error ?
|
|
47
|
+
const errorText = error ? _d2I18n.default.t('Could not update comment') : '';
|
|
48
48
|
return /*#__PURE__*/_react.default.createElement("div", {
|
|
49
49
|
className: _style.default.dynamic([["2690082310", [_ui.spacers.dp8, _ui.spacers.dp8, _ui.colors.grey100]]]) + " " + "message"
|
|
50
50
|
}, /*#__PURE__*/_react.default.createElement(_index.MessageEditorContainer, {
|
|
@@ -41,7 +41,7 @@ const query = {
|
|
|
41
41
|
return id;
|
|
42
42
|
},
|
|
43
43
|
params: {
|
|
44
|
-
fields: ['access', 'id', 'text', 'created', '
|
|
44
|
+
fields: ['access[write,manage]', 'id', 'text', 'created', 'createdBy[id,displayName]', 'likes', 'likedBy', 'comments[id,text,created,createdBy[id,displayName]]']
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
};
|
|
@@ -34,6 +34,7 @@ const InterpretationThread = _ref => {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
}, [initialFocus]);
|
|
37
|
+
const interpretationAccess = (0, _index.getInterpretationAccess)(interpretation, currentUser);
|
|
37
38
|
return /*#__PURE__*/_react.default.createElement("div", {
|
|
38
39
|
className: "jsx-615306698" + " " + ((0, _classnames.default)('container', {
|
|
39
40
|
fetching
|
|
@@ -52,12 +53,13 @@ const InterpretationThread = _ref => {
|
|
|
52
53
|
}, /*#__PURE__*/_react.default.createElement(_index.Interpretation, {
|
|
53
54
|
currentUser: currentUser,
|
|
54
55
|
interpretation: interpretation,
|
|
55
|
-
onReplyIconClick: () => {
|
|
56
|
+
onReplyIconClick: interpretationAccess.comment ? () => {
|
|
56
57
|
var _focusRef$current;
|
|
57
58
|
return (_focusRef$current = focusRef.current) === null || _focusRef$current === void 0 ? void 0 : _focusRef$current.focus();
|
|
58
|
-
},
|
|
59
|
+
} : null,
|
|
59
60
|
onUpdated: () => onThreadUpdated(true),
|
|
60
|
-
onDeleted: onInterpretationDeleted
|
|
61
|
+
onDeleted: onInterpretationDeleted,
|
|
62
|
+
isInThread: true
|
|
61
63
|
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
62
64
|
className: "jsx-615306698" + " " + 'comments'
|
|
63
65
|
}, interpretation.comments.map(comment => /*#__PURE__*/_react.default.createElement(_Comment.Comment, {
|
|
@@ -65,8 +67,9 @@ const InterpretationThread = _ref => {
|
|
|
65
67
|
comment: comment,
|
|
66
68
|
currentUser: currentUser,
|
|
67
69
|
interpretationId: interpretation.id,
|
|
68
|
-
onThreadUpdated: onThreadUpdated
|
|
69
|
-
|
|
70
|
+
onThreadUpdated: onThreadUpdated,
|
|
71
|
+
canComment: interpretationAccess.comment
|
|
72
|
+
}))), interpretationAccess.comment && /*#__PURE__*/_react.default.createElement(_CommentAddForm.CommentAddForm, {
|
|
70
73
|
currentUser: currentUser,
|
|
71
74
|
interpretationId: interpretation.id,
|
|
72
75
|
onSave: () => onThreadUpdated(true),
|
|
@@ -25,7 +25,7 @@ const interpretationsQuery = {
|
|
|
25
25
|
id
|
|
26
26
|
} = _ref;
|
|
27
27
|
return {
|
|
28
|
-
fields: ['access', 'id', '
|
|
28
|
+
fields: ['access[write,manage]', 'id', 'createdBy[id,displayName]', 'created', 'text', 'comments[id]', 'likes', 'likedBy[id]'],
|
|
29
29
|
filter: `${type}.id:eq:${id}`
|
|
30
30
|
};
|
|
31
31
|
}
|
|
@@ -23,7 +23,8 @@ const Interpretation = _ref => {
|
|
|
23
23
|
onUpdated,
|
|
24
24
|
onDeleted,
|
|
25
25
|
disabled,
|
|
26
|
-
onReplyIconClick
|
|
26
|
+
onReplyIconClick,
|
|
27
|
+
isInThread
|
|
27
28
|
} = _ref;
|
|
28
29
|
const [isUpdateMode, setIsUpdateMode] = (0, _react.useState)(false);
|
|
29
30
|
const [showSharingDialog, setShowSharingDialog] = (0, _react.useState)(false);
|
|
@@ -37,17 +38,30 @@ const Interpretation = _ref => {
|
|
|
37
38
|
onComplete: onUpdated
|
|
38
39
|
});
|
|
39
40
|
const shouldShowButton = !!onClick && !disabled;
|
|
41
|
+
const interpretationAccess = (0, _index.getInterpretationAccess)(interpretation, currentUser);
|
|
42
|
+
let tooltip = _d2I18n.default.t('Reply');
|
|
43
|
+
if (!interpretationAccess.comment) {
|
|
44
|
+
if (isInThread) {
|
|
45
|
+
tooltip = _d2I18n.default.t('{{count}} replies', {
|
|
46
|
+
count: interpretation.comments.length,
|
|
47
|
+
defaultValue: '{{count}} reply',
|
|
48
|
+
defaultValue_plural: '{{count}} replies'
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
tooltip = _d2I18n.default.t('View replies');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
40
54
|
return isUpdateMode ? /*#__PURE__*/_react.default.createElement(_InterpretationUpdateForm.InterpretationUpdateForm, {
|
|
41
55
|
close: () => setIsUpdateMode(false),
|
|
42
56
|
id: interpretation.id,
|
|
43
|
-
showSharingLink:
|
|
57
|
+
showSharingLink: interpretationAccess.share,
|
|
44
58
|
onComplete: onUpdated,
|
|
45
59
|
text: interpretation.text,
|
|
46
60
|
currentUser: currentUser
|
|
47
61
|
}) : /*#__PURE__*/_react.default.createElement(_index.Message, {
|
|
48
62
|
text: interpretation.text,
|
|
49
63
|
created: interpretation.created,
|
|
50
|
-
username: interpretation.
|
|
64
|
+
username: interpretation.createdBy.displayName
|
|
51
65
|
}, !disabled && /*#__PURE__*/_react.default.createElement(_index.MessageStatsBar, null, /*#__PURE__*/_react.default.createElement(_index.MessageIconButton, {
|
|
52
66
|
tooltipContent: isLikedByCurrentUser ? _d2I18n.default.t('Unlike') : _d2I18n.default.t('Like'),
|
|
53
67
|
iconComponent: _ui.IconThumbUp16,
|
|
@@ -57,12 +71,13 @@ const Interpretation = _ref => {
|
|
|
57
71
|
disabled: toggleLikeInProgress,
|
|
58
72
|
dataTest: "interpretation-like-unlike-button"
|
|
59
73
|
}), /*#__PURE__*/_react.default.createElement(_index.MessageIconButton, {
|
|
60
|
-
tooltipContent:
|
|
74
|
+
tooltipContent: tooltip,
|
|
61
75
|
iconComponent: _ui.IconReply16,
|
|
62
|
-
onClick: () => onReplyIconClick(interpretation.id),
|
|
76
|
+
onClick: () => onReplyIconClick && onReplyIconClick(interpretation.id),
|
|
63
77
|
count: interpretation.comments.length,
|
|
64
|
-
dataTest: "interpretation-reply-button"
|
|
65
|
-
|
|
78
|
+
dataTest: "interpretation-reply-button",
|
|
79
|
+
viewOnly: isInThread && !interpretationAccess.comment
|
|
80
|
+
}), interpretationAccess.share && /*#__PURE__*/_react.default.createElement(_index.MessageIconButton, {
|
|
66
81
|
iconComponent: _ui.IconShare16,
|
|
67
82
|
tooltipContent: _d2I18n.default.t('Share'),
|
|
68
83
|
onClick: () => setShowSharingDialog(true),
|
|
@@ -72,15 +87,15 @@ const Interpretation = _ref => {
|
|
|
72
87
|
type: 'interpretation',
|
|
73
88
|
id: interpretation.id,
|
|
74
89
|
onClose: () => setShowSharingDialog(false)
|
|
75
|
-
}),
|
|
90
|
+
}), /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, interpretationAccess.edit && /*#__PURE__*/_react.default.createElement(_index.MessageIconButton, {
|
|
76
91
|
iconComponent: _ui.IconEdit16,
|
|
77
92
|
tooltipContent: _d2I18n.default.t('Edit'),
|
|
78
93
|
onClick: () => setIsUpdateMode(true),
|
|
79
94
|
dataTest: "interpretation-edit-button"
|
|
80
|
-
}),
|
|
95
|
+
}), interpretationAccess.delete && /*#__PURE__*/_react.default.createElement(_InterpretationDeleteButton.InterpretationDeleteButton, {
|
|
81
96
|
id: interpretation.id,
|
|
82
97
|
onComplete: onDeleted
|
|
83
|
-
})), shouldShowButton && /*#__PURE__*/_react.default.createElement(_ui.Button, {
|
|
98
|
+
}))), shouldShowButton && /*#__PURE__*/_react.default.createElement(_ui.Button, {
|
|
84
99
|
secondary: true,
|
|
85
100
|
small: true,
|
|
86
101
|
onClick: (_, event) => {
|
|
@@ -97,5 +112,6 @@ Interpretation.propTypes = {
|
|
|
97
112
|
onReplyIconClick: _propTypes.default.func.isRequired,
|
|
98
113
|
onUpdated: _propTypes.default.func.isRequired,
|
|
99
114
|
disabled: _propTypes.default.bool,
|
|
115
|
+
isInThread: _propTypes.default.bool,
|
|
100
116
|
onClick: _propTypes.default.func
|
|
101
117
|
};
|
|
@@ -18,7 +18,8 @@ const MessageIconButton = _ref => {
|
|
|
18
18
|
selected,
|
|
19
19
|
count,
|
|
20
20
|
iconComponent: Icon,
|
|
21
|
-
dataTest
|
|
21
|
+
dataTest,
|
|
22
|
+
viewOnly
|
|
22
23
|
} = _ref;
|
|
23
24
|
return /*#__PURE__*/_react.default.createElement(_ui.Tooltip, {
|
|
24
25
|
closeDelay: 200,
|
|
@@ -33,7 +34,7 @@ const MessageIconButton = _ref => {
|
|
|
33
34
|
ref: ref,
|
|
34
35
|
onMouseOver: onMouseOver,
|
|
35
36
|
onMouseOut: onMouseOut,
|
|
36
|
-
className: _style.default.dynamic([["
|
|
37
|
+
className: _style.default.dynamic([["163818318", [_ui.spacers.dp4, _ui.colors.grey700, _ui.colors.teal600, _ui.colors.grey900, _ui.colors.teal800, _ui.colors.teal500, _ui.colors.teal700, _ui.theme.disabled, _ui.theme.disabled]]]) + " " + "wrapper"
|
|
37
38
|
}, /*#__PURE__*/_react.default.createElement("button", {
|
|
38
39
|
onClick: event => {
|
|
39
40
|
event.stopPropagation();
|
|
@@ -41,13 +42,14 @@ const MessageIconButton = _ref => {
|
|
|
41
42
|
},
|
|
42
43
|
disabled: disabled,
|
|
43
44
|
"data-test": dataTest,
|
|
44
|
-
className: _style.default.dynamic([["
|
|
45
|
-
selected
|
|
45
|
+
className: _style.default.dynamic([["163818318", [_ui.spacers.dp4, _ui.colors.grey700, _ui.colors.teal600, _ui.colors.grey900, _ui.colors.teal800, _ui.colors.teal500, _ui.colors.teal700, _ui.theme.disabled, _ui.theme.disabled]]]) + " " + ((0, _classnames.default)('button', {
|
|
46
|
+
selected,
|
|
47
|
+
viewOnly
|
|
46
48
|
}) || "")
|
|
47
49
|
}, count && count, /*#__PURE__*/_react.default.createElement(Icon, null)), /*#__PURE__*/_react.default.createElement(_style.default, {
|
|
48
|
-
id: "
|
|
50
|
+
id: "163818318",
|
|
49
51
|
dynamic: [_ui.spacers.dp4, _ui.colors.grey700, _ui.colors.teal600, _ui.colors.grey900, _ui.colors.teal800, _ui.colors.teal500, _ui.colors.teal700, _ui.theme.disabled, _ui.theme.disabled]
|
|
50
|
-
}, [".wrapper.__jsx-style-dynamic-selector{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;}", `.button.__jsx-style-dynamic-selector{all:unset;cursor:pointer;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:${_ui.spacers.dp4};-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:12px;line-height:14px;color:${_ui.colors.grey700};}`, `.button.selected.__jsx-style-dynamic-selector{color:${_ui.colors.teal600};font-weight:500;}`, `.button.__jsx-style-dynamic-selector:hover{color:${_ui.colors.grey900};}`, `.button.selected.__jsx-style-dynamic-selector:hover{color:${_ui.colors.teal800};}`, `.button.selected.__jsx-style-dynamic-selector svg{color:${_ui.colors.teal500};}`, `.button.selected.__jsx-style-dynamic-selector:hover svg{color:${_ui.colors.teal700};}`, `.button.__jsx-style-dynamic-selector:disabled{color:${_ui.theme.disabled};cursor:not-allowed;}`, `.button.__jsx-style-dynamic-selector:disabled svg{color:${_ui.theme.disabled};}`]));
|
|
52
|
+
}, [".wrapper.__jsx-style-dynamic-selector{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;}", `.button.__jsx-style-dynamic-selector{all:unset;cursor:pointer;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:${_ui.spacers.dp4};-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:12px;line-height:14px;color:${_ui.colors.grey700};}`, ".viewOnly.__jsx-style-dynamic-selector{cursor:default;}", `.button.selected.__jsx-style-dynamic-selector{color:${_ui.colors.teal600};font-weight:500;}`, `.button.__jsx-style-dynamic-selector:hover{color:${_ui.colors.grey900};}`, `.button.selected.__jsx-style-dynamic-selector:hover{color:${_ui.colors.teal800};}`, `.button.selected.__jsx-style-dynamic-selector svg{color:${_ui.colors.teal500};}`, `.button.selected.__jsx-style-dynamic-selector:hover svg{color:${_ui.colors.teal700};}`, `.button.__jsx-style-dynamic-selector:disabled{color:${_ui.theme.disabled};cursor:not-allowed;}`, `.button.__jsx-style-dynamic-selector:disabled svg{color:${_ui.theme.disabled};}`]));
|
|
51
53
|
});
|
|
52
54
|
};
|
|
53
55
|
exports.MessageIconButton = MessageIconButton;
|
|
@@ -58,5 +60,6 @@ MessageIconButton.propTypes = {
|
|
|
58
60
|
dataTest: _propTypes.default.string,
|
|
59
61
|
disabled: _propTypes.default.bool,
|
|
60
62
|
selected: _propTypes.default.bool,
|
|
63
|
+
viewOnly: _propTypes.default.bool,
|
|
61
64
|
onClick: _propTypes.default.func
|
|
62
65
|
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _getInterpretationAccess = require("../getInterpretationAccess.js");
|
|
4
|
+
const superuser = {
|
|
5
|
+
id: 'iamsuper',
|
|
6
|
+
authorities: new Set(['ALL'])
|
|
7
|
+
};
|
|
8
|
+
const userJoe = {
|
|
9
|
+
id: 'johndoe',
|
|
10
|
+
authorities: new Set(['Some'])
|
|
11
|
+
};
|
|
12
|
+
const userJane = {
|
|
13
|
+
id: 'jane',
|
|
14
|
+
authorities: new Set(['Some'])
|
|
15
|
+
};
|
|
16
|
+
describe('interpretation and comment access', () => {
|
|
17
|
+
describe('getInterpretationAccess', () => {
|
|
18
|
+
it('returns true for all accesses for superuser', () => {
|
|
19
|
+
const interpretation = {
|
|
20
|
+
access: {
|
|
21
|
+
write: true,
|
|
22
|
+
manage: true
|
|
23
|
+
},
|
|
24
|
+
createdBy: userJoe
|
|
25
|
+
};
|
|
26
|
+
expect((0, _getInterpretationAccess.getInterpretationAccess)(interpretation, superuser)).toMatchObject({
|
|
27
|
+
share: true,
|
|
28
|
+
comment: true,
|
|
29
|
+
edit: true,
|
|
30
|
+
delete: true
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it('returns true for all accesses for creator', () => {
|
|
34
|
+
const interpretation = {
|
|
35
|
+
access: {
|
|
36
|
+
write: true,
|
|
37
|
+
manage: true
|
|
38
|
+
},
|
|
39
|
+
createdBy: userJoe
|
|
40
|
+
};
|
|
41
|
+
expect((0, _getInterpretationAccess.getInterpretationAccess)(interpretation, userJoe)).toMatchObject({
|
|
42
|
+
share: true,
|
|
43
|
+
comment: true,
|
|
44
|
+
edit: true,
|
|
45
|
+
delete: true
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it('returns false for edit/delete if user is not creator/superuser', () => {
|
|
49
|
+
const interpretation = {
|
|
50
|
+
access: {
|
|
51
|
+
write: true,
|
|
52
|
+
manage: true
|
|
53
|
+
},
|
|
54
|
+
createdBy: userJane
|
|
55
|
+
};
|
|
56
|
+
expect((0, _getInterpretationAccess.getInterpretationAccess)(interpretation, userJoe)).toMatchObject({
|
|
57
|
+
share: true,
|
|
58
|
+
comment: true,
|
|
59
|
+
edit: false,
|
|
60
|
+
delete: false
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
it('returns false for comment/edit/delete if user is not creator/superuser and no write access', () => {
|
|
64
|
+
const interpretation = {
|
|
65
|
+
access: {
|
|
66
|
+
write: false,
|
|
67
|
+
manage: true
|
|
68
|
+
},
|
|
69
|
+
createdBy: userJane
|
|
70
|
+
};
|
|
71
|
+
expect((0, _getInterpretationAccess.getInterpretationAccess)(interpretation, userJoe)).toMatchObject({
|
|
72
|
+
share: true,
|
|
73
|
+
comment: false,
|
|
74
|
+
edit: false,
|
|
75
|
+
delete: false
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('returns false for share/comment/edit/delete if user is not creator/superuser and no write or manage access', () => {
|
|
79
|
+
const interpretation = {
|
|
80
|
+
access: {
|
|
81
|
+
write: false,
|
|
82
|
+
manage: false
|
|
83
|
+
},
|
|
84
|
+
createdBy: userJane
|
|
85
|
+
};
|
|
86
|
+
expect((0, _getInterpretationAccess.getInterpretationAccess)(interpretation, userJoe)).toMatchObject({
|
|
87
|
+
share: false,
|
|
88
|
+
comment: false,
|
|
89
|
+
edit: false,
|
|
90
|
+
delete: false
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('getCommentAccess', () => {
|
|
95
|
+
it('returns true for all accesses for superuser', () => {
|
|
96
|
+
const interpretation = {
|
|
97
|
+
access: {
|
|
98
|
+
write: true
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const comment = {
|
|
102
|
+
createdBy: userJoe
|
|
103
|
+
};
|
|
104
|
+
expect((0, _getInterpretationAccess.getCommentAccess)(comment, interpretation.access.write, superuser)).toMatchObject({
|
|
105
|
+
edit: true,
|
|
106
|
+
delete: true
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it('returns true for all accesses for creator when interpretation has write access', () => {
|
|
110
|
+
const interpretation = {
|
|
111
|
+
access: {
|
|
112
|
+
write: true
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const comment = {
|
|
116
|
+
createdBy: userJoe
|
|
117
|
+
};
|
|
118
|
+
expect((0, _getInterpretationAccess.getCommentAccess)(comment, interpretation.access.write, userJoe)).toMatchObject({
|
|
119
|
+
edit: true,
|
|
120
|
+
delete: true
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
it('returns true for edit and false for delete for creator when interpretation does not have write access', () => {
|
|
124
|
+
const interpretation = {
|
|
125
|
+
access: {
|
|
126
|
+
write: false
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const comment = {
|
|
130
|
+
createdBy: userJoe
|
|
131
|
+
};
|
|
132
|
+
expect((0, _getInterpretationAccess.getCommentAccess)(comment, interpretation.access.write, userJoe)).toMatchObject({
|
|
133
|
+
edit: true,
|
|
134
|
+
delete: false
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
it('returns false for edit/delete for user who is not creator or superuser', () => {
|
|
138
|
+
const interpretation = {
|
|
139
|
+
access: {
|
|
140
|
+
write: true
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const comment = {
|
|
144
|
+
createdBy: userJane
|
|
145
|
+
};
|
|
146
|
+
expect((0, _getInterpretationAccess.getCommentAccess)(comment, interpretation.access.write, userJoe)).toMatchObject({
|
|
147
|
+
edit: false,
|
|
148
|
+
delete: false
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getInterpretationAccess = exports.getCommentAccess = void 0;
|
|
7
|
+
const isCreatorOrSuperuser = (object, currentUser) => (object === null || object === void 0 ? void 0 : object.createdBy.id) === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id) || (currentUser === null || currentUser === void 0 ? void 0 : currentUser.authorities.has('ALL'));
|
|
8
|
+
const getInterpretationAccess = (interpretation, currentUser) => {
|
|
9
|
+
const canEditDelete = isCreatorOrSuperuser(interpretation, currentUser);
|
|
10
|
+
return {
|
|
11
|
+
share: interpretation.access.manage,
|
|
12
|
+
comment: interpretation.access.write,
|
|
13
|
+
edit: canEditDelete,
|
|
14
|
+
delete: canEditDelete
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
exports.getInterpretationAccess = getInterpretationAccess;
|
|
18
|
+
const getCommentAccess = (comment, hasInterpretationReplyAccess, currentUser) => {
|
|
19
|
+
const canEditDelete = isCreatorOrSuperuser(comment, currentUser);
|
|
20
|
+
return {
|
|
21
|
+
edit: canEditDelete,
|
|
22
|
+
delete: canEditDelete && hasInterpretationReplyAccess
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
exports.getCommentAccess = getCommentAccess;
|
|
@@ -35,4 +35,15 @@ Object.keys(_index3).forEach(function (key) {
|
|
|
35
35
|
return _index3[key];
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
|
+
});
|
|
39
|
+
var _getInterpretationAccess = require("./getInterpretationAccess.js");
|
|
40
|
+
Object.keys(_getInterpretationAccess).forEach(function (key) {
|
|
41
|
+
if (key === "default" || key === "__esModule") return;
|
|
42
|
+
if (key in exports && exports[key] === _getInterpretationAccess[key]) return;
|
|
43
|
+
Object.defineProperty(exports, key, {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
get: function () {
|
|
46
|
+
return _getInterpretationAccess[key];
|
|
47
|
+
}
|
|
48
|
+
});
|
|
38
49
|
});
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"Edit": "Edit",
|
|
108
108
|
"Write a reply": "Write a reply",
|
|
109
109
|
"Post reply": "Post reply",
|
|
110
|
+
"Delete failed": "Delete failed",
|
|
110
111
|
"Could not update comment": "Could not update comment",
|
|
111
112
|
"Enter comment text": "Enter comment text",
|
|
112
113
|
"Update": "Update",
|
|
@@ -117,9 +118,12 @@
|
|
|
117
118
|
"Write an interpretation": "Write an interpretation",
|
|
118
119
|
"Post interpretation": "Post interpretation",
|
|
119
120
|
"Interpretations": "Interpretations",
|
|
121
|
+
"Reply": "Reply",
|
|
122
|
+
"{{count}} replies": "{{count}} reply",
|
|
123
|
+
"{{count}} replies_plural": "{{count}} replies",
|
|
124
|
+
"View replies": "View replies",
|
|
120
125
|
"Unlike": "Unlike",
|
|
121
126
|
"Like": "Like",
|
|
122
|
-
"Reply": "Reply",
|
|
123
127
|
"Share": "Share",
|
|
124
128
|
"See interpretation": "See interpretation",
|
|
125
129
|
"Manage sharing": "Manage sharing",
|
|
@@ -2,7 +2,7 @@ import i18n from '@dhis2/d2-i18n';
|
|
|
2
2
|
import { IconEdit16 } from '@dhis2/ui';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import React, { useState } from 'react';
|
|
5
|
-
import { Message, MessageIconButton, MessageStatsBar } from '../common/index.js';
|
|
5
|
+
import { Message, MessageIconButton, MessageStatsBar, getCommentAccess } from '../common/index.js';
|
|
6
6
|
import { CommentDeleteButton } from './CommentDeleteButton.js';
|
|
7
7
|
import { CommentUpdateForm } from './CommentUpdateForm.js';
|
|
8
8
|
const Comment = _ref => {
|
|
@@ -10,9 +10,11 @@ const Comment = _ref => {
|
|
|
10
10
|
comment,
|
|
11
11
|
currentUser,
|
|
12
12
|
interpretationId,
|
|
13
|
-
onThreadUpdated
|
|
13
|
+
onThreadUpdated,
|
|
14
|
+
canComment
|
|
14
15
|
} = _ref;
|
|
15
16
|
const [isUpdateMode, setIsUpdateMode] = useState(false);
|
|
17
|
+
const commentAccess = getCommentAccess(comment, canComment, currentUser);
|
|
16
18
|
return isUpdateMode ? /*#__PURE__*/React.createElement(CommentUpdateForm, {
|
|
17
19
|
close: () => setIsUpdateMode(false),
|
|
18
20
|
commentId: comment.id,
|
|
@@ -24,11 +26,11 @@ const Comment = _ref => {
|
|
|
24
26
|
text: comment.text,
|
|
25
27
|
created: comment.created,
|
|
26
28
|
username: comment.createdBy.displayName
|
|
27
|
-
}, /*#__PURE__*/React.createElement(MessageStatsBar, null,
|
|
29
|
+
}, commentAccess.edit && /*#__PURE__*/React.createElement(MessageStatsBar, null, /*#__PURE__*/React.createElement(MessageIconButton, {
|
|
28
30
|
iconComponent: IconEdit16,
|
|
29
31
|
tooltipContent: i18n.t('Edit'),
|
|
30
32
|
onClick: () => setIsUpdateMode(true)
|
|
31
|
-
}),
|
|
33
|
+
}), commentAccess.delete && /*#__PURE__*/React.createElement(CommentDeleteButton, {
|
|
32
34
|
commentId: comment.id,
|
|
33
35
|
interpretationId: interpretationId,
|
|
34
36
|
onComplete: () => onThreadUpdated(true)
|
|
@@ -38,6 +40,7 @@ Comment.propTypes = {
|
|
|
38
40
|
comment: PropTypes.object.isRequired,
|
|
39
41
|
currentUser: PropTypes.object.isRequired,
|
|
40
42
|
interpretationId: PropTypes.string.isRequired,
|
|
41
|
-
onThreadUpdated: PropTypes.func.isRequired
|
|
43
|
+
onThreadUpdated: PropTypes.func.isRequired,
|
|
44
|
+
canComment: PropTypes.bool
|
|
42
45
|
};
|
|
43
46
|
export { Comment };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import _JSXStyle from "styled-jsx/style";
|
|
1
2
|
import { useDataMutation } from '@dhis2/app-runtime';
|
|
2
3
|
import i18n from '@dhis2/d2-i18n';
|
|
3
|
-
import { IconDelete16 } from '@dhis2/ui';
|
|
4
|
+
import { IconDelete16, colors } from '@dhis2/ui';
|
|
4
5
|
import PropTypes from 'prop-types';
|
|
5
|
-
import React from 'react';
|
|
6
|
+
import React, { useState } from 'react';
|
|
6
7
|
import { MessageIconButton } from '../common/index.js';
|
|
7
8
|
const mutation = {
|
|
8
9
|
resource: 'interpretations',
|
|
@@ -21,21 +22,37 @@ const CommentDeleteButton = _ref2 => {
|
|
|
21
22
|
interpretationId,
|
|
22
23
|
onComplete
|
|
23
24
|
} = _ref2;
|
|
25
|
+
const [deleteError, setDeleteError] = useState(null);
|
|
24
26
|
const [remove, {
|
|
25
27
|
loading
|
|
26
28
|
}] = useDataMutation(mutation, {
|
|
27
|
-
onComplete
|
|
29
|
+
onComplete: () => {
|
|
30
|
+
setDeleteError(null);
|
|
31
|
+
onComplete();
|
|
32
|
+
},
|
|
33
|
+
onError: () => setDeleteError(i18n.t('Delete failed')),
|
|
28
34
|
variables: {
|
|
29
35
|
commentId,
|
|
30
36
|
interpretationId
|
|
31
37
|
}
|
|
32
38
|
});
|
|
33
|
-
|
|
39
|
+
const onDelete = () => {
|
|
40
|
+
setDeleteError(null);
|
|
41
|
+
remove();
|
|
42
|
+
};
|
|
43
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
44
|
+
className: _JSXStyle.dynamic([["945681082", [colors.red500]]]) + " " + "delete-button-container"
|
|
45
|
+
}, /*#__PURE__*/React.createElement(MessageIconButton, {
|
|
34
46
|
tooltipContent: i18n.t('Delete'),
|
|
35
47
|
iconComponent: IconDelete16,
|
|
36
|
-
onClick:
|
|
48
|
+
onClick: onDelete,
|
|
37
49
|
disabled: loading
|
|
38
|
-
})
|
|
50
|
+
}), deleteError && /*#__PURE__*/React.createElement("span", {
|
|
51
|
+
className: _JSXStyle.dynamic([["945681082", [colors.red500]]]) + " " + "delete-error"
|
|
52
|
+
}, deleteError), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
53
|
+
id: "945681082",
|
|
54
|
+
dynamic: [colors.red500]
|
|
55
|
+
}, [".delete-button-container.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px;}", `.delete-error.__jsx-style-dynamic-selector{color:${colors.red500};font-size:12px;line-height:12px;}`]));
|
|
39
56
|
};
|
|
40
57
|
CommentDeleteButton.propTypes = {
|
|
41
58
|
commentId: PropTypes.string.isRequired,
|
|
@@ -35,7 +35,7 @@ export const CommentUpdateForm = _ref => {
|
|
|
35
35
|
close();
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
|
-
const errorText = error ?
|
|
38
|
+
const errorText = error ? i18n.t('Could not update comment') : '';
|
|
39
39
|
return /*#__PURE__*/React.createElement("div", {
|
|
40
40
|
className: _JSXStyle.dynamic([["2690082310", [spacers.dp8, spacers.dp8, colors.grey100]]]) + " " + "message"
|
|
41
41
|
}, /*#__PURE__*/React.createElement(MessageEditorContainer, {
|
|
@@ -32,7 +32,7 @@ const query = {
|
|
|
32
32
|
return id;
|
|
33
33
|
},
|
|
34
34
|
params: {
|
|
35
|
-
fields: ['access', 'id', 'text', 'created', '
|
|
35
|
+
fields: ['access[write,manage]', 'id', 'text', 'created', 'createdBy[id,displayName]', 'likes', 'likedBy', 'comments[id,text,created,createdBy[id,displayName]]']
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
};
|
|
@@ -4,7 +4,7 @@ import cx from 'classnames';
|
|
|
4
4
|
import moment from 'moment';
|
|
5
5
|
import PropTypes from 'prop-types';
|
|
6
6
|
import React, { useRef, useEffect } from 'react';
|
|
7
|
-
import { Interpretation } from '../common/index.js';
|
|
7
|
+
import { Interpretation, getInterpretationAccess } from '../common/index.js';
|
|
8
8
|
import { Comment } from './Comment.js';
|
|
9
9
|
import { CommentAddForm } from './CommentAddForm.js';
|
|
10
10
|
const InterpretationThread = _ref => {
|
|
@@ -25,6 +25,7 @@ const InterpretationThread = _ref => {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
}, [initialFocus]);
|
|
28
|
+
const interpretationAccess = getInterpretationAccess(interpretation, currentUser);
|
|
28
29
|
return /*#__PURE__*/React.createElement("div", {
|
|
29
30
|
className: "jsx-615306698" + " " + (cx('container', {
|
|
30
31
|
fetching
|
|
@@ -43,12 +44,13 @@ const InterpretationThread = _ref => {
|
|
|
43
44
|
}, /*#__PURE__*/React.createElement(Interpretation, {
|
|
44
45
|
currentUser: currentUser,
|
|
45
46
|
interpretation: interpretation,
|
|
46
|
-
onReplyIconClick: () => {
|
|
47
|
+
onReplyIconClick: interpretationAccess.comment ? () => {
|
|
47
48
|
var _focusRef$current;
|
|
48
49
|
return (_focusRef$current = focusRef.current) === null || _focusRef$current === void 0 ? void 0 : _focusRef$current.focus();
|
|
49
|
-
},
|
|
50
|
+
} : null,
|
|
50
51
|
onUpdated: () => onThreadUpdated(true),
|
|
51
|
-
onDeleted: onInterpretationDeleted
|
|
52
|
+
onDeleted: onInterpretationDeleted,
|
|
53
|
+
isInThread: true
|
|
52
54
|
}), /*#__PURE__*/React.createElement("div", {
|
|
53
55
|
className: "jsx-615306698" + " " + 'comments'
|
|
54
56
|
}, interpretation.comments.map(comment => /*#__PURE__*/React.createElement(Comment, {
|
|
@@ -56,8 +58,9 @@ const InterpretationThread = _ref => {
|
|
|
56
58
|
comment: comment,
|
|
57
59
|
currentUser: currentUser,
|
|
58
60
|
interpretationId: interpretation.id,
|
|
59
|
-
onThreadUpdated: onThreadUpdated
|
|
60
|
-
|
|
61
|
+
onThreadUpdated: onThreadUpdated,
|
|
62
|
+
canComment: interpretationAccess.comment
|
|
63
|
+
}))), interpretationAccess.comment && /*#__PURE__*/React.createElement(CommentAddForm, {
|
|
61
64
|
currentUser: currentUser,
|
|
62
65
|
interpretationId: interpretation.id,
|
|
63
66
|
onSave: () => onThreadUpdated(true),
|
|
@@ -16,7 +16,7 @@ const interpretationsQuery = {
|
|
|
16
16
|
id
|
|
17
17
|
} = _ref;
|
|
18
18
|
return {
|
|
19
|
-
fields: ['access', 'id', '
|
|
19
|
+
fields: ['access[write,manage]', 'id', 'createdBy[id,displayName]', 'created', 'text', 'comments[id]', 'likes', 'likedBy[id]'],
|
|
20
20
|
filter: `${type}.id:eq:${id}`
|
|
21
21
|
};
|
|
22
22
|
}
|
|
@@ -2,7 +2,7 @@ import i18n from '@dhis2/d2-i18n';
|
|
|
2
2
|
import { Button, SharingDialog, IconReply16, IconShare16, IconThumbUp16, IconEdit16 } from '@dhis2/ui';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import React, { useState } from 'react';
|
|
5
|
-
import { Message, MessageStatsBar, MessageIconButton } from '../index.js';
|
|
5
|
+
import { Message, MessageStatsBar, MessageIconButton, getInterpretationAccess } from '../index.js';
|
|
6
6
|
import { InterpretationDeleteButton } from './InterpretationDeleteButton.js';
|
|
7
7
|
import { InterpretationUpdateForm } from './InterpretationUpdateForm.js';
|
|
8
8
|
import { useLike } from './useLike.js';
|
|
@@ -14,7 +14,8 @@ export const Interpretation = _ref => {
|
|
|
14
14
|
onUpdated,
|
|
15
15
|
onDeleted,
|
|
16
16
|
disabled,
|
|
17
|
-
onReplyIconClick
|
|
17
|
+
onReplyIconClick,
|
|
18
|
+
isInThread
|
|
18
19
|
} = _ref;
|
|
19
20
|
const [isUpdateMode, setIsUpdateMode] = useState(false);
|
|
20
21
|
const [showSharingDialog, setShowSharingDialog] = useState(false);
|
|
@@ -28,17 +29,30 @@ export const Interpretation = _ref => {
|
|
|
28
29
|
onComplete: onUpdated
|
|
29
30
|
});
|
|
30
31
|
const shouldShowButton = !!onClick && !disabled;
|
|
32
|
+
const interpretationAccess = getInterpretationAccess(interpretation, currentUser);
|
|
33
|
+
let tooltip = i18n.t('Reply');
|
|
34
|
+
if (!interpretationAccess.comment) {
|
|
35
|
+
if (isInThread) {
|
|
36
|
+
tooltip = i18n.t('{{count}} replies', {
|
|
37
|
+
count: interpretation.comments.length,
|
|
38
|
+
defaultValue: '{{count}} reply',
|
|
39
|
+
defaultValue_plural: '{{count}} replies'
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
tooltip = i18n.t('View replies');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
return isUpdateMode ? /*#__PURE__*/React.createElement(InterpretationUpdateForm, {
|
|
32
46
|
close: () => setIsUpdateMode(false),
|
|
33
47
|
id: interpretation.id,
|
|
34
|
-
showSharingLink:
|
|
48
|
+
showSharingLink: interpretationAccess.share,
|
|
35
49
|
onComplete: onUpdated,
|
|
36
50
|
text: interpretation.text,
|
|
37
51
|
currentUser: currentUser
|
|
38
52
|
}) : /*#__PURE__*/React.createElement(Message, {
|
|
39
53
|
text: interpretation.text,
|
|
40
54
|
created: interpretation.created,
|
|
41
|
-
username: interpretation.
|
|
55
|
+
username: interpretation.createdBy.displayName
|
|
42
56
|
}, !disabled && /*#__PURE__*/React.createElement(MessageStatsBar, null, /*#__PURE__*/React.createElement(MessageIconButton, {
|
|
43
57
|
tooltipContent: isLikedByCurrentUser ? i18n.t('Unlike') : i18n.t('Like'),
|
|
44
58
|
iconComponent: IconThumbUp16,
|
|
@@ -48,12 +62,13 @@ export const Interpretation = _ref => {
|
|
|
48
62
|
disabled: toggleLikeInProgress,
|
|
49
63
|
dataTest: "interpretation-like-unlike-button"
|
|
50
64
|
}), /*#__PURE__*/React.createElement(MessageIconButton, {
|
|
51
|
-
tooltipContent:
|
|
65
|
+
tooltipContent: tooltip,
|
|
52
66
|
iconComponent: IconReply16,
|
|
53
|
-
onClick: () => onReplyIconClick(interpretation.id),
|
|
67
|
+
onClick: () => onReplyIconClick && onReplyIconClick(interpretation.id),
|
|
54
68
|
count: interpretation.comments.length,
|
|
55
|
-
dataTest: "interpretation-reply-button"
|
|
56
|
-
|
|
69
|
+
dataTest: "interpretation-reply-button",
|
|
70
|
+
viewOnly: isInThread && !interpretationAccess.comment
|
|
71
|
+
}), interpretationAccess.share && /*#__PURE__*/React.createElement(MessageIconButton, {
|
|
57
72
|
iconComponent: IconShare16,
|
|
58
73
|
tooltipContent: i18n.t('Share'),
|
|
59
74
|
onClick: () => setShowSharingDialog(true),
|
|
@@ -63,15 +78,15 @@ export const Interpretation = _ref => {
|
|
|
63
78
|
type: 'interpretation',
|
|
64
79
|
id: interpretation.id,
|
|
65
80
|
onClose: () => setShowSharingDialog(false)
|
|
66
|
-
}),
|
|
81
|
+
}), /*#__PURE__*/React.createElement(React.Fragment, null, interpretationAccess.edit && /*#__PURE__*/React.createElement(MessageIconButton, {
|
|
67
82
|
iconComponent: IconEdit16,
|
|
68
83
|
tooltipContent: i18n.t('Edit'),
|
|
69
84
|
onClick: () => setIsUpdateMode(true),
|
|
70
85
|
dataTest: "interpretation-edit-button"
|
|
71
|
-
}),
|
|
86
|
+
}), interpretationAccess.delete && /*#__PURE__*/React.createElement(InterpretationDeleteButton, {
|
|
72
87
|
id: interpretation.id,
|
|
73
88
|
onComplete: onDeleted
|
|
74
|
-
})), shouldShowButton && /*#__PURE__*/React.createElement(Button, {
|
|
89
|
+
}))), shouldShowButton && /*#__PURE__*/React.createElement(Button, {
|
|
75
90
|
secondary: true,
|
|
76
91
|
small: true,
|
|
77
92
|
onClick: (_, event) => {
|
|
@@ -87,5 +102,6 @@ Interpretation.propTypes = {
|
|
|
87
102
|
onReplyIconClick: PropTypes.func.isRequired,
|
|
88
103
|
onUpdated: PropTypes.func.isRequired,
|
|
89
104
|
disabled: PropTypes.bool,
|
|
105
|
+
isInThread: PropTypes.bool,
|
|
90
106
|
onClick: PropTypes.func
|
|
91
107
|
};
|
|
@@ -11,7 +11,8 @@ const MessageIconButton = _ref => {
|
|
|
11
11
|
selected,
|
|
12
12
|
count,
|
|
13
13
|
iconComponent: Icon,
|
|
14
|
-
dataTest
|
|
14
|
+
dataTest,
|
|
15
|
+
viewOnly
|
|
15
16
|
} = _ref;
|
|
16
17
|
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
17
18
|
closeDelay: 200,
|
|
@@ -26,7 +27,7 @@ const MessageIconButton = _ref => {
|
|
|
26
27
|
ref: ref,
|
|
27
28
|
onMouseOver: onMouseOver,
|
|
28
29
|
onMouseOut: onMouseOut,
|
|
29
|
-
className: _JSXStyle.dynamic([["
|
|
30
|
+
className: _JSXStyle.dynamic([["163818318", [spacers.dp4, colors.grey700, colors.teal600, colors.grey900, colors.teal800, colors.teal500, colors.teal700, theme.disabled, theme.disabled]]]) + " " + "wrapper"
|
|
30
31
|
}, /*#__PURE__*/React.createElement("button", {
|
|
31
32
|
onClick: event => {
|
|
32
33
|
event.stopPropagation();
|
|
@@ -34,13 +35,14 @@ const MessageIconButton = _ref => {
|
|
|
34
35
|
},
|
|
35
36
|
disabled: disabled,
|
|
36
37
|
"data-test": dataTest,
|
|
37
|
-
className: _JSXStyle.dynamic([["
|
|
38
|
-
selected
|
|
38
|
+
className: _JSXStyle.dynamic([["163818318", [spacers.dp4, colors.grey700, colors.teal600, colors.grey900, colors.teal800, colors.teal500, colors.teal700, theme.disabled, theme.disabled]]]) + " " + (cx('button', {
|
|
39
|
+
selected,
|
|
40
|
+
viewOnly
|
|
39
41
|
}) || "")
|
|
40
42
|
}, count && count, /*#__PURE__*/React.createElement(Icon, null)), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
41
|
-
id: "
|
|
43
|
+
id: "163818318",
|
|
42
44
|
dynamic: [spacers.dp4, colors.grey700, colors.teal600, colors.grey900, colors.teal800, colors.teal500, colors.teal700, theme.disabled, theme.disabled]
|
|
43
|
-
}, [".wrapper.__jsx-style-dynamic-selector{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;}", `.button.__jsx-style-dynamic-selector{all:unset;cursor:pointer;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:${spacers.dp4};-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:12px;line-height:14px;color:${colors.grey700};}`, `.button.selected.__jsx-style-dynamic-selector{color:${colors.teal600};font-weight:500;}`, `.button.__jsx-style-dynamic-selector:hover{color:${colors.grey900};}`, `.button.selected.__jsx-style-dynamic-selector:hover{color:${colors.teal800};}`, `.button.selected.__jsx-style-dynamic-selector svg{color:${colors.teal500};}`, `.button.selected.__jsx-style-dynamic-selector:hover svg{color:${colors.teal700};}`, `.button.__jsx-style-dynamic-selector:disabled{color:${theme.disabled};cursor:not-allowed;}`, `.button.__jsx-style-dynamic-selector:disabled svg{color:${theme.disabled};}`]));
|
|
45
|
+
}, [".wrapper.__jsx-style-dynamic-selector{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;}", `.button.__jsx-style-dynamic-selector{all:unset;cursor:pointer;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:${spacers.dp4};-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:12px;line-height:14px;color:${colors.grey700};}`, ".viewOnly.__jsx-style-dynamic-selector{cursor:default;}", `.button.selected.__jsx-style-dynamic-selector{color:${colors.teal600};font-weight:500;}`, `.button.__jsx-style-dynamic-selector:hover{color:${colors.grey900};}`, `.button.selected.__jsx-style-dynamic-selector:hover{color:${colors.teal800};}`, `.button.selected.__jsx-style-dynamic-selector svg{color:${colors.teal500};}`, `.button.selected.__jsx-style-dynamic-selector:hover svg{color:${colors.teal700};}`, `.button.__jsx-style-dynamic-selector:disabled{color:${theme.disabled};cursor:not-allowed;}`, `.button.__jsx-style-dynamic-selector:disabled svg{color:${theme.disabled};}`]));
|
|
44
46
|
});
|
|
45
47
|
};
|
|
46
48
|
MessageIconButton.propTypes = {
|
|
@@ -50,6 +52,7 @@ MessageIconButton.propTypes = {
|
|
|
50
52
|
dataTest: PropTypes.string,
|
|
51
53
|
disabled: PropTypes.bool,
|
|
52
54
|
selected: PropTypes.bool,
|
|
55
|
+
viewOnly: PropTypes.bool,
|
|
53
56
|
onClick: PropTypes.func
|
|
54
57
|
};
|
|
55
58
|
export { MessageIconButton };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getInterpretationAccess, getCommentAccess } from '../getInterpretationAccess.js';
|
|
2
|
+
const superuser = {
|
|
3
|
+
id: 'iamsuper',
|
|
4
|
+
authorities: new Set(['ALL'])
|
|
5
|
+
};
|
|
6
|
+
const userJoe = {
|
|
7
|
+
id: 'johndoe',
|
|
8
|
+
authorities: new Set(['Some'])
|
|
9
|
+
};
|
|
10
|
+
const userJane = {
|
|
11
|
+
id: 'jane',
|
|
12
|
+
authorities: new Set(['Some'])
|
|
13
|
+
};
|
|
14
|
+
describe('interpretation and comment access', () => {
|
|
15
|
+
describe('getInterpretationAccess', () => {
|
|
16
|
+
it('returns true for all accesses for superuser', () => {
|
|
17
|
+
const interpretation = {
|
|
18
|
+
access: {
|
|
19
|
+
write: true,
|
|
20
|
+
manage: true
|
|
21
|
+
},
|
|
22
|
+
createdBy: userJoe
|
|
23
|
+
};
|
|
24
|
+
expect(getInterpretationAccess(interpretation, superuser)).toMatchObject({
|
|
25
|
+
share: true,
|
|
26
|
+
comment: true,
|
|
27
|
+
edit: true,
|
|
28
|
+
delete: true
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
it('returns true for all accesses for creator', () => {
|
|
32
|
+
const interpretation = {
|
|
33
|
+
access: {
|
|
34
|
+
write: true,
|
|
35
|
+
manage: true
|
|
36
|
+
},
|
|
37
|
+
createdBy: userJoe
|
|
38
|
+
};
|
|
39
|
+
expect(getInterpretationAccess(interpretation, userJoe)).toMatchObject({
|
|
40
|
+
share: true,
|
|
41
|
+
comment: true,
|
|
42
|
+
edit: true,
|
|
43
|
+
delete: true
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it('returns false for edit/delete if user is not creator/superuser', () => {
|
|
47
|
+
const interpretation = {
|
|
48
|
+
access: {
|
|
49
|
+
write: true,
|
|
50
|
+
manage: true
|
|
51
|
+
},
|
|
52
|
+
createdBy: userJane
|
|
53
|
+
};
|
|
54
|
+
expect(getInterpretationAccess(interpretation, userJoe)).toMatchObject({
|
|
55
|
+
share: true,
|
|
56
|
+
comment: true,
|
|
57
|
+
edit: false,
|
|
58
|
+
delete: false
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it('returns false for comment/edit/delete if user is not creator/superuser and no write access', () => {
|
|
62
|
+
const interpretation = {
|
|
63
|
+
access: {
|
|
64
|
+
write: false,
|
|
65
|
+
manage: true
|
|
66
|
+
},
|
|
67
|
+
createdBy: userJane
|
|
68
|
+
};
|
|
69
|
+
expect(getInterpretationAccess(interpretation, userJoe)).toMatchObject({
|
|
70
|
+
share: true,
|
|
71
|
+
comment: false,
|
|
72
|
+
edit: false,
|
|
73
|
+
delete: false
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it('returns false for share/comment/edit/delete if user is not creator/superuser and no write or manage access', () => {
|
|
77
|
+
const interpretation = {
|
|
78
|
+
access: {
|
|
79
|
+
write: false,
|
|
80
|
+
manage: false
|
|
81
|
+
},
|
|
82
|
+
createdBy: userJane
|
|
83
|
+
};
|
|
84
|
+
expect(getInterpretationAccess(interpretation, userJoe)).toMatchObject({
|
|
85
|
+
share: false,
|
|
86
|
+
comment: false,
|
|
87
|
+
edit: false,
|
|
88
|
+
delete: false
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('getCommentAccess', () => {
|
|
93
|
+
it('returns true for all accesses for superuser', () => {
|
|
94
|
+
const interpretation = {
|
|
95
|
+
access: {
|
|
96
|
+
write: true
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const comment = {
|
|
100
|
+
createdBy: userJoe
|
|
101
|
+
};
|
|
102
|
+
expect(getCommentAccess(comment, interpretation.access.write, superuser)).toMatchObject({
|
|
103
|
+
edit: true,
|
|
104
|
+
delete: true
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it('returns true for all accesses for creator when interpretation has write access', () => {
|
|
108
|
+
const interpretation = {
|
|
109
|
+
access: {
|
|
110
|
+
write: true
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const comment = {
|
|
114
|
+
createdBy: userJoe
|
|
115
|
+
};
|
|
116
|
+
expect(getCommentAccess(comment, interpretation.access.write, userJoe)).toMatchObject({
|
|
117
|
+
edit: true,
|
|
118
|
+
delete: true
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
it('returns true for edit and false for delete for creator when interpretation does not have write access', () => {
|
|
122
|
+
const interpretation = {
|
|
123
|
+
access: {
|
|
124
|
+
write: false
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const comment = {
|
|
128
|
+
createdBy: userJoe
|
|
129
|
+
};
|
|
130
|
+
expect(getCommentAccess(comment, interpretation.access.write, userJoe)).toMatchObject({
|
|
131
|
+
edit: true,
|
|
132
|
+
delete: false
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
it('returns false for edit/delete for user who is not creator or superuser', () => {
|
|
136
|
+
const interpretation = {
|
|
137
|
+
access: {
|
|
138
|
+
write: true
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const comment = {
|
|
142
|
+
createdBy: userJane
|
|
143
|
+
};
|
|
144
|
+
expect(getCommentAccess(comment, interpretation.access.write, userJoe)).toMatchObject({
|
|
145
|
+
edit: false,
|
|
146
|
+
delete: false
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const isCreatorOrSuperuser = (object, currentUser) => (object === null || object === void 0 ? void 0 : object.createdBy.id) === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id) || (currentUser === null || currentUser === void 0 ? void 0 : currentUser.authorities.has('ALL'));
|
|
2
|
+
export const getInterpretationAccess = (interpretation, currentUser) => {
|
|
3
|
+
const canEditDelete = isCreatorOrSuperuser(interpretation, currentUser);
|
|
4
|
+
return {
|
|
5
|
+
share: interpretation.access.manage,
|
|
6
|
+
comment: interpretation.access.write,
|
|
7
|
+
edit: canEditDelete,
|
|
8
|
+
delete: canEditDelete
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export const getCommentAccess = (comment, hasInterpretationReplyAccess, currentUser) => {
|
|
12
|
+
const canEditDelete = isCreatorOrSuperuser(comment, currentUser);
|
|
13
|
+
return {
|
|
14
|
+
edit: canEditDelete,
|
|
15
|
+
delete: canEditDelete && hasInterpretationReplyAccess
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"Edit": "Edit",
|
|
108
108
|
"Write a reply": "Write a reply",
|
|
109
109
|
"Post reply": "Post reply",
|
|
110
|
+
"Delete failed": "Delete failed",
|
|
110
111
|
"Could not update comment": "Could not update comment",
|
|
111
112
|
"Enter comment text": "Enter comment text",
|
|
112
113
|
"Update": "Update",
|
|
@@ -117,9 +118,12 @@
|
|
|
117
118
|
"Write an interpretation": "Write an interpretation",
|
|
118
119
|
"Post interpretation": "Post interpretation",
|
|
119
120
|
"Interpretations": "Interpretations",
|
|
121
|
+
"Reply": "Reply",
|
|
122
|
+
"{{count}} replies": "{{count}} reply",
|
|
123
|
+
"{{count}} replies_plural": "{{count}} replies",
|
|
124
|
+
"View replies": "View replies",
|
|
120
125
|
"Unlike": "Unlike",
|
|
121
126
|
"Like": "Like",
|
|
122
|
-
"Reply": "Reply",
|
|
123
127
|
"Share": "Share",
|
|
124
128
|
"See interpretation": "See interpretation",
|
|
125
129
|
"Manage sharing": "Manage sharing",
|