tycho-components 0.42.0 → 0.42.2

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.
Files changed (28) hide show
  1. package/dist/configs/localization/CommentsTexts.d.ts +30 -0
  2. package/dist/configs/localization/CommentsTexts.js +30 -0
  3. package/dist/features/Comments/CommentDetails/CommentDetails.d.ts +8 -0
  4. package/dist/features/Comments/CommentDetails/CommentDetails.js +15 -0
  5. package/dist/features/Comments/CommentDetails/index.d.ts +2 -0
  6. package/dist/features/Comments/CommentDetails/index.js +2 -0
  7. package/dist/features/Comments/CommentDetails/styles.scss +87 -0
  8. package/dist/features/Comments/{CommentInfo.d.ts → CommentInfo/CommentInfo.d.ts} +2 -2
  9. package/dist/features/Comments/CommentInfo/CommentInfo.js +74 -0
  10. package/dist/features/Comments/CommentInfo/index.d.ts +2 -0
  11. package/dist/features/Comments/CommentInfo/index.js +2 -0
  12. package/dist/features/Comments/CommentInfo/styles.scss +95 -0
  13. package/dist/features/Comments/CommentService.d.ts +2 -0
  14. package/dist/features/Comments/CommentService.js +8 -0
  15. package/dist/features/Comments/CommentService.mock.js +1 -0
  16. package/dist/features/Comments/{CommentThread.d.ts → CommentThread/CommentThread.d.ts} +2 -1
  17. package/dist/features/Comments/{CommentThread.js → CommentThread/CommentThread.js} +3 -2
  18. package/dist/features/Comments/CommentThread/index.d.ts +2 -0
  19. package/dist/features/Comments/CommentThread/index.js +2 -0
  20. package/dist/features/Comments/CommentThread/styles.scss +16 -0
  21. package/dist/features/Comments/Comments.js +2 -51
  22. package/dist/features/Comments/style.scss +0 -110
  23. package/dist/features/Comments/types/Comment.d.ts +2 -0
  24. package/dist/features/Comments/useCommentsResize.d.ts +7 -0
  25. package/dist/features/Comments/useCommentsResize.js +60 -0
  26. package/dist/features/PageRender/collapseElementsForTier.js +115 -3
  27. package/package.json +3 -3
  28. 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,8 @@
1
+ import { Comment } from '../types/Comment';
2
+ import './styles.scss';
3
+ type Props = {
4
+ comment: Comment;
5
+ onClose: () => void;
6
+ };
7
+ export default function CommentDetails({ comment, onClose }: Props): import("react").JSX.Element;
8
+ export {};
@@ -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,2 @@
1
+ import CommentDetails from './CommentDetails';
2
+ export default CommentDetails;
@@ -0,0 +1,2 @@
1
+ import CommentDetails from './CommentDetails';
2
+ export default CommentDetails;
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  import '@uiw/react-markdown-preview/markdown.css';
2
- import './style.scss';
3
- import { Comment } from './types/Comment';
2
+ import { Comment } from '../types/Comment';
3
+ import './styles.scss';
4
4
  type Props = {
5
5
  comment: Comment;
6
6
  hasEditAccess?: boolean;
@@ -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,2 @@
1
+ import CommentInfo from './CommentInfo';
2
+ export default CommentInfo;
@@ -0,0 +1,2 @@
1
+ import CommentInfo from './CommentInfo';
2
+ export default CommentInfo;
@@ -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
+ }
@@ -4,6 +4,7 @@ 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
6
  declare function markRead(id: string): Promise<AxiosResponse<Comment, any, {}, any>>;
7
+ declare function resolve(id: string): Promise<AxiosResponse<Comment, any, {}, any>>;
7
8
  declare function update(id: string, comment: CommentRequest): Promise<AxiosResponse<Comment, any, {}, any>>;
8
9
  declare function remove(id: string): Promise<AxiosResponse<Comment, any, {}, any>>;
9
10
  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,6 +15,7 @@ declare const CommentService: {
14
15
  remove: typeof remove;
15
16
  update: typeof update;
16
17
  markRead: typeof markRead;
18
+ resolve: typeof resolve;
17
19
  find: typeof find;
18
20
  findNotifications: typeof findNotifications;
19
21
  findReadNotifications: typeof findReadNotifications;
@@ -40,6 +40,13 @@ function markRead(id) {
40
40
  }
41
41
  return api.put(`${import.meta.env.VITE_APP_COMMENT_API_URL}/read/${id}`);
42
42
  }
