tycho-components 0.42.1 → 0.42.3
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/dist/configs/localization/CommentsTexts.d.ts +30 -0
- package/dist/configs/localization/CommentsTexts.js +30 -0
- package/dist/features/Comments/CommentDetails/CommentDetails.d.ts +8 -0
- package/dist/features/Comments/CommentDetails/CommentDetails.js +15 -0
- package/dist/features/Comments/CommentDetails/index.d.ts +2 -0
- package/dist/features/Comments/CommentDetails/index.js +2 -0
- package/dist/features/Comments/CommentDetails/styles.scss +87 -0
- package/dist/features/Comments/{CommentInfo.d.ts → CommentInfo/CommentInfo.d.ts} +2 -2
- package/dist/features/Comments/CommentInfo/CommentInfo.js +74 -0
- package/dist/features/Comments/CommentInfo/index.d.ts +2 -0
- package/dist/features/Comments/CommentInfo/index.js +2 -0
- package/dist/features/Comments/CommentInfo/styles.scss +95 -0
- package/dist/features/Comments/CommentService.d.ts +4 -0
- package/dist/features/Comments/CommentService.js +12 -0
- package/dist/features/Comments/CommentService.mock.js +1 -0
- package/dist/features/Comments/{CommentThread.d.ts → CommentThread/CommentThread.d.ts} +2 -1
- package/dist/features/Comments/{CommentThread.js → CommentThread/CommentThread.js} +3 -2
- package/dist/features/Comments/CommentThread/index.d.ts +2 -0
- package/dist/features/Comments/CommentThread/index.js +2 -0
- package/dist/features/Comments/CommentThread/styles.scss +16 -0
- package/dist/features/Comments/Comments.js +2 -51
- package/dist/features/Comments/HeaderNotifications/HeaderNotifications.js +27 -7
- package/dist/features/Comments/style.scss +0 -110
- package/dist/features/Comments/types/Comment.d.ts +2 -0
- package/dist/features/Comments/useCommentsResize.d.ts +7 -0
- package/dist/features/Comments/useCommentsResize.js +60 -0
- package/package.json +3 -3
- package/dist/features/Comments/CommentInfo.js +0 -35
|
@@ -15,6 +15,15 @@ export declare const CommentsTexts: {
|
|
|
15
15
|
"tooltip.delete": string;
|
|
16
16
|
"tooltip.read": string;
|
|
17
17
|
"tooltip.reply": string;
|
|
18
|
+
"tooltip.resolve": string;
|
|
19
|
+
"tooltip.details": string;
|
|
20
|
+
"modal.title.details": string;
|
|
21
|
+
"label.author": string;
|
|
22
|
+
"label.edited.on": string;
|
|
23
|
+
"label.read": string;
|
|
24
|
+
"label.unread": string;
|
|
25
|
+
"label.read.on": string;
|
|
26
|
+
"label.requested": string;
|
|
18
27
|
"modal.title.footnote": string;
|
|
19
28
|
"placeholder.footnote.text": string;
|
|
20
29
|
"modal.title.footnote.remove": string;
|
|
@@ -31,6 +40,7 @@ export declare const CommentsTexts: {
|
|
|
31
40
|
"notification.empty": string;
|
|
32
41
|
"notification.text": string;
|
|
33
42
|
"label.edited": string;
|
|
43
|
+
"label.resolved": string;
|
|
34
44
|
"tab.write": string;
|
|
35
45
|
"tab.preview": string;
|
|
36
46
|
"placeholder.preview.empty": string;
|
|
@@ -55,6 +65,15 @@ export declare const CommentsTexts: {
|
|
|
55
65
|
"tooltip.delete": string;
|
|
56
66
|
"tooltip.read": string;
|
|
57
67
|
"tooltip.reply": string;
|
|
68
|
+
"tooltip.resolve": string;
|
|
69
|
+
"tooltip.details": string;
|
|
70
|
+
"modal.title.details": string;
|
|
71
|
+
"label.author": string;
|
|
72
|
+
"label.edited.on": string;
|
|
73
|
+
"label.read": string;
|
|
74
|
+
"label.unread": string;
|
|
75
|
+
"label.read.on": string;
|
|
76
|
+
"label.requested": string;
|
|
58
77
|
"modal.title.footnote": string;
|
|
59
78
|
"placeholder.footnote.text": string;
|
|
60
79
|
"modal.title.footnote.remove": string;
|
|
@@ -71,6 +90,7 @@ export declare const CommentsTexts: {
|
|
|
71
90
|
"notification.empty": string;
|
|
72
91
|
"notification.text": string;
|
|
73
92
|
"label.edited": string;
|
|
93
|
+
"label.resolved": string;
|
|
74
94
|
"tab.write": string;
|
|
75
95
|
"tab.preview": string;
|
|
76
96
|
"placeholder.preview.empty": string;
|
|
@@ -95,6 +115,15 @@ export declare const CommentsTexts: {
|
|
|
95
115
|
"tooltip.delete": string;
|
|
96
116
|
"tooltip.read": string;
|
|
97
117
|
"tooltip.reply": string;
|
|
118
|
+
"tooltip.resolve": string;
|
|
119
|
+
"tooltip.details": string;
|
|
120
|
+
"modal.title.details": string;
|
|
121
|
+
"label.author": string;
|
|
122
|
+
"label.edited.on": string;
|
|
123
|
+
"label.read": string;
|
|
124
|
+
"label.unread": string;
|
|
125
|
+
"label.read.on": string;
|
|
126
|
+
"label.requested": string;
|
|
98
127
|
"modal.title.footnote": string;
|
|
99
128
|
"placeholder.footnote.text": string;
|
|
100
129
|
"modal.title.footnote.remove": string;
|
|
@@ -111,6 +140,7 @@ export declare const CommentsTexts: {
|
|
|
111
140
|
"notification.empty": string;
|
|
112
141
|
"notification.text": string;
|
|
113
142
|
"label.edited": string;
|
|
143
|
+
"label.resolved": string;
|
|
114
144
|
"tab.write": string;
|
|
115
145
|
"tab.preview": string;
|
|
116
146
|
"placeholder.preview.empty": string;
|
|
@@ -15,6 +15,15 @@ export const CommentsTexts = {
|
|
|
15
15
|
"tooltip.delete": "Remove comment",
|
|
16
16
|
"tooltip.read": "Mark as read",
|
|
17
17
|
"tooltip.reply": "Reply",
|
|
18
|
+
"tooltip.resolve": "Mark as resolved",
|
|
19
|
+
"tooltip.details": "See detailed information",
|
|
20
|
+
"modal.title.details": "Comment details",
|
|
21
|
+
"label.author": "Author",
|
|
22
|
+
"label.edited.on": "Edited on:",
|
|
23
|
+
"label.read": "Read",
|
|
24
|
+
"label.unread": "Unread",
|
|
25
|
+
"label.read.on": "Read on:",
|
|
26
|
+
"label.requested": "Requested",
|
|
18
27
|
"modal.title.footnote": "Edit footnote",
|
|
19
28
|
"placeholder.footnote.text": "Enter footnote",
|
|
20
29
|
"modal.title.footnote.remove": "Confirm footnote removal",
|
|
@@ -31,6 +40,7 @@ export const CommentsTexts = {
|
|
|
31
40
|
"notification.empty": "No more notifications",
|
|
32
41
|
"notification.text": "{{user}} added a new comment.",
|
|
33
42
|
"label.edited": "edited",
|
|
43
|
+
"label.resolved": "resolved",
|
|
34
44
|
"tab.write": "Write",
|
|
35
45
|
"tab.preview": "Preview",
|
|
36
46
|
"placeholder.preview.empty": "Nothing to preview",
|
|
@@ -55,6 +65,15 @@ export const CommentsTexts = {
|
|
|
55
65
|
"tooltip.delete": "Remover comentário",
|
|
56
66
|
"tooltip.read": "Marcar como lido",
|
|
57
67
|
"tooltip.reply": "Responder",
|
|
68
|
+
"tooltip.resolve": "Marcar como resolvido",
|
|
69
|
+
"tooltip.details": "Ver informações detalhadas",
|
|
70
|
+
"modal.title.details": "Detalhes do comentário",
|
|
71
|
+
"label.author": "Autor",
|
|
72
|
+
"label.edited.on": "Editado em:",
|
|
73
|
+
"label.read": "Lido",
|
|
74
|
+
"label.unread": "Não lido",
|
|
75
|
+
"label.read.on": "Lido em:",
|
|
76
|
+
"label.requested": "Solicitados",
|
|
58
77
|
"modal.title.footnote": "Editar nota de rodapé",
|
|
59
78
|
"placeholder.footnote.text": "Entre o texto da nota de rodapé",
|
|
60
79
|
"modal.title.footnote.remove": "Confirmação de exclusão de nota de rodapé",
|
|
@@ -71,6 +90,7 @@ export const CommentsTexts = {
|
|
|
71
90
|
"notification.empty": "Não há notificações",
|
|
72
91
|
"notification.text": "{{user}} adicionou um novo comentário.",
|
|
73
92
|
"label.edited": "editado",
|
|
93
|
+
"label.resolved": "resolvido",
|
|
74
94
|
"tab.write": "Escrever",
|
|
75
95
|
"tab.preview": "Visualizar",
|
|
76
96
|
"placeholder.preview.empty": "Nada para visualizar",
|
|
@@ -95,6 +115,15 @@ export const CommentsTexts = {
|
|
|
95
115
|
"tooltip.delete": "Rimuovi commento",
|
|
96
116
|
"tooltip.read": "Segnala come letto",
|
|
97
117
|
"tooltip.reply": "Rispondi",
|
|
118
|
+
"tooltip.resolve": "Segna come risolto",
|
|
119
|
+
"tooltip.details": "Vedi informazioni dettagliate",
|
|
120
|
+
"modal.title.details": "Dettagli del commento",
|
|
121
|
+
"label.author": "Autore",
|
|
122
|
+
"label.edited.on": "Modificato il:",
|
|
123
|
+
"label.read": "Letto",
|
|
124
|
+
"label.unread": "Non letto",
|
|
125
|
+
"label.read.on": "Letto il:",
|
|
126
|
+
"label.requested": "Richiesti",
|
|
98
127
|
"modal.title.footnote": "Modifica nota a piè di pagina",
|
|
99
128
|
"placeholder.footnote.text": "Inserisci nota a piè di pagina",
|
|
100
129
|
"modal.title.footnote.remove": "Conferma rimozione nota a piè di pagina",
|
|
@@ -111,6 +140,7 @@ export const CommentsTexts = {
|
|
|
111
140
|
"notification.empty": "Nessun'altra notifica",
|
|
112
141
|
"notification.text": "{{user}} ha aggiunto un commento all'inserimento {{entry}}.",
|
|
113
142
|
"label.edited": "modificato",
|
|
143
|
+
"label.resolved": "risolto",
|
|
114
144
|
"tab.write": "Scrivi",
|
|
115
145
|
"tab.preview": "Anteprima",
|
|
116
146
|
"placeholder.preview.empty": "Niente da visualizzare",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Avatar, Tag } from 'tycho-storybook';
|
|
4
|
+
import AppModal from '../../../common/AppModal/AppModal';
|
|
5
|
+
import DateUtils from '../../../functions/DateUtils';
|
|
6
|
+
import './styles.scss';
|
|
7
|
+
const DATE_TIME_FORMAT = 'MM/dd/yyyy - HH:mm';
|
|
8
|
+
export default function CommentDetails({ comment, onClose }) {
|
|
9
|
+
const { t } = useTranslation('comments');
|
|
10
|
+
const postedDateTime = DateUtils.formatDateTime(comment.date, DATE_TIME_FORMAT);
|
|
11
|
+
const editedDateTime = comment.edited
|
|
12
|
+
? DateUtils.formatDateTime(comment.edited, DATE_TIME_FORMAT)
|
|
13
|
+
: undefined;
|
|
14
|
+
return (_jsx(AppModal, { title: t('modal.title.details'), close: onClose, hideFooter: true, className: "comment-details-modal", children: _jsxs("div", { className: "comment-details", children: [_jsxs("div", { className: "comment-details-section", children: [_jsxs("div", { className: "comment-details-author", children: [_jsx(Avatar, { title: comment.name, src: comment.picture, size: "small" }), _jsxs("div", { className: "comment-details-author-meta", children: [_jsx("span", { className: "comment-details-label", children: t('label.author') }), _jsx("span", { className: "comment-details-value", children: comment.name })] }), comment.resolved && (_jsx(Tag, { text: t('label.resolved'), color: "green", size: "small" }))] }), _jsxs("div", { className: "comment-details-row", children: [_jsx("span", { className: "comment-details-label", children: t('label.posted.on') }), _jsx("span", { className: "comment-details-label", children: postedDateTime })] }), editedDateTime && (_jsxs("div", { className: "comment-details-row", children: [_jsx("span", { className: "comment-details-label", children: t('label.edited.on') }), _jsx("span", { className: "comment-details-label", children: editedDateTime })] }))] }), comment.requested && comment.requested.length > 0 && (_jsxs("div", { className: "comment-details-section", children: [_jsx("span", { className: "comment-details-section-title", children: t('label.requested') }), _jsx("ul", { className: "comment-details-requested", children: comment.requested.map((user) => (_jsxs("li", { className: "comment-details-requested-item", children: [_jsx(Avatar, { title: user.name, src: user.picture, size: "x-small" }), _jsxs("div", { className: "comment-details-requested-meta", children: [_jsx("span", { className: "comment-details-value", children: user.name }), user.email && (_jsx("span", { className: "comment-details-secondary", children: user.email })), user.read && user.dateRead && (_jsxs("span", { className: "comment-details-secondary", children: [t('label.read.on'), ' ', DateUtils.formatDateTime(user.dateRead, DATE_TIME_FORMAT)] }))] }), _jsx(Tag, { text: user.read ? t('label.read') : t('label.unread'), color: user.read ? 'green' : 'gray', size: "small" })] }, user.uid))) })] }))] }) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.comment-details-modal {
|
|
2
|
+
width: 36vw;
|
|
3
|
+
|
|
4
|
+
> .body {
|
|
5
|
+
padding: 32px 16px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.comment-details {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: var(--spacing-300);
|
|
12
|
+
min-width: min(480px, 80vw);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.comment-details-section {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--spacing-150);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.comment-details-section-title {
|
|
22
|
+
@include label-medium-1;
|
|
23
|
+
color: var(--text-secondary);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.comment-details-author {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: var(--spacing-150);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.comment-details-author-meta {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: var(--spacing-050);
|
|
36
|
+
min-width: 0;
|
|
37
|
+
flex: 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.comment-details-row {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-wrap: wrap;
|
|
43
|
+
gap: var(--spacing-050) var(--spacing-100);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.comment-details-label {
|
|
47
|
+
@include helper-small-1;
|
|
48
|
+
color: var(--text-tertiary);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.comment-details-value {
|
|
52
|
+
@include body-medium-1;
|
|
53
|
+
color: var(--text-primary);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.comment-details-secondary {
|
|
57
|
+
@include helper-small-1;
|
|
58
|
+
color: var(--text-tertiary);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.comment-details-author-meta .comment-details-value {
|
|
62
|
+
@include label-medium-1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.comment-details-requested {
|
|
66
|
+
list-style: none;
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0;
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
gap: var(--spacing-150);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.comment-details-requested-item {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: var(--spacing-150);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.comment-details-requested-meta {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
gap: var(--spacing-050);
|
|
84
|
+
min-width: 0;
|
|
85
|
+
flex: 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import '@uiw/react-markdown-preview/markdown.css';
|
|
3
|
+
import MDEditor from '@uiw/react-md-editor';
|
|
4
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Avatar, DropdownMenu, Tag } from 'tycho-storybook';
|
|
7
|
+
import CommonContext from '../../../configs/CommonContext';
|
|
8
|
+
import DateUtils from '../../../functions/DateUtils';
|
|
9
|
+
import CommentDetails from '../CommentDetails';
|
|
10
|
+
import CommentService from '../CommentService';
|
|
11
|
+
import './styles.scss';
|
|
12
|
+
export default function CommentInfo({ comment, hasEditAccess = true, onRemove, onUpdate, onEdit, onReply, }) {
|
|
13
|
+
const { t } = useTranslation('comments');
|
|
14
|
+
const { state } = useContext(CommonContext);
|
|
15
|
+
const markedReadRef = useRef(false);
|
|
16
|
+
const [showDetails, setShowDetails] = useState(false);
|
|
17
|
+
const isNotRead = (thisComment) => {
|
|
18
|
+
if (!thisComment.requested)
|
|
19
|
+
return false;
|
|
20
|
+
const requested = thisComment.requested.find((r) => r.uid === state.logged?.uid);
|
|
21
|
+
if (!requested)
|
|
22
|
+
return false;
|
|
23
|
+
return !requested.read;
|
|
24
|
+
};
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!isNotRead(comment) || markedReadRef.current)
|
|
27
|
+
return;
|
|
28
|
+
markedReadRef.current = true;
|
|
29
|
+
CommentService.markRead(comment.id).then((r) => onUpdate(r.data));
|
|
30
|
+
}, [comment, onUpdate]);
|
|
31
|
+
const handleRemove = () => {
|
|
32
|
+
CommentService.remove(comment.id).then(() => {
|
|
33
|
+
onRemove();
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const handleResolve = () => {
|
|
37
|
+
CommentService.resolve(comment.id).then((r) => onUpdate(r.data));
|
|
38
|
+
};
|
|
39
|
+
const isAuthor = hasEditAccess && state.logged?.uid === comment.user;
|
|
40
|
+
const menuOptions = [];
|
|
41
|
+
menuOptions.push({
|
|
42
|
+
label: t('tooltip.details'),
|
|
43
|
+
icon: 'info',
|
|
44
|
+
onClick: () => setShowDetails(true),
|
|
45
|
+
});
|
|
46
|
+
if (hasEditAccess && !comment.reply) {
|
|
47
|
+
menuOptions.push({
|
|
48
|
+
label: t('tooltip.reply'),
|
|
49
|
+
icon: 'reply',
|
|
50
|
+
onClick: onReply,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (isAuthor) {
|
|
54
|
+
menuOptions.push({
|
|
55
|
+
label: t('tooltip.edit'),
|
|
56
|
+
icon: 'edit',
|
|
57
|
+
onClick: onEdit,
|
|
58
|
+
}, {
|
|
59
|
+
label: t('tooltip.delete'),
|
|
60
|
+
icon: 'delete',
|
|
61
|
+
onClick: handleRemove,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (hasEditAccess && !comment.reply && !comment.resolved) {
|
|
65
|
+
menuOptions.push({
|
|
66
|
+
label: t('tooltip.resolve'),
|
|
67
|
+
icon: 'check',
|
|
68
|
+
onClick: handleResolve,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const displayDate = comment.edited || comment.date;
|
|
72
|
+
const fullDateTitle = DateUtils.formatDateTime(displayDate, 'MM/dd/yyyy - HH:mm');
|
|
73
|
+
return (_jsxs("div", { className: "comment", children: [_jsxs("div", { className: "comment-header", children: [_jsx(Avatar, { title: comment.name, src: comment.picture, size: "x-small" }), _jsxs("div", { className: "meta", children: [_jsx("span", { className: "name", children: comment.name }), _jsxs("span", { className: "timestamp", title: fullDateTitle, children: [t('label.on'), " ", DateUtils.formatDateTime(displayDate, 'MMM d')] })] }), _jsxs("div", { className: "buttons", children: [comment.resolved && (_jsx(Tag, { text: t('label.resolved'), color: "green", size: "small" })), comment.edited && !comment.resolved && (_jsx(Tag, { text: t('label.edited'), color: "blue", size: "small" })), menuOptions.length > 0 && (_jsx(DropdownMenu, { size: "small", className: "comment-menu", list: menuOptions }))] })] }), _jsxs("div", { className: "comment-body", children: [_jsx("div", { className: "text", "data-color-mode": "light", children: _jsx(MDEditor.Markdown, { source: comment.value }) }), comment.requested && (_jsxs("div", { className: "meta-line", children: [_jsx("span", { className: "me-1", children: t('label.requested.to') }), _jsx("span", { children: comment.requested.map((e) => e.name).join(', ') })] }))] }), showDetails && (_jsx(CommentDetails, { comment: comment, onClose: () => setShowDetails(false) }))] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
.comment {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
border: 1px solid var(--border-subtle-1);
|
|
5
|
+
border-radius: var(--radius-200);
|
|
6
|
+
background-color: var(--background-default);
|
|
7
|
+
|
|
8
|
+
.comment-header {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: var(--spacing-100);
|
|
12
|
+
padding: 4px 8px;
|
|
13
|
+
background-color: var(--layer-hover-2);
|
|
14
|
+
border-bottom: 1px solid var(--border-subtle-1);
|
|
15
|
+
border-radius: var(--radius-200) var(--radius-200) 0 0;
|
|
16
|
+
|
|
17
|
+
> .ds-avatar {
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.meta {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: baseline;
|
|
24
|
+
flex-wrap: wrap;
|
|
25
|
+
gap: var(--spacing-050) var(--spacing-100);
|
|
26
|
+
min-width: 0;
|
|
27
|
+
|
|
28
|
+
.name {
|
|
29
|
+
@include label-medium-1;
|
|
30
|
+
color: var(--text-primary);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.timestamp {
|
|
34
|
+
@include helper-small-1;
|
|
35
|
+
color: var(--text-tertiary);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.buttons {
|
|
40
|
+
display: flex;
|
|
41
|
+
margin-left: auto;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
gap: var(--spacing-100);
|
|
44
|
+
position: relative;
|
|
45
|
+
z-index: 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.comment-body {
|
|
50
|
+
padding: var(--spacing-200) var(--spacing-150);
|
|
51
|
+
background-color: var(--background-default);
|
|
52
|
+
border-radius: 0 0 var(--radius-200) var(--radius-200);
|
|
53
|
+
|
|
54
|
+
.text {
|
|
55
|
+
@include body-medium-1;
|
|
56
|
+
color: var(--text-primary);
|
|
57
|
+
word-break: break-word;
|
|
58
|
+
|
|
59
|
+
.wmde-markdown {
|
|
60
|
+
background: transparent;
|
|
61
|
+
font-size: inherit;
|
|
62
|
+
color: inherit;
|
|
63
|
+
|
|
64
|
+
p {
|
|
65
|
+
margin: 0 0 var(--spacing-100);
|
|
66
|
+
|
|
67
|
+
&:last-child {
|
|
68
|
+
margin-bottom: 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pre,
|
|
73
|
+
code {
|
|
74
|
+
border-radius: var(--radius-100);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pre {
|
|
78
|
+
overflow-x: auto;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ul,
|
|
82
|
+
ol {
|
|
83
|
+
margin: 0 0 var(--spacing-100);
|
|
84
|
+
padding-left: var(--spacing-250);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.meta-line {
|
|
90
|
+
@include helper-small-1;
|
|
91
|
+
color: var(--text-tertiary);
|
|
92
|
+
margin-top: var(--spacing-100);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -3,7 +3,9 @@ import { User } from '../../configs/types/User';
|
|
|
3
3
|
import { Comment, CommentRequest } from './types/Comment';
|
|
4
4
|
declare function findNotifications(uid: string, mode: string): Promise<AxiosResponse<Comment[], any, {}, any>>;
|
|
5
5
|
declare function findReadNotifications(uid: string, mode: string): Promise<AxiosResponse<Comment[], any, {}, any>>;
|
|
6
|
+
declare function countUnreadNotifications(uid: string, mode: string): Promise<AxiosResponse<number, any, {}, any>>;
|
|
6
7
|
declare function markRead(id: string): Promise<AxiosResponse<Comment, any, {}, any>>;
|
|
8
|
+
declare function resolve(id: string): Promise<AxiosResponse<Comment, any, {}, any>>;
|
|
7
9
|
declare function update(id: string, comment: CommentRequest): Promise<AxiosResponse<Comment, any, {}, any>>;
|
|
8
10
|
declare function remove(id: string): Promise<AxiosResponse<Comment, any, {}, any>>;
|
|
9
11
|
declare function add(uid: string, mode: string, comment: CommentRequest, references: Record<string, string | number | boolean>, keywords?: Record<string, string | number | boolean>): Promise<AxiosResponse<Comment, any, {}, any>>;
|
|
@@ -14,9 +16,11 @@ declare const CommentService: {
|
|
|
14
16
|
remove: typeof remove;
|
|
15
17
|
update: typeof update;
|
|
16
18
|
markRead: typeof markRead;
|
|
19
|
+
resolve: typeof resolve;
|
|
17
20
|
find: typeof find;
|
|
18
21
|
findNotifications: typeof findNotifications;
|
|
19
22
|
findReadNotifications: typeof findReadNotifications;
|
|
23
|
+
countUnreadNotifications: typeof countUnreadNotifications;
|
|
20
24
|
findAvailableUsers: typeof findAvailableUsers;
|
|
21
25
|
};
|
|
22
26
|
export default CommentService;
|
|
@@ -31,6 +31,9 @@ function findNotifications(uid, mode) {
|
|
|
31
31
|
function findReadNotifications(uid, mode) {
|
|
32
32
|
return api.get(`${import.meta.env.VITE_APP_COMMENT_API_URL}/${mode}/notifications/read/${uid}`);
|
|
33
33
|
}
|
|
34
|
+
function countUnreadNotifications(uid, mode) {
|
|
35
|
+
return api.get(`${import.meta.env.VITE_APP_COMMENT_API_URL}/${mode}/notifications/count/${uid}`);
|
|
36
|
+
}
|
|
34
37
|
function markRead(id) {
|
|
35
38
|
if (StorybookUtils.isStorybook()) {
|
|
36
39
|
storybookComments = storybookComments.map((c) => c.id === id
|
|
@@ -40,6 +43,13 @@ function markRead(id) {
|
|
|
40
43
|
}
|
|
41
44
|
return api.put(`${import.meta.env.VITE_APP_COMMENT_API_URL}/read/${id}`);
|
|
42
45
|
}
|
|
46
|
+
function resolve(id) {
|
|
47
|
+
if (StorybookUtils.isStorybook()) {
|
|
48
|
+
storybookComments = storybookComments.map((c) => c.id === id ? { ...c, resolved: true } : c);
|
|
49
|
+
return mockResponse(storybookComments.find((c) => c.id === id));
|
|
50
|
+
}
|
|
51
|
+
return api.put(`${import.meta.env.VITE_APP_COMMENT_API_URL}/resolve/${id}`);
|
|
52
|
+
}
|
|
43
53
|
function update(id, comment) {
|
|
44
54
|
if (StorybookUtils.isStorybook()) {
|
|
45
55
|
storybookComments = storybookComments.map((c) => {
|
|
@@ -106,9 +116,11 @@ const CommentService = {
|
|
|
106
116
|
remove,
|
|
107
117
|
update,
|
|
108
118
|
markRead,
|
|
119
|
+
resolve,
|
|
109
120
|
find,
|
|
110
121
|
findNotifications,
|
|
111
122
|
findReadNotifications,
|
|
123
|
+
countUnreadNotifications,
|
|
112
124
|
findAvailableUsers,
|
|
113
125
|
};
|
|
114
126
|
export default CommentService;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import UsabilityUtils from '
|
|
3
|
-
import CommentInfo from '
|
|
2
|
+
import UsabilityUtils from '../../../functions/UsabilityUtils';
|
|
3
|
+
import CommentInfo from '../CommentInfo';
|
|
4
|
+
import './styles.scss';
|
|
4
5
|
export default function CommentThread({ comments, hasEditAccess, onRemove, onUpdate, onEdit, onReply, }) {
|
|
5
6
|
const hasReplies = (thisComment) => comments.some((c) => c.reply === thisComment.id);
|
|
6
7
|
const getReplies = (thisComment) => comments.filter((c) => c.reply === thisComment.id);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.thread {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--spacing-200);
|
|
5
|
+
|
|
6
|
+
& + .thread {
|
|
7
|
+
margin-top: var(--spacing-100);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.replies {
|
|
11
|
+
margin-left: var(--spacing-400);
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: var(--spacing-100);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -13,6 +13,7 @@ import CommentService from './CommentService';
|
|
|
13
13
|
import CommentThread from './CommentThread';
|
|
14
14
|
import CommentUtils from './CommentUtils';
|
|
15
15
|
import './style.scss';
|
|
16
|
+
import useCommentsResize from './useCommentsResize';
|
|
16
17
|
function CommentsPanel({ dockPosition, showResizeHandle, onResizePointerDown, children, }) {
|
|
17
18
|
const isHorizontal = dockPosition === 'bottom';
|
|
18
19
|
return (_jsxs("div", { className: cx('comments-container', {
|
|
@@ -33,9 +34,8 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
33
34
|
const [comments, setComments] = useState();
|
|
34
35
|
const [users, setUsers] = useState([]);
|
|
35
36
|
const [reply, setReply] = useState();
|
|
36
|
-
const [drawerWidth, setDrawerWidth] = useState(CommentUtils.readStoredDrawerWidth);
|
|
37
|
-
const [drawerHeight, setDrawerHeight] = useState(CommentUtils.readStoredDrawerHeight);
|
|
38
37
|
const [dockPosition, setDockPosition] = useState(CommentUtils.readStoredDockPosition);
|
|
38
|
+
const { drawerWidth, drawerHeight, handleResizePointerDown } = useCommentsResize(dockPosition);
|
|
39
39
|
const [externalContainer, setExternalContainer] = useState(null);
|
|
40
40
|
const commentAddRef = useRef(null);
|
|
41
41
|
const popupRef = useRef(null);
|
|
@@ -209,55 +209,6 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
209
209
|
closePopup();
|
|
210
210
|
};
|
|
211
211
|
}, [closePopup]);
|
|
212
|
-
const handleResizePointerDown = useCallback((event) => {
|
|
213
|
-
event.preventDefault();
|
|
214
|
-
const handle = event.currentTarget;
|
|
215
|
-
handle.setPointerCapture(event.pointerId);
|
|
216
|
-
const previousUserSelect = document.body.style.userSelect;
|
|
217
|
-
const previousCursor = document.body.style.cursor;
|
|
218
|
-
const isBottom = dockPosition === 'bottom';
|
|
219
|
-
const isLeft = dockPosition === 'left';
|
|
220
|
-
document.body.style.userSelect = 'none';
|
|
221
|
-
document.body.style.cursor = isBottom ? 'row-resize' : 'col-resize';
|
|
222
|
-
const computeSize = (clientX, clientY) => {
|
|
223
|
-
if (isBottom) {
|
|
224
|
-
return CommentUtils.clampDrawerHeight(window.innerHeight - clientY);
|
|
225
|
-
}
|
|
226
|
-
if (isLeft) {
|
|
227
|
-
return CommentUtils.clampDrawerWidth(clientX);
|
|
228
|
-
}
|
|
229
|
-
return CommentUtils.clampDrawerWidth(window.innerWidth - clientX);
|
|
230
|
-
};
|
|
231
|
-
const onPointerMove = (moveEvent) => {
|
|
232
|
-
const next = computeSize(moveEvent.clientX, moveEvent.clientY);
|
|
233
|
-
if (isBottom) {
|
|
234
|
-
setDrawerHeight(next);
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
setDrawerWidth(next);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
const onPointerUp = (upEvent) => {
|
|
241
|
-
const next = computeSize(upEvent.clientX, upEvent.clientY);
|
|
242
|
-
if (isBottom) {
|
|
243
|
-
setDrawerHeight(next);
|
|
244
|
-
CommentUtils.persistDrawerHeight(next);
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
setDrawerWidth(next);
|
|
248
|
-
CommentUtils.persistDrawerWidth(next);
|
|
249
|
-
}
|
|
250
|
-
document.body.style.userSelect = previousUserSelect;
|
|
251
|
-
document.body.style.cursor = previousCursor;
|
|
252
|
-
handle.releasePointerCapture(upEvent.pointerId);
|
|
253
|
-
handle.removeEventListener('pointermove', onPointerMove);
|
|
254
|
-
handle.removeEventListener('pointerup', onPointerUp);
|
|
255
|
-
handle.removeEventListener('pointercancel', onPointerUp);
|
|
256
|
-
};
|
|
257
|
-
handle.addEventListener('pointermove', onPointerMove);
|
|
258
|
-
handle.addEventListener('pointerup', onPointerUp);
|
|
259
|
-
handle.addEventListener('pointercancel', onPointerUp);
|
|
260
|
-
}, [dockPosition]);
|
|
261
212
|
const paperStyle = dockPosition === 'bottom'
|
|
262
213
|
? { height: drawerHeight, width: '100%' }
|
|
263
214
|
: { width: drawerWidth };
|
|
@@ -5,12 +5,14 @@ import { Avatar, IconButton } from 'tycho-storybook';
|
|
|
5
5
|
import DateUtils from '../../../functions/DateUtils';
|
|
6
6
|
import CommentService from '../CommentService';
|
|
7
7
|
import './style.scss';
|
|
8
|
+
const POLL_INTERVAL_MS = 2 * 60 * 1000;
|
|
9
|
+
const READ_NOTIFICATIONS_LIMIT = 5;
|
|
8
10
|
export default function HeaderNotifications({ uid, mode, mobile }) {
|
|
9
11
|
const { t } = useTranslation('comments');
|
|
10
12
|
const [open, setOpen] = useState(false);
|
|
11
13
|
const [visible, setVisible] = useState(true);
|
|
12
14
|
const [notifications, setNotifications] = useState();
|
|
13
|
-
const [
|
|
15
|
+
const [unreadCount, setUnreadCount] = useState(0);
|
|
14
16
|
const [tab, setTab] = useState('unread');
|
|
15
17
|
const handleOpen = (notification) => {
|
|
16
18
|
if (!notification.url)
|
|
@@ -18,27 +20,45 @@ export default function HeaderNotifications({ uid, mode, mobile }) {
|
|
|
18
20
|
location.href = notification.url;
|
|
19
21
|
setOpen(false);
|
|
20
22
|
};
|
|
21
|
-
const
|
|
23
|
+
const loadUnreadCount = () => {
|
|
24
|
+
CommentService.countUnreadNotifications(uid, mode)
|
|
25
|
+
.then((r) => {
|
|
26
|
+
setUnreadCount(r.data ?? 0);
|
|
27
|
+
setVisible(true);
|
|
28
|
+
})
|
|
29
|
+
.catch(() => {
|
|
30
|
+
setVisible(false);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const loadNotifications = () => {
|
|
22
34
|
const serviceMethod = tab === 'read'
|
|
23
35
|
? CommentService.findReadNotifications
|
|
24
36
|
: CommentService.findNotifications;
|
|
25
37
|
serviceMethod(uid, mode)
|
|
26
38
|
.then((r) => {
|
|
39
|
+
const data = tab === 'read' ? r.data.slice(0, READ_NOTIFICATIONS_LIMIT) : r.data;
|
|
40
|
+
setNotifications(data);
|
|
27
41
|
if (tab !== 'read') {
|
|
28
|
-
|
|
42
|
+
setUnreadCount(data.length);
|
|
29
43
|
}
|
|
30
|
-
setNotifications(r.data);
|
|
31
44
|
})
|
|
32
45
|
.catch(() => {
|
|
33
46
|
setVisible(false);
|
|
34
47
|
});
|
|
35
48
|
};
|
|
36
49
|
useEffect(() => {
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
loadUnreadCount();
|
|
51
|
+
const intervalId = window.setInterval(loadUnreadCount, POLL_INTERVAL_MS);
|
|
52
|
+
return () => window.clearInterval(intervalId);
|
|
53
|
+
}, [uid, mode]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!open)
|
|
56
|
+
return;
|
|
57
|
+
loadNotifications();
|
|
58
|
+
}, [open, uid, mode, tab]);
|
|
39
59
|
if (!visible)
|
|
40
60
|
return null;
|
|
41
|
-
return (_jsxs("div", { className: "ds-dropdown-container notifications-container", children: [_jsx(IconButton, { name: "notifications_active", className:
|
|
61
|
+
return (_jsxs("div", { className: "ds-dropdown-container notifications-container", children: [_jsx(IconButton, { name: "notifications_active", className: unreadCount > 0 ? 'shake' : '', size: "large", onClick: () => setOpen(!open), mode: mobile ? 'outlined' : 'filled' }), open && (_jsx("div", { className: "ds-dropdown-list", children: _jsxs("div", { className: "notifications-panel", children: [_jsxs("div", { className: "header", children: [_jsx("span", { children: t('notification.title') }), _jsx(IconButton, { name: "close", size: "small", mode: "ghost", onClick: () => setOpen(false) })] }), _jsxs("div", { className: "tabs", children: [_jsxs("button", { className: tab === 'unread' ? 'active-tab' : '', onClick: () => setTab('unread'), children: [t('notification.unread'), " (", unreadCount, ")"] }), _jsx("button", { className: tab === 'read' ? 'active-tab' : '', onClick: () => setTab('read'), children: t('notification.all') })] }), _jsxs("div", { className: "content", children: [notifications &&
|
|
42
62
|
notifications.map((not, idx) => (_jsxs("div", { className: "item", onClick: () => handleOpen(not), children: [_jsx(Avatar, { src: not.picture, size: "small" }), _jsxs("div", { className: "message", children: [_jsx("span", { className: "text", children: _jsx(Trans, { t: t, i18nKey: "notification.text", values: {
|
|
43
63
|
user: not.name,
|
|
44
64
|
} }) }), _jsx("span", { className: "date", children: DateUtils.formatDateTime(not.edited ? not.edited : not.date, 'MM/dd/yyyy - HH:mm') })] })] }, idx.valueOf()))), notifications && notifications.length === 0 && (_jsx("div", { className: "empty-notifications", children: t('notification.empty') }))] })] }) }))] }));
|
|
@@ -123,116 +123,6 @@
|
|
|
123
123
|
overflow-y: auto;
|
|
124
124
|
padding: var(--spacing-200);
|
|
125
125
|
background-color: var(--background-default);
|
|
126
|
-
|
|
127
|
-
.thread {
|
|
128
|
-
display: flex;
|
|
129
|
-
flex-direction: column;
|
|
130
|
-
gap: var(--spacing-200);
|
|
131
|
-
|
|
132
|
-
& + .thread {
|
|
133
|
-
margin-top: var(--spacing-100);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.comment {
|
|
137
|
-
display: flex;
|
|
138
|
-
flex-direction: column;
|
|
139
|
-
border: 1px solid var(--border-subtle-1);
|
|
140
|
-
border-radius: var(--radius-200);
|
|
141
|
-
overflow: hidden;
|
|
142
|
-
background-color: var(--background-default);
|
|
143
|
-
|
|
144
|
-
.comment-header {
|
|
145
|
-
display: flex;
|
|
146
|
-
align-items: center;
|
|
147
|
-
gap: var(--spacing-100);
|
|
148
|
-
padding: 4px 8px;
|
|
149
|
-
background-color: var(--layer-hover-2);
|
|
150
|
-
border-bottom: 1px solid var(--border-subtle-1);
|
|
151
|
-
|
|
152
|
-
> .ds-avatar {
|
|
153
|
-
flex-shrink: 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.meta {
|
|
157
|
-
display: flex;
|
|
158
|
-
align-items: baseline;
|
|
159
|
-
flex-wrap: wrap;
|
|
160
|
-
gap: var(--spacing-050) var(--spacing-100);
|
|
161
|
-
min-width: 0;
|
|
162
|
-
|
|
163
|
-
.name {
|
|
164
|
-
@include label-medium-1;
|
|
165
|
-
color: var(--text-primary);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.timestamp {
|
|
169
|
-
@include helper-small-1;
|
|
170
|
-
color: var(--text-tertiary);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.buttons {
|
|
175
|
-
display: flex;
|
|
176
|
-
margin-left: auto;
|
|
177
|
-
flex-shrink: 0;
|
|
178
|
-
gap: var(--spacing-100);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.comment-body {
|
|
183
|
-
padding: var(--spacing-200) var(--spacing-150);
|
|
184
|
-
background-color: var(--background-default);
|
|
185
|
-
|
|
186
|
-
.text {
|
|
187
|
-
@include body-medium-1;
|
|
188
|
-
color: var(--text-primary);
|
|
189
|
-
word-break: break-word;
|
|
190
|
-
|
|
191
|
-
.wmde-markdown {
|
|
192
|
-
background: transparent;
|
|
193
|
-
font-size: inherit;
|
|
194
|
-
color: inherit;
|
|
195
|
-
|
|
196
|
-
p {
|
|
197
|
-
margin: 0 0 var(--spacing-100);
|
|
198
|
-
|
|
199
|
-
&:last-child {
|
|
200
|
-
margin-bottom: 0;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
pre,
|
|
205
|
-
code {
|
|
206
|
-
border-radius: var(--radius-100);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
pre {
|
|
210
|
-
overflow-x: auto;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
ul,
|
|
214
|
-
ol {
|
|
215
|
-
margin: 0 0 var(--spacing-100);
|
|
216
|
-
padding-left: var(--spacing-250);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.meta-line {
|
|
222
|
-
@include helper-small-1;
|
|
223
|
-
color: var(--text-tertiary);
|
|
224
|
-
margin-top: var(--spacing-100);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.replies {
|
|
230
|
-
margin-left: var(--spacing-400);
|
|
231
|
-
display: flex;
|
|
232
|
-
flex-direction: column;
|
|
233
|
-
gap: var(--spacing-100);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
126
|
}
|
|
237
127
|
}
|
|
238
128
|
|
|
@@ -9,6 +9,7 @@ export type Comment = {
|
|
|
9
9
|
value: string;
|
|
10
10
|
date: string;
|
|
11
11
|
edited?: string;
|
|
12
|
+
resolved?: boolean;
|
|
12
13
|
name: string;
|
|
13
14
|
user: string;
|
|
14
15
|
picture: string;
|
|
@@ -32,6 +33,7 @@ export type UserComment = {
|
|
|
32
33
|
picture: string;
|
|
33
34
|
email: string;
|
|
34
35
|
read: boolean;
|
|
36
|
+
dateRead?: string;
|
|
35
37
|
};
|
|
36
38
|
export declare const EMPTY_COMMENT: Comment;
|
|
37
39
|
export declare const EMPTY_COMMENT_REQUEST: CommentRequest;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PointerEvent as ReactPointerEvent } from 'react';
|
|
2
|
+
import { type DockPosition } from './CommentUtils';
|
|
3
|
+
export default function useCommentsResize(dockPosition: DockPosition): {
|
|
4
|
+
drawerWidth: number;
|
|
5
|
+
drawerHeight: number;
|
|
6
|
+
handleResizePointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
|
7
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useCallback, useState, } from 'react';
|
|
2
|
+
import CommentUtils from './CommentUtils';
|
|
3
|
+
export default function useCommentsResize(dockPosition) {
|
|
4
|
+
const [drawerWidth, setDrawerWidth] = useState(CommentUtils.readStoredDrawerWidth);
|
|
5
|
+
const [drawerHeight, setDrawerHeight] = useState(CommentUtils.readStoredDrawerHeight);
|
|
6
|
+
const handleResizePointerDown = useCallback((event) => {
|
|
7
|
+
event.preventDefault();
|
|
8
|
+
const handle = event.currentTarget;
|
|
9
|
+
handle.setPointerCapture(event.pointerId);
|
|
10
|
+
const previousUserSelect = document.body.style.userSelect;
|
|
11
|
+
const previousCursor = document.body.style.cursor;
|
|
12
|
+
const isBottom = dockPosition === 'bottom';
|
|
13
|
+
const isLeft = dockPosition === 'left';
|
|
14
|
+
document.body.style.userSelect = 'none';
|
|
15
|
+
document.body.style.cursor = isBottom ? 'row-resize' : 'col-resize';
|
|
16
|
+
const computeSize = (clientX, clientY) => {
|
|
17
|
+
if (isBottom) {
|
|
18
|
+
return CommentUtils.clampDrawerHeight(window.innerHeight - clientY);
|
|
19
|
+
}
|
|
20
|
+
if (isLeft) {
|
|
21
|
+
return CommentUtils.clampDrawerWidth(clientX);
|
|
22
|
+
}
|
|
23
|
+
return CommentUtils.clampDrawerWidth(window.innerWidth - clientX);
|
|
24
|
+
};
|
|
25
|
+
const onPointerMove = (moveEvent) => {
|
|
26
|
+
const next = computeSize(moveEvent.clientX, moveEvent.clientY);
|
|
27
|
+
if (isBottom) {
|
|
28
|
+
setDrawerHeight(next);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
setDrawerWidth(next);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const onPointerUp = (upEvent) => {
|
|
35
|
+
const next = computeSize(upEvent.clientX, upEvent.clientY);
|
|
36
|
+
if (isBottom) {
|
|
37
|
+
setDrawerHeight(next);
|
|
38
|
+
CommentUtils.persistDrawerHeight(next);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
setDrawerWidth(next);
|
|
42
|
+
CommentUtils.persistDrawerWidth(next);
|
|
43
|
+
}
|
|
44
|
+
document.body.style.userSelect = previousUserSelect;
|
|
45
|
+
document.body.style.cursor = previousCursor;
|
|
46
|
+
handle.releasePointerCapture(upEvent.pointerId);
|
|
47
|
+
handle.removeEventListener('pointermove', onPointerMove);
|
|
48
|
+
handle.removeEventListener('pointerup', onPointerUp);
|
|
49
|
+
handle.removeEventListener('pointercancel', onPointerUp);
|
|
50
|
+
};
|
|
51
|
+
handle.addEventListener('pointermove', onPointerMove);
|
|
52
|
+
handle.addEventListener('pointerup', onPointerUp);
|
|
53
|
+
handle.addEventListener('pointercancel', onPointerUp);
|
|
54
|
+
}, [dockPosition]);
|
|
55
|
+
return {
|
|
56
|
+
drawerWidth,
|
|
57
|
+
drawerHeight,
|
|
58
|
+
handleResizePointerDown,
|
|
59
|
+
};
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tycho-components",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.42.
|
|
4
|
+
"version": "0.42.3",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"react-i18next": "^13.0.2",
|
|
86
86
|
"react-router-dom": "^6.14.2",
|
|
87
87
|
"react-toastify": "^9.1.3",
|
|
88
|
-
"tycho-storybook": "0.12.
|
|
88
|
+
"tycho-storybook": "0.12.1",
|
|
89
89
|
"wavesurfer-react": "^2.2.2",
|
|
90
90
|
"wavesurfer.js": "^6.6.3"
|
|
91
91
|
},
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"react-toastify": "^9.1.3",
|
|
116
116
|
"sass-embedded": "^1.97.2",
|
|
117
117
|
"storybook": "^10.1.11",
|
|
118
|
-
"tycho-storybook": "^0.12.
|
|
118
|
+
"tycho-storybook": "^0.12.1",
|
|
119
119
|
"typescript": "^5.7.3",
|
|
120
120
|
"vite": "^7.0.0",
|
|
121
121
|
"vitest": "^3.2.6",
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import MDEditor from '@uiw/react-md-editor';
|
|
3
|
-
import '@uiw/react-markdown-preview/markdown.css';
|
|
4
|
-
import { useContext } from 'react';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { Avatar, IconButton, Tag } from 'tycho-storybook';
|
|
7
|
-
import CommonContext from '../../configs/CommonContext';
|
|
8
|
-
import DateUtils from '../../functions/DateUtils';
|
|
9
|
-
import CommentService from './CommentService';
|
|
10
|
-
import './style.scss';
|
|
11
|
-
export default function CommentInfo({ comment, hasEditAccess = true, onRemove, onUpdate, onEdit, onReply, }) {
|
|
12
|
-
const { t } = useTranslation('comments');
|
|
13
|
-
const { state } = useContext(CommonContext);
|
|
14
|
-
const handleMarkRead = () => {
|
|
15
|
-
CommentService.markRead(comment.id).then((r) => {
|
|
16
|
-
onUpdate(r.data);
|
|
17
|
-
});
|
|
18
|
-
};
|
|
19
|
-
const handleRemove = () => {
|
|
20
|
-
CommentService.remove(comment.id).then(() => {
|
|
21
|
-
onRemove();
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
const isNotRead = (thisComment) => {
|
|
25
|
-
if (!thisComment.requested)
|
|
26
|
-
return false;
|
|
27
|
-
const requested = thisComment.requested.find((r) => r.uid === state.logged?.uid);
|
|
28
|
-
if (!requested)
|
|
29
|
-
return false;
|
|
30
|
-
return !requested.read;
|
|
31
|
-
};
|
|
32
|
-
const displayDate = comment.edited || comment.date;
|
|
33
|
-
const fullDateTitle = DateUtils.formatDateTime(displayDate, 'MM/dd/yyyy - HH:mm');
|
|
34
|
-
return (_jsxs("div", { className: "comment", children: [_jsxs("div", { className: "comment-header", children: [_jsx(Avatar, { title: comment.name, src: comment.picture, size: "x-small" }), _jsxs("div", { className: "meta", children: [_jsx("span", { className: "name", children: comment.name }), _jsxs("span", { className: "timestamp", title: fullDateTitle, children: [t('label.on'), ' ', DateUtils.formatDateTime(displayDate, 'MMM d')] })] }), _jsxs("div", { className: "buttons", children: [comment.edited && (_jsx(Tag, { text: t('label.edited'), color: "blue", size: "small" })), hasEditAccess && !comment.reply && (_jsx(IconButton, { size: "x-small", onClick: onReply, title: t('tooltip.reply'), name: "reply" })), hasEditAccess && state.logged?.uid === comment.user && (_jsxs(_Fragment, { children: [_jsx(IconButton, { size: "x-small", onClick: onEdit, title: t('tooltip.edit'), name: "edit" }), _jsx(IconButton, { size: "x-small", onClick: handleRemove, title: t('tooltip.delete'), name: "delete" })] })), isNotRead(comment) && (_jsx(IconButton, { size: "x-small", onClick: handleMarkRead, title: t('tooltip.read'), name: "check_circle" }))] })] }), _jsxs("div", { className: "comment-body", children: [_jsx("div", { className: "text", "data-color-mode": "light", children: _jsx(MDEditor.Markdown, { source: comment.value }) }), comment.requested && (_jsxs("div", { className: "meta-line", children: [_jsx("span", { className: "me-1", children: t('label.requested.to') }), _jsx("span", { children: comment.requested.map((e) => e.name).join(', ') })] }))] })] }));
|
|
35
|
-
}
|