43
+ function resolve(id) {
44
+ if (StorybookUtils.isStorybook()) {
45
+ storybookComments = storybookComments.map((c) => c.id === id ? { ...c, resolved: true } : c);
46
+ return mockResponse(storybookComments.find((c) => c.id === id));
47
+ }
48
+ return api.put(`${import.meta.env.VITE_APP_COMMENT_API_URL}/resolve/${id}`);
49
+ }
43
50
  function update(id, comment) {
44
51
  if (StorybookUtils.isStorybook()) {
45
52
  storybookComments = storybookComments.map((c) => {
@@ -106,6 +113,7 @@ const CommentService = {
106
113
  remove,
107
114
  update,
108
115
  markRead,
116
+ resolve,
109
117
  find,
110
118
  findNotifications,
111
119
  findReadNotifications,
@@ -37,6 +37,7 @@ export const mockComments = [
37
37
  user: 'storybook-user-ana',
38
38
  picture: '',
39
39
  edited: new Date(Date.now() - 86400000).toISOString(),
40
+ resolved: true,
40
41
  requested: [
41
42
  {
42
43
  uid: 'storybook-user-john',
@@ -1,4 +1,5 @@
1
- import { Comment } from './types/Comment';
1
+ import { Comment } from '../types/Comment';
2
+ import './styles.scss';
2
3
  type Props = {
3
4
  comments: Comment[];
4
5
  hasEditAccess: boolean;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import UsabilityUtils from '../../functions/UsabilityUtils';
3
- import CommentInfo from './CommentInfo';
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,2 @@
1
+ import CommentThread from './CommentThread';
2
+ export default CommentThread;
@@ -0,0 +1,2 @@
1
+ import CommentThread from './CommentThread';
2
+ export default CommentThread;
@@ -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 };
@@ -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
+ }
@@ -109,8 +109,9 @@ export function shouldSkipJunctionContinuationElement(elements, index, selectedT
109
109
  if (element.type === PageRenderElementType.JUNCTION_SPLIT &&
110
110
  splitIdx > 0 &&
111
111
  segmented) {
112
- const lastP = getGlobalLastSegmentationPos(elements, ref.uid, segmentationGroup);
113
- if (lastP !== null && ref.p < lastP) {
112
+ // Only hand later splits to a later host when that host also has junction
113
+ // (two-host diplomatic pattern). Seg-only follow-ons must not hide idx>0.
114
+ if (hasLaterJunctionHostInSegmentationGroup(elements, ref.uid, segmentationGroup, ref.p)) {
114
115
  return true;
115
116
  }
116
117
  }
@@ -196,6 +197,30 @@ function getLastSegmentationPosInGroup(elements, start, end) {
196
197
  }
197
198
  return lastP;
198
199
  }
200
+ function groupHasJunctionHost(elements, start, end) {
201
+ for (let i = start; i <= end; i += 1) {
202
+ const ref = elements[i].token;
203
+ if (ref && hasJunctionFromRef(ref)) {
204
+ return true;
205
+ }
206
+ }
207
+ return false;
208
+ }
209
+ function hasLaterJunctionHostInSegmentationGroup(elements, uid, groupKey, currentP) {
210
+ for (const element of elements) {
211
+ if (!isJunctionEditionElement(element))
212
+ continue;
213
+ const ref = element.token;
214
+ if (!sameSegmentationGroupRef(ref, uid, groupKey))
215
+ continue;
216
+ if (!hasSegmentationFromRef(ref))
217
+ continue;
218
+ if (ref.p > currentP) {
219
+ return true;
220
+ }
221
+ }
222
+ return false;
223
+ }
199
224
  export function shouldSkipSegmentationElement(elements, index, selectedTier, corpus) {
200
225
  if (!isUnderSelected(selectedTier, SEGMENTATION_TIER, corpus)) {
201
226
  return false;
@@ -205,19 +230,99 @@ export function shouldSkipSegmentationElement(elements, index, selectedTier, cor
205
230
  return false;
206
231
  }
207
232
  const { start, end } = findSegmentationGroupBounds(elements, index);
233
+ const ref = element.token;
234
+ // Junction host + seg-only pieces: diplomatic text lives on the junction host.
235
+ if (groupHasJunctionHost(elements, start, end)) {
236
+ if (hasJunctionFromRef(ref)) {
237
+ return false;
238
+ }
239
+ return true;
240
+ }
208
241
  const lastP = getLastSegmentationPosInGroup(elements, start, end);
209
- return element.token.p < lastP;
242
+ return ref.p < lastP;
210
243
  }
211
244
  /** @deprecated Use shouldSkipSegmentationElement */
212
245
  export function shouldSkipSegmentationToken(elements, index, selectedTier, corpus) {
213
246
  return shouldSkipSegmentationElement(elements, index, selectedTier, corpus);
214
247
  }
248
+ function isMidJunctionHostLineBreak(elements, index) {
249
+ if (elements[index]?.type !== PageRenderElementType.LINE_BREAK) {
250
+ return false;
251
+ }
252
+ const prev = elements[index - 1];
253
+ if (prev?.type !== PageRenderElementType.JUNCTION_SPLIT || !prev.token) {
254
+ return false;
255
+ }
256
+ let nextIdx = index + 1;
257
+ while (nextIdx < elements.length &&
258
+ elements[nextIdx].type === PageRenderElementType.LINE_NUMBER) {
259
+ nextIdx += 1;
260
+ }
261
+ const next = elements[nextIdx];
262
+ if (next?.type !== PageRenderElementType.JUNCTION_SPLIT || !next.token) {
263
+ return false;
264
+ }
265
+ return prev.token.uid === next.token.uid && prev.token.p === next.token.p;
266
+ }
267
+ function sameDeferredBreakGroup(element, group) {
268
+ const ref = element.token;
269
+ if (!ref)
270
+ return false;
271
+ if (ref.uid === group.uid && ref.p === group.p)
272
+ return true;
273
+ if (group.segGroup &&
274
+ hasSegmentationFromRef(ref) &&
275
+ sameSegmentationGroupRef(ref, group.uid, group.segGroup)) {
276
+ return true;
277
+ }
278
+ return false;
279
+ }
215
280
  export default function collapseElementsForTier(elements, selectedTier, corpus) {
216
281
  const showJunctionSplits = isUnderSelected(selectedTier, JUNCTION_TIER, corpus);
217
282
  const collapsed = [];
218
283
  let index = 0;
284
+ let deferredBreak = null;
285
+ let deferredLineNumbers = [];
286
+ let deferredBreakGroup = null;
287
+ const flushDeferredBreak = () => {
288
+ if (!deferredBreak)
289
+ return;
290
+ collapsed.push({
291
+ kind: 'single',
292
+ element: deferredBreak,
293
+ key: `LINE_BREAK_deferred_${collapsed.length}`,
294
+ });
295
+ for (const lineNumber of deferredLineNumbers) {
296
+ collapsed.push({
297
+ kind: 'single',
298
+ element: lineNumber,
299
+ key: `LINE_NUMBER_deferred_${collapsed.length}`,
300
+ });
301
+ }
302
+ deferredBreak = null;
303
+ deferredLineNumbers = [];
304
+ deferredBreakGroup = null;
305
+ };
219
306
  while (index < elements.length) {
220
307
  const element = elements[index];
308
+ if (!showJunctionSplits &&
309
+ deferredBreak === null &&
310
+ isMidJunctionHostLineBreak(elements, index)) {
311
+ const host = elements[index - 1].token;
312
+ deferredBreak = element;
313
+ deferredBreakGroup = {
314
+ uid: host.uid,
315
+ p: host.p,
316
+ segGroup: segmentationGroupKey(host),
317
+ };
318
+ index += 1;
319
+ while (index < elements.length &&
320
+ elements[index].type === PageRenderElementType.LINE_NUMBER) {
321
+ deferredLineNumbers.push(elements[index]);
322
+ index += 1;
323
+ }
324
+ continue;
325
+ }
221
326
  if (shouldSkipSegmentationElement(elements, index, selectedTier, corpus)) {
222
327
  index += 1;
223
328
  continue;
@@ -226,6 +331,12 @@ export default function collapseElementsForTier(elements, selectedTier, corpus)
226
331
  index += 1;
227
332
  continue;
228
333
  }
334
+ const inDeferredGroup = !!element.token &&
335
+ !!deferredBreakGroup &&
336
+ sameDeferredBreakGroup(element, deferredBreakGroup);
337
+ if (deferredBreak && !inDeferredGroup) {
338
+ flushDeferredBreak();
339
+ }
229
340
  if (!showJunctionSplits &&
230
341
  element.type === PageRenderElementType.JUNCTION_SPLIT &&
231
342
  element.token) {
@@ -251,6 +362,7 @@ export default function collapseElementsForTier(elements, selectedTier, corpus)
251
362
  });
252
363
  index += 1;
253
364
  }
365
+ flushDeferredBreak();
254
366
  return collapsed;
255
367
  }
256
368
  export function isTokenBearingCollapsed(item) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tycho-components",
3
3
  "private": false,
4
- "version": "0.42.0",
4
+ "version": "0.42.2",
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.0",
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.0",
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
- }