@rh-support/troubleshoot 2.6.358 → 2.6.361

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 (102) hide show
  1. package/lib/esm/components/AccountInfo/OwnerSelector.js +1 -1
  2. package/lib/esm/components/CaseEditView/CaseDetailsErrorMessage.d.ts.map +1 -1
  3. package/lib/esm/components/CaseEditView/CaseDetailsErrorMessage.js +14 -4
  4. package/lib/esm/components/CaseEditView/CaseDetailsTabs.d.ts.map +1 -1
  5. package/lib/esm/components/CaseEditView/CaseDetailsTabs.js +11 -7
  6. package/lib/esm/components/CaseEditView/CaseOverview/CaseStatus.d.ts +2 -0
  7. package/lib/esm/components/CaseEditView/CaseOverview/CaseStatus.d.ts.map +1 -1
  8. package/lib/esm/components/CaseEditView/CaseOverview/CaseStatus.js +19 -8
  9. package/lib/esm/components/CaseEditView/CaseOverview/index.d.ts.map +1 -1
  10. package/lib/esm/components/CaseEditView/CaseOverview/index.js +16 -13
  11. package/lib/esm/components/CaseEditView/CaseSolutions/CaseSolutions.d.ts.map +1 -1
  12. package/lib/esm/components/CaseEditView/CaseSolutions/CaseSolutions.js +1 -1
  13. package/lib/esm/components/CaseEditView/ConfirmationModals/ReopenCaseModal.js +2 -2
  14. package/lib/esm/components/CaseEditView/RequestRemoteSession/NewEssTermsModal.d.ts.map +1 -1
  15. package/lib/esm/components/CaseEditView/RequestRemoteSession/NewEssTermsModal.js +7 -5
  16. package/lib/esm/components/CaseEditView/RequestRemoteSession/RemoteSessionAgreementModal.d.ts.map +1 -1
  17. package/lib/esm/components/CaseEditView/RequestRemoteSession/RemoteSessionAgreementModal.js +7 -5
  18. package/lib/esm/components/CaseEditView/RequestRemoteSession/remoteSessionUtils.d.ts +6 -0
  19. package/lib/esm/components/CaseEditView/RequestRemoteSession/remoteSessionUtils.d.ts.map +1 -1
  20. package/lib/esm/components/CaseEditView/RequestRemoteSession/remoteSessionUtils.js +8 -0
  21. package/lib/esm/components/CaseEditView/Tabs/CaseActionPlan/CaseActionPlan.d.ts.map +1 -1
  22. package/lib/esm/components/CaseEditView/Tabs/CaseActionPlan/CaseActionPlan.js +6 -3
  23. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseContactPhoneNumber.js +1 -1
  24. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseHostname.js +1 -1
  25. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseInternalStatus.js +1 -1
  26. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseOpenshiftClusterId/CaseOpenshiftClusterId.d.ts.map +1 -1
  27. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseOpenshiftClusterId/CaseOpenshiftClusterId.js +1 -1
  28. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CasePhoneNumberConfirmAlert.js +1 -1
  29. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseReferenceNumber.js +1 -1
  30. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseSbrGroups.d.ts.map +1 -1
  31. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseSbrGroups.js +3 -1
  32. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/ProductVersion.d.ts.map +1 -1
  33. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/ProductVersion.js +1 -6
  34. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/Summary.js +1 -1
  35. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/useCustomEmails.d.ts.map +1 -1
  36. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/useCustomEmails.js +4 -2
  37. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseAttachment.d.ts.map +1 -1
  38. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseAttachment.js +5 -3
  39. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseComments.d.ts.map +1 -1
  40. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseComments.js +33 -3
  41. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.d.ts +6 -0
  42. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.d.ts.map +1 -1
  43. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.js +210 -35
  44. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.d.ts +1 -1
  45. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.d.ts.map +1 -1
  46. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.js +3 -1
  47. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.d.ts.map +1 -1
  48. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.js +11 -17
  49. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/VerifyCaseStatusModal/VerifyCaseStatusModal.d.ts.map +1 -1
  50. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/VerifyCaseStatusModal/VerifyCaseStatusModal.js +1 -1
  51. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.d.ts.map +1 -1
  52. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.js +5 -2
  53. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts +2 -1
  54. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts.map +1 -1
  55. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.js +4 -11
  56. package/lib/esm/components/CaseEditView/Tabs/CasePrivateNotes/CasePrivateNotes.d.ts.map +1 -1
  57. package/lib/esm/components/CaseEditView/Tabs/CasePrivateNotes/CasePrivateNotes.js +6 -3
  58. package/lib/esm/components/CaseEditView/Tabs/CaseSummary/CaseSummary.d.ts.map +1 -1
  59. package/lib/esm/components/CaseEditView/Tabs/CaseSummary/CaseSummary.js +1 -0
  60. package/lib/esm/components/CaseInformation/CaseGroup.d.ts.map +1 -1
  61. package/lib/esm/components/CaseInformation/CaseGroup.js +6 -2
  62. package/lib/esm/components/CaseInformation/CaseLabels.d.ts.map +1 -1
  63. package/lib/esm/components/CaseInformation/CaseLabels.js +6 -4
  64. package/lib/esm/components/CaseInformation/Fts.js +2 -2
  65. package/lib/esm/components/CaseManagement/Cep.js +1 -1
  66. package/lib/esm/components/CaseManagement/RHAssociatesSelector.d.ts.map +1 -1
  67. package/lib/esm/components/CaseManagement/RHAssociatesSelector.js +4 -2
  68. package/lib/esm/components/CaseManagement/SendNotifications/CaseContactSelector.d.ts.map +1 -1
  69. package/lib/esm/components/CaseManagement/SendNotifications/CaseContactSelector.js +4 -2
  70. package/lib/esm/components/EditDescription/EditDescription.d.ts.map +1 -1
  71. package/lib/esm/components/EditDescription/EditDescription.js +3 -1
  72. package/lib/esm/components/UpdateSeverityModal/UpdateSeverityModal.js +4 -4
  73. package/lib/esm/css/case.css +154 -8
  74. package/lib/esm/graphql/queries/getEscalationDetails.d.ts.map +1 -1
  75. package/lib/esm/graphql/queries/getEscalationDetails.js +5 -0
  76. package/lib/esm/hooks/useCaseComments/caseCommentsQuery.d.ts.map +1 -1
  77. package/lib/esm/hooks/useCaseComments/caseCommentsQuery.js +6 -1
  78. package/lib/esm/hooks/useCaseComments/index.d.ts +2 -1
  79. package/lib/esm/hooks/useCaseComments/index.d.ts.map +1 -1
  80. package/lib/esm/hooks/useCaseComments/types.d.ts +8 -0
  81. package/lib/esm/hooks/useCaseComments/types.d.ts.map +1 -1
  82. package/lib/esm/hooks/useCaseComments/useCaseComments.d.ts +12 -3
  83. package/lib/esm/hooks/useCaseComments/useCaseComments.d.ts.map +1 -1
  84. package/lib/esm/hooks/useCaseComments/useCaseComments.js +84 -9
  85. package/lib/esm/hooks/useCaseHistory.d.ts +2 -2
  86. package/lib/esm/hooks/useCaseHistory.d.ts.map +1 -1
  87. package/lib/esm/hooks/useCaseHistory.js +16 -7
  88. package/lib/esm/hooks/useCaseUpdateErrorMessage.d.ts +9 -1
  89. package/lib/esm/hooks/useCaseUpdateErrorMessage.d.ts.map +1 -1
  90. package/lib/esm/hooks/useCaseUpdateErrorMessage.js +103 -16
  91. package/lib/esm/models/caseHistory.d.ts +6 -0
  92. package/lib/esm/models/caseHistory.d.ts.map +1 -1
  93. package/lib/esm/reducers/CaseDiscussionTabReducer.d.ts +2 -1
  94. package/lib/esm/reducers/CaseDiscussionTabReducer.d.ts.map +1 -1
  95. package/lib/esm/reducers/CaseDiscussionTabReducer.js +26 -4
  96. package/lib/esm/utils/caseHistoryUtils.d.ts +19 -0
  97. package/lib/esm/utils/caseHistoryUtils.d.ts.map +1 -1
  98. package/lib/esm/utils/caseHistoryUtils.js +31 -0
  99. package/lib/esm/utils/linkifyHtmlTextNodes.d.ts +8 -0
  100. package/lib/esm/utils/linkifyHtmlTextNodes.d.ts.map +1 -0
  101. package/lib/esm/utils/linkifyHtmlTextNodes.js +37 -0
  102. package/package.json +7 -6
@@ -17,6 +17,7 @@ import { deleteCaseAttachment, formatDateTime, humanizeSize, updateCaseAttachmen
17
17
  import isEmpty from 'lodash/isEmpty';
18
18
  import React, { useContext, useRef, useState } from 'react';
19
19
  import { Trans, useTranslation } from 'react-i18next';
20
+ import { useCaseUpdateErrorMessage } from '../../../../hooks/useCaseUpdateErrorMessage';
20
21
  import { ATTACHMENT_DESCRIPTION_LENGTH_LIMIT } from '../../../../reducers/CaseConstNTypes';
21
22
  import { DiscussionType } from '../../../../reducers/CaseDiscussionTabReducer';
22
23
  import { PDFContext } from '../../PDFContainer';
@@ -25,6 +26,7 @@ import { JumpAndCopyLink } from './JumpAndCopyLink';
25
26
  const CaseAttachment = React.forwardRef((props, ref) => {
26
27
  var _a;
27
28
  const { t } = useTranslation();
29
+ const caseUpdateError = useCaseUpdateErrorMessage();
28
30
  const clipboardRef = useRef(null);
29
31
  const { clipboardText, copyToClipboard } = useCopyToClipboard(clipboardRef);
30
32
  const { request, isFetching: isDeleting } = useFetch(deleteCaseAttachment, { propgateErrors: true });
@@ -39,7 +41,7 @@ const CaseAttachment = React.forwardRef((props, ref) => {
39
41
  const canEditCase = useCanEditCase();
40
42
  const onDescriptionSave = () => __awaiter(void 0, void 0, void 0, function* () {
41
43
  if (!apolloClient.mutate) {
42
- ToastNotification.addDangerMessage(t('Failed to update attachment description'));
44
+ caseUpdateError.showFieldError({}, t('Attachment description'));
43
45
  return;
44
46
  }
45
47
  setIsUpdating(true);
@@ -47,8 +49,8 @@ const CaseAttachment = React.forwardRef((props, ref) => {
47
49
  const updatedAttachment = yield updateCaseAttachmentDescriptionByGraphQL(apolloClient, props.attachment, attachmentDescription);
48
50
  props.onMetadataUpdate && props.onMetadataUpdate(props.attachment.uuid, updatedAttachment);
49
51
  }
50
- catch (_a) {
51
- ToastNotification.addDangerMessage(t('Failed to update attachment description'));
52
+ catch (e) {
53
+ caseUpdateError.showFieldError(e, t('Attachment description'), onDescriptionSave);
52
54
  }
53
55
  finally {
54
56
  setIsUpdating(false);
@@ -1 +1 @@
1
- {"version":3,"file":"CaseComments.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/CaseComments.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AA2BvE,OAAO,KAAwC,MAAM,OAAO,CAAC;AAU7D,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,kBAAkB,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAChH;AAED,QAAA,MAAM,YAAY,4EA8UhB,CAAC;AAEH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"CaseComments.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/CaseComments.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AA2BvE,OAAO,KAAwC,MAAM,OAAO,CAAC;AAW7D,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,kBAAkB,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAChH;AAED,QAAA,MAAM,YAAY,4EAgXhB,CAAC;AAEH,eAAe,YAAY,CAAC"}
@@ -25,6 +25,7 @@ import { Trans } from 'react-i18next';
25
25
  // TODO: Re-enable after GraphQL migration of feedback
26
26
  // import { useTranslation } from 'react-i18next';
27
27
  import { DiscussionType } from '../../../../reducers/CaseDiscussionTabReducer';
28
+ import { linkifyHtmlTextNodes } from '../../../../utils/linkifyHtmlTextNodes';
28
29
  import { PDFContext } from '../../PDFContainer';
29
30
  import { DiscussionItemLink } from './DiscussionItemLink';
30
31
  import { JumpAndCopyLink } from './JumpAndCopyLink';
@@ -149,6 +150,34 @@ const CaseComments = React.forwardRef((props, ref) => {
149
150
  return React.createElement(Trans, null, "Automated comment");
150
151
  }
151
152
  };
153
+ // Tags the Quill RTE legitimately produces — everything else was typed
154
+ // literally by the user or injected from an external source (SFDC) and
155
+ // should render as visible text, not interpreted as HTML formatting.
156
+ const QUILL_TAGS = new Set([
157
+ 'p',
158
+ 'br',
159
+ 'strong',
160
+ 'em',
161
+ 'u',
162
+ 's',
163
+ 'ul',
164
+ 'ol',
165
+ 'li',
166
+ 'a',
167
+ 'img',
168
+ 'h1',
169
+ 'h2',
170
+ 'h3',
171
+ 'h4',
172
+ 'h5',
173
+ 'h6',
174
+ 'span',
175
+ 'pre',
176
+ 'blockquote',
177
+ 'sub',
178
+ 'sup',
179
+ ]);
180
+ const escapeNonQuillTags = (html) => html.replace(/<(\/?)([\w-]+)([^>]*)>/g, (match, slash, tag, attrs) => QUILL_TAGS.has(tag.toLowerCase()) ? match : `&lt;${slash}${tag}${attrs}&gt;`);
152
181
  const sanitizeHtml = (html) => {
153
182
  DOMPurify.addHook('afterSanitizeAttributes', (node) => {
154
183
  if (node.tagName === 'A') {
@@ -156,7 +185,7 @@ const CaseComments = React.forwardRef((props, ref) => {
156
185
  node.setAttribute('rel', 'noopener noreferrer');
157
186
  }
158
187
  });
159
- const clean = DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] });
188
+ const clean = DOMPurify.sanitize(escapeNonQuillTags(html), { ADD_ATTR: ['target', 'rel'] });
160
189
  DOMPurify.removeHook('afterSanitizeAttributes');
161
190
  return clean;
162
191
  };
@@ -216,6 +245,7 @@ const CaseComments = React.forwardRef((props, ref) => {
216
245
  }
217
246
  // HTML comments (new rich text or legacy markdown — both stored as HTML)
218
247
  let htmlString = comment.commentBody;
248
+ htmlString = linkifyHtmlTextNodes(htmlString);
219
249
  if (isExportingPDF) {
220
250
  htmlString = formatMarkdownHtmlForPDF(htmlString);
221
251
  }
@@ -251,8 +281,8 @@ const CaseComments = React.forwardRef((props, ref) => {
251
281
  React.createElement("span", { className: "pull-right pf-v6-l-flex pf-m-align-items-center" },
252
282
  props.showJumpToComment && (React.createElement(DiscussionItemLink, { onClick: onJumpToComment(props.comment.id), discussionType: DiscussionType.COMMENT, itemId: props.comment.id, caseNumber: caseNumber, isCopyLink: false },
253
283
  React.createElement(Trans, null, "Jump to comment"))),
254
- React.createElement(JumpAndCopyLink, { caseNumber: caseNumber, cardId: props.comment.id, discussionType: DiscussionType.COMMENT }),
255
284
  React.createElement("button", { onClick: () => props.onReply(props.comment.lastModifiedById, props.comment.id, props.comment.lastModifiedDate), className: `btn btn-app btn-link case-reply-button ${isExportingPDF ? 'hide-in-pdf' : ''}`, "data-tracking-id": "case-comment-reply", type: "button" },
256
- React.createElement(Trans, null, "Reply"))))));
285
+ React.createElement(Trans, null, "Reply")),
286
+ React.createElement(JumpAndCopyLink, { caseNumber: caseNumber, cardId: props.comment.id, discussionType: DiscussionType.COMMENT })))));
257
287
  });
258
288
  export default CaseComments;
@@ -9,6 +9,12 @@ interface IProps {
9
9
  tabRef: React.MutableRefObject<HTMLDivElement>;
10
10
  isDiscussionTab: boolean;
11
11
  refetchComments: () => Promise<unknown> | void;
12
+ fetchMore?: () => Promise<unknown> | void;
13
+ hasNextPage?: boolean;
14
+ isLoadingMore?: boolean;
15
+ loadMoreError?: boolean;
16
+ loadMoreRetryCount?: number;
17
+ retryLoadMore?: () => Promise<unknown> | void;
12
18
  }
13
19
  export default function CaseDiscussion(props: IProps): React.JSX.Element;
14
20
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"CaseDiscussion.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAkBvE,OAAO,KAAoE,MAAM,OAAO,CAAC;AAsCzF,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC/C,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAClD;AAOD,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,KAAK,EAAE,MAAM,qBAshBnD"}
1
+ {"version":3,"file":"CaseDiscussion.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAoBvE,OAAO,KAA6E,MAAM,OAAO,CAAC;AAwClG,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC/C,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,KAAK,EAAE,MAAM,qBAyzBnD"}
@@ -8,25 +8,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { useApolloClient } from '@apollo/client/react';
11
- import { MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core';
11
+ import { Button, MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core';
12
12
  import { AlertMessage, AlertType, ErrorBoundary, LoadingIndicator, ToastNotification } from '@rh-support/components';
13
13
  import { useGlobalStateContext } from '@rh-support/react-context';
14
14
  import { AbilityContext, CaseDetailsFields, resourceActions, resources } from '@rh-support/user-permissions';
15
- import { downloadCaseAttachment, dtmTrackEventCaseStepEncountered, getUrlParsedParams, scrollIntoView, } from '@rh-support/utils';
15
+ import { downloadCaseAttachment, dtmTrackEventCaseStepEncountered, getDropdownBtnPlaceholder, getUrlParsedParams, scrollIntoView, } from '@rh-support/utils';
16
16
  import cloneDeep from 'lodash/cloneDeep';
17
17
  import filter from 'lodash/filter';
18
18
  import findIndex from 'lodash/findIndex';
19
19
  import isEmpty from 'lodash/isEmpty';
20
20
  import isEqual from 'lodash/isEqual';
21
+ import orderBy from 'lodash/orderBy';
21
22
  import Mark from 'mark.js';
22
- import React, { useContext, useEffect, useRef, useState } from 'react';
23
+ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
23
24
  import { Trans, useTranslation } from 'react-i18next';
25
+ import { useInView } from 'react-intersection-observer';
24
26
  import { useLocation } from 'react-router-dom';
25
27
  import { AutoSizer, CellMeasurer, CellMeasurerCache, List, WindowScroller } from 'react-virtualized';
26
28
  import { useCaseDispatch, useCaseSelector } from '../../../../context/CaseContext';
27
29
  import { useCaseDetailsPageDispatchContext, useCaseDetailsPageStateContext, } from '../../../../context/CaseDetailsPageContext';
28
30
  import { useCaseDiscussionTabDispatchContext, useCaseDiscussionTabStateContext, } from '../../../../context/CaseDiscussionTabContext';
29
- import { clearReplyingTo, discussionSortOrderConst, DiscussionType, setDiscussions, setReplyingTo, setSort, updateAttachment, updateAttacmentsAfterRemoval, updateDiscussionStateAttachments, } from '../../../../reducers/CaseDiscussionTabReducer';
31
+ import { clearReplyingTo, discussionSortOrderConst, DiscussionType, setDiscussions, setFilter, setReplyingTo, setSort, updateAttachment, updateAttacmentsAfterRemoval, updateDiscussionStateAttachments, } from '../../../../reducers/CaseDiscussionTabReducer';
30
32
  import { setCaseState } from '../../../../reducers/CaseReducer';
31
33
  import { AttachmentContextProvider } from '../../../shared/fileUpload/reducer/AttachmentReducerContext';
32
34
  import { PDFContext } from '../../PDFContainer';
@@ -37,11 +39,8 @@ import { CommentSearch } from './CommentSearch';
37
39
  import { PostComment } from './PostComment';
38
40
  /** Threshold above which comments render in a virtualized (windowed) list for performance. */
39
41
  const VC_MINIMUM_COMMENT = 150;
40
- let cellMeasureCache = new CellMeasurerCache({
41
- fixedWidth: true,
42
- defaultHeight: 100,
43
- });
44
42
  export default function CaseDiscussion(props) {
43
+ var _a;
45
44
  const { t } = useTranslation();
46
45
  const { isPostingRemoteRequestCommentCompleted, product, version, caseSalesforceId } = useCaseSelector((state) => ({
47
46
  isPostingRemoteRequestCommentCompleted: state.isPostingRemoteRequestCommentCompleted,
@@ -52,7 +51,7 @@ export default function CaseDiscussion(props) {
52
51
  const urlSearchParams = getUrlParsedParams(useLocation().search);
53
52
  const discussionIdFromUrl = ((urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.commentId) || (urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.attachmentId));
54
53
  const activeDiscussionElementRef = useRef(null);
55
- const { sort, discussionFiltersListState, allDiscussions, replyingToAuthor, replyingToTimestamp } = useCaseDiscussionTabStateContext();
54
+ const { sort, discussionFiltersListState, replyingToAuthor, replyingToTimestamp } = useCaseDiscussionTabStateContext();
56
55
  const { globalMetadataState: { navBarRef, loggedInUsersAccount }, } = useGlobalStateContext();
57
56
  const dispatchDiscussion = useCaseDiscussionTabDispatchContext();
58
57
  const { caseDetailsPageState: { caseAttachmentHydra }, } = useCaseDetailsPageStateContext();
@@ -66,7 +65,9 @@ export default function CaseDiscussion(props) {
66
65
  const listRef = React.useRef(null);
67
66
  const windowScrollerRef = React.useRef(null);
68
67
  const commentRef = React.useRef(null);
68
+ const cellMeasureCacheRef = useRef(new CellMeasurerCache({ fixedWidth: true, defaultHeight: 100 }));
69
69
  const { isExportingPDF } = useContext(PDFContext);
70
+ const { ref: sentinelRef, inView } = useInView({ threshold: 0, rootMargin: '200px' });
70
71
  const [searchResult, setSearchResult] = useState([]);
71
72
  const [searchCommentText, setSearchCommentText] = useState('');
72
73
  const [commentIndex, setCommentIndex] = useState(-1);
@@ -74,19 +75,105 @@ export default function CaseDiscussion(props) {
74
75
  const [isJumpToCommentClicked, setJumpToCommentClicked] = useState(false);
75
76
  const [canUpdateScrollTop, setCanUpdateScrollTop] = useState(true);
76
77
  const [discussionSectionWidth, setDiscussionSectionWidth] = useState(null);
78
+ const [debouncedShowSkeleton, setDebouncedShowSkeleton] = useState(false);
79
+ const [isOpen, setIsOpen] = useState(false);
80
+ const [selectedFilterItems, setSelectedFilterItems] = useState([]);
77
81
  const [isSortOpen, setIsSortOpen] = useState(false);
78
82
  const [sortSelected, setSortSelected] = useState('Select a value');
79
- if (props.isDiscussionTab) {
80
- cellMeasureCache = new CellMeasurerCache({
81
- fixedWidth: true,
82
- defaultHeight: 100,
83
+ const { hasNextPage, isLoadingMore, loadMoreError, fetchMore } = props;
84
+ const isCommentFilterActive = discussionFiltersListState.some((f) => (f.value === DiscussionType.COMMENT || f.value === DiscussionType.ALL) && f.isSelected);
85
+ useEffect(() => {
86
+ console.log('[CaseDiscussion] fetchMore useEffect', {
87
+ inView,
88
+ hasNextPage,
89
+ isLoadingMore,
90
+ loadMoreError,
91
+ hasFetchMore: !!fetchMore,
92
+ isCommentFilterActive,
93
+ willFetch: inView && hasNextPage && !isLoadingMore && !loadMoreError && !!fetchMore && isCommentFilterActive,
83
94
  });
95
+ if (inView && hasNextPage && !isLoadingMore && !loadMoreError && fetchMore && isCommentFilterActive) {
96
+ fetchMore();
97
+ }
98
+ }, [inView, hasNextPage, isLoadingMore, loadMoreError, fetchMore, isCommentFilterActive]);
99
+ useEffect(() => {
100
+ if (props.isLoadingMore && props.hasNextPage) {
101
+ const timer = setTimeout(() => setDebouncedShowSkeleton(true), 300);
102
+ return () => clearTimeout(timer);
103
+ }
104
+ setDebouncedShowSkeleton(false);
105
+ }, [props.isLoadingMore, props.hasNextPage]);
106
+ const cellMeasureCache = cellMeasureCacheRef.current;
107
+ const allDiscussions = useMemo(() => {
108
+ var _a, _b, _c;
109
+ const allComments = (props.caseComments || [])
110
+ .filter((comment) => comment.isDraft === false)
111
+ .map((comment) => ({
112
+ item: Object.assign(Object.assign({}, comment), { isPublic: comment.isPublic === undefined ? true : comment.isPublic, last_modified_date: comment.lastModifiedDate || comment.createdDate }),
113
+ discussionType: DiscussionType.COMMENT,
114
+ last_modified_date: comment.lastModifiedDate || comment.createdDate,
115
+ }));
116
+ let allAttachments = canSeeAttachments
117
+ ? (caseAttachmentHydra.data || [])
118
+ .filter((attachment) => (attachment === null || attachment === void 0 ? void 0 : attachment.lastModifiedDate) || (attachment === null || attachment === void 0 ? void 0 : attachment.createdDate))
119
+ .map((attachment) => ({
120
+ item: Object.assign(Object.assign({}, attachment), { lastModifiedDate: attachment.lastModifiedDate || attachment.createdDate }),
121
+ last_modified_date: (attachment.lastModifiedDate || attachment.createdDate),
122
+ discussionType: DiscussionType.ATTACHMENT,
123
+ }))
124
+ : [];
125
+ if (props.hasNextPage && allComments.length > 0) {
126
+ const sortOrder = (_a = sort === null || sort === void 0 ? void 0 : sort.sortOrder) !== null && _a !== void 0 ? _a : 'desc';
127
+ const commentDates = allComments.map((c) => c.last_modified_date).filter(Boolean);
128
+ if (commentDates.length > 0) {
129
+ if (sortOrder === 'desc') {
130
+ const oldestLoaded = commentDates.reduce((min, d) => (d < min ? d : min));
131
+ allAttachments = allAttachments.filter((a) => a.last_modified_date >= oldestLoaded);
132
+ }
133
+ else {
134
+ const newestLoaded = commentDates.reduce((max, d) => (d > max ? d : max));
135
+ allAttachments = allAttachments.filter((a) => a.last_modified_date <= newestLoaded);
136
+ }
137
+ }
138
+ }
139
+ const merged = [...allComments, ...allAttachments];
140
+ return orderBy(merged, ['last_modified_date', (d) => { var _a; return (_a = d.item.id) !== null && _a !== void 0 ? _a : ''; }], [(_b = sort === null || sort === void 0 ? void 0 : sort.sortOrder) !== null && _b !== void 0 ? _b : 'desc', (_c = sort === null || sort === void 0 ? void 0 : sort.sortOrder) !== null && _c !== void 0 ? _c : 'desc']);
141
+ }, [props.caseComments, caseAttachmentHydra.data, canSeeAttachments, sort, props.hasNextPage]);
142
+ const usedPaginationRef = useRef(false);
143
+ if (props.hasNextPage || props.isLoadingMore) {
144
+ usedPaginationRef.current = true;
84
145
  }
85
- const shouldRenderRVComponent = !isExportingPDF && allDiscussions.length > VC_MINIMUM_COMMENT;
146
+ const shouldRenderRVComponent = !isExportingPDF && allDiscussions.length > VC_MINIMUM_COMMENT && !usedPaginationRef.current;
147
+ const list = discussionFiltersListState.filter((f) => f.canSee);
148
+ const computeFilterComment = () => {
149
+ return filter(allDiscussions, (discussion) => {
150
+ const activeFilter = discussionFiltersListState.filter((f) => f.value === discussion.discussionType && f.isSelected);
151
+ return activeFilter.length === 1 ? discussion : null;
152
+ });
153
+ };
154
+ const computeFilteredComments = computeFilterComment();
86
155
  const comuteSearchedComments = searchResult.length === 0
87
- ? cloneDeep(allDiscussions)
88
- : filter([...allDiscussions], (o) => { var _a; return searchResult.indexOf((_a = o === null || o === void 0 ? void 0 : o.item) === null || _a === void 0 ? void 0 : _a.id) !== -1; });
89
- const isSearchResultEmpty = !isEmpty(searchCommentText) && (searchResult.length === 0 || comuteSearchedComments.length === 0);
156
+ ? cloneDeep(computeFilteredComments)
157
+ : filter([...computeFilteredComments], (o) => { var _a; return searchResult.indexOf((_a = o === null || o === void 0 ? void 0 : o.item) === null || _a === void 0 ? void 0 : _a.id) !== -1; });
158
+ const isRefetching = props.commentsLoading && !props.isLoadingMore;
159
+ const isSearchResultEmpty = !isRefetching &&
160
+ !isEmpty(searchCommentText) &&
161
+ (searchResult.length === 0 || comuteSearchedComments.length === 0);
162
+ // DEBUG: track every render of CaseDiscussion
163
+ console.log('[CaseDiscussion] render', {
164
+ commentsLoading: props.commentsLoading,
165
+ isLoadingMore: props.isLoadingMore,
166
+ hasNextPage: props.hasNextPage,
167
+ loadMoreError: props.loadMoreError,
168
+ isRefetching,
169
+ isSearchResultEmpty,
170
+ allDiscussionsLen: allDiscussions.length,
171
+ filteredLen: computeFilteredComments.length,
172
+ searchedLen: comuteSearchedComments.length,
173
+ caseCommentsLen: (_a = props.caseComments) === null || _a === void 0 ? void 0 : _a.length,
174
+ inView,
175
+ isCommentFilterActive,
176
+ });
90
177
  const setVRCommentIndex = (cId) => {
91
178
  setCommentIndex(cId < 0 ? 0 : cId);
92
179
  setJumpToCommentClicked(true);
@@ -100,11 +187,15 @@ export default function CaseDiscussion(props) {
100
187
  (allDiscussions || []).length === 0) {
101
188
  return;
102
189
  }
190
+ const targetIndex = findIndex([...comuteSearchedComments], (discussion) => discussion.item.id === discussionIdFromUrl);
191
+ // Comment is on a later page — wait until keep-fetching brings it in.
192
+ if (targetIndex < 0) {
193
+ return;
194
+ }
103
195
  activeElementId.current = discussionIdFromUrl;
104
196
  if (allDiscussions.length > VC_MINIMUM_COMMENT) {
105
- const commentIndex = findIndex([...comuteSearchedComments], (discussion) => discussion.item.id === discussionIdFromUrl);
106
- setVRCommentIndex(commentIndex);
107
- if (0 <= commentIndex && commentIndex <= 3) {
197
+ setVRCommentIndex(targetIndex);
198
+ if (targetIndex <= 3) {
108
199
  const offset = 80;
109
200
  const scrollTopValue = ((_a = windowScrollerRef === null || windowScrollerRef === void 0 ? void 0 : windowScrollerRef.current) === null || _a === void 0 ? void 0 : _a._positionFromTop) - offset;
110
201
  const scrollLeftValue = (_b = windowScrollerRef === null || windowScrollerRef === void 0 ? void 0 : windowScrollerRef.current) === null || _b === void 0 ? void 0 : _b._positionFromLeft;
@@ -126,13 +217,13 @@ export default function CaseDiscussion(props) {
126
217
  });
127
218
  }
128
219
  // eslint-disable-next-line react-hooks/exhaustive-deps
129
- }, [discussionIdFromUrl, allDiscussions, activeElementId.current]);
220
+ }, [discussionIdFromUrl]);
130
221
  useEffect(() => {
131
- if (!isExportingPDF) {
132
- setDiscussions(dispatchDiscussion, props.caseComments, [], caseAttachmentHydra.data, [], [], discussionFiltersListState, false, false, canSeeAttachments);
133
- }
222
+ if (isExportingPDF)
223
+ return;
224
+ setDiscussions(dispatchDiscussion, props.caseComments, [], caseAttachmentHydra.data, [], [], discussionFiltersListState, false, false, canSeeAttachments, sort);
134
225
  // eslint-disable-next-line react-hooks/exhaustive-deps
135
- }, [props.caseComments, caseAttachmentHydra, canSeeAttachments]);
226
+ }, [props.caseComments, caseAttachmentHydra, canSeeAttachments, sort]);
136
227
  useEffect(() => {
137
228
  if (!caseAttachmentHydra.isFetching && caseAttachmentHydra.isError) {
138
229
  ToastNotification.addDangerMessage(t('There was an error loading attachments.'));
@@ -238,6 +329,15 @@ export default function CaseDiscussion(props) {
238
329
  }
239
330
  // eslint-disable-next-line react-hooks/exhaustive-deps
240
331
  }, [discussionSectionWidth, shouldRenderRVComponent]);
332
+ useEffect(() => {
333
+ var _a;
334
+ if (shouldRenderRVComponent) {
335
+ cellMeasureCache.clearAll();
336
+ (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.recomputeRowHeights();
337
+ }
338
+ usedPaginationRef.current = false;
339
+ // eslint-disable-next-line react-hooks/exhaustive-deps
340
+ }, [sort]);
241
341
  const renderVRCommentElement = () => {
242
342
  return (React.createElement(WindowScroller, { ref: windowScrollerRef, onScroll: handleScroll }, ({ height, registerChild, onChildScroll, isScrolling, scrollTop }) => {
243
343
  return (React.createElement("div", { ref: commentRef, "data-tracking-id": "vr-case-comments" },
@@ -313,6 +413,22 @@ export default function CaseDiscussion(props) {
313
413
  }
314
414
  // eslint-disable-next-line react-hooks/exhaustive-deps
315
415
  }, [isPostingRemoteRequestCommentCompleted]);
416
+ const onToggleClick = (e) => {
417
+ e.stopPropagation();
418
+ setIsOpen(!isOpen);
419
+ };
420
+ const onFilterSelect = (_event, prevOption) => {
421
+ const isFilterSelected = !prevOption.isSelected;
422
+ const prevSelectedFilters = list.filter((option) => option.isSelected && option.value !== prevOption.value);
423
+ const prevSelectedFiltersValue = prevSelectedFilters.map((option) => option.value);
424
+ const newSelectedFilters = isFilterSelected
425
+ ? [...prevSelectedFiltersValue, prevOption.value]
426
+ : prevSelectedFiltersValue;
427
+ setFilter(dispatchDiscussion, newSelectedFilters, discussionFiltersListState);
428
+ shouldRenderRVComponent && setCanUpdateScrollTop(false);
429
+ setSelectedFilterItems(prevSelectedFiltersValue);
430
+ };
431
+ const filterToggle = (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: onToggleClick, isExpanded: isOpen, isFullWidth: true }, getDropdownBtnPlaceholder(t('Select a filter'), list.filter((i) => i.isSelected).map((i) => i.filterLabel), ' ', list.length, t('All comments'))));
316
432
  const onSortToggleClick = (e) => {
317
433
  e.stopPropagation();
318
434
  setIsSortOpen(!isSortOpen);
@@ -327,24 +443,83 @@ export default function CaseDiscussion(props) {
327
443
  };
328
444
  const onSortToggle = (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: onSortToggleClick, isExpanded: isSortOpen, isFullWidth: true }, sort.name));
329
445
  return (React.createElement(React.Fragment, null,
446
+ (() => {
447
+ const showInitialLoading = props.commentsLoading && allDiscussions.length === 0;
448
+ const showNoComments = !props.commentsLoading && !props.commentsError && allDiscussions.length === 0;
449
+ const showDiscussions = allDiscussions.length !== 0;
450
+ const showSkeleton = debouncedShowSkeleton;
451
+ const showNoMore = !props.hasNextPage &&
452
+ !props.isLoadingMore &&
453
+ !props.commentsLoading &&
454
+ !props.loadMoreError &&
455
+ comuteSearchedComments.length > 0;
456
+ console.log('[CaseDiscussion] RENDER DECISION', {
457
+ showInitialLoading,
458
+ showNoComments,
459
+ showDiscussions,
460
+ isRefetching,
461
+ showSkeleton,
462
+ showNoMore,
463
+ isSearchResultEmpty,
464
+ shouldRenderRVComponent,
465
+ });
466
+ return null;
467
+ })(),
330
468
  React.createElement(ErrorBoundary, { errorMsgInfo: { message: t('There was an error loading post comment section') } },
331
469
  React.createElement(AttachmentContextProvider, null,
332
470
  React.createElement(PostComment, { caseNumber: caseNumber, allDiscussions: allDiscussions, onCommentAdded: onCommentAdded, onAttachmentAdded: onAttachmentAdded, onAttachmentRemove: onAttachmentRemove, replyingToAuthor: replyingToAuthor, replyingToTimestamp: replyingToTimestamp, onClearReply: onClearReply }))),
333
471
  props.commentsLoading && allDiscussions.length === 0 && React.createElement(LoadingIndicator, { size: "sm" }),
334
472
  props.commentsError && allDiscussions.length === 0 && (React.createElement(AlertMessage, { variant: AlertType.DANGER, show: true, title: t('Failed to load comments. Try refreshing.') })),
335
- !props.commentsLoading && !props.commentsError && allDiscussions.length === 0 && (React.createElement(Trans, null, "No comments")),
473
+ !props.commentsLoading && !props.commentsError && allDiscussions.length === 0 && (React.createElement("p", null,
474
+ React.createElement(Trans, null, "No comments"))),
336
475
  allDiscussions.length !== 0 && (React.createElement(ErrorBoundary, { errorMsgInfo: { message: t('There was an error loading comments. Try refreshing.') } },
337
476
  React.createElement("div", { className: `pfe-l-grid pfe-m-all-6-col pfe-m-gutters push-bottom ${isExportingPDF ? 'hide-in-pdf' : ''}` },
338
- React.createElement("div", { className: "pfe-m-12-col" }, allDiscussions.length > 20 && (React.createElement(CommentSearch, { caseDiscussions: allDiscussions || [], onCommentSearch: onCaseCommentSearch, isCommentSearchCleared: isCommentSearchCleared }))),
477
+ React.createElement("div", { className: "pfe-m-12-col" }, allDiscussions.length > 20 && (React.createElement(CommentSearch, { caseDiscussions: computeFilteredComments || [], onCommentSearch: onCaseCommentSearch, isCommentSearchCleared: isCommentSearchCleared }))),
478
+ React.createElement("span", { className: "push-top-narrow" },
479
+ React.createElement("label", { htmlFor: "case-list-comments-dropdown", className: "pf-v6-u-mb-sm" }, t('Filter by')),
480
+ React.createElement(Select, { "data-tracking-id": "discussions-filter-comments-dropdown", id: "case-list-comments-dropdown", role: "menu", isOpen: isOpen, selected: selectedFilterItems, onSelect: onFilterSelect, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: filterToggle, popperProps: { direction: 'down', enableFlip: false }, zIndex: 9 },
481
+ React.createElement(SelectList, null, list.map((option) => (React.createElement(SelectOption, { key: option.value, hasCheckbox: true, value: option, isSelected: option.isSelected, isDisabled: option.isDisabled }, option.filterLabel)))))),
339
482
  React.createElement("span", { className: "push-top-narrow" },
340
483
  React.createElement("label", { htmlFor: "se-discussion-sort", className: "pf-v6-u-mb-sm" }, t('Sort by')),
341
484
  React.createElement(Select, { id: "se-discussion-sort", "data-tracking-id": "discussions-sort-dropdown", isOpen: isSortOpen, selected: sortSelected, onSelect: onSortSelect, onOpenChange: (isSortOpen) => setIsSortOpen(isSortOpen), toggle: onSortToggle, shouldFocusToggleOnSelect: true, popperProps: { direction: 'down', enableFlip: false }, zIndex: 9 },
342
- React.createElement(SelectList, null, discussionSortOrderConst.map((option) => (React.createElement(SelectOption, { value: option.name, isSelected: isEqual(sort.name, option.name) }, option.name))))))),
343
- React.createElement("div", { className: "support-comment-section" }, !shouldRenderRVComponent &&
344
- !isSearchResultEmpty &&
345
- comuteSearchedComments.map((discussion, index) => {
346
- return renderCaseCommentElement(discussion);
347
- })),
348
- isSearchResultEmpty && (React.createElement(AlertMessage, { variant: AlertType.WARNING, show: true, title: t('No result found') })),
349
- shouldRenderRVComponent && !isSearchResultEmpty && renderVRCommentElement()))));
485
+ React.createElement(SelectList, null, discussionSortOrderConst.map((option) => (React.createElement(SelectOption, { key: option.name, value: option.name, isSelected: isEqual(sort.name, option.name) }, option.name))))))),
486
+ React.createElement("div", { className: "support-comment-section" },
487
+ isRefetching && React.createElement(LoadingIndicator, { size: "sm" }),
488
+ !isRefetching &&
489
+ !shouldRenderRVComponent &&
490
+ !isSearchResultEmpty &&
491
+ comuteSearchedComments.map((discussion, index) => {
492
+ return renderCaseCommentElement(discussion);
493
+ })),
494
+ isSearchResultEmpty && (React.createElement("p", { className: "infinite-scroll-end-status" },
495
+ React.createElement(Trans, null, "No results found"))),
496
+ !isRefetching && shouldRenderRVComponent && !isSearchResultEmpty && renderVRCommentElement(),
497
+ React.createElement("div", { ref: sentinelRef, "aria-hidden": "true", style: { height: 10, marginBottom: -10 } }),
498
+ debouncedShowSkeleton && (React.createElement("div", { className: "load-more-skeleton", role: "status", "aria-label": t('Loading more comments') },
499
+ React.createElement("div", { className: "load-more-skeleton__header" },
500
+ React.createElement("div", { className: "load-more-skeleton__avatar" }),
501
+ React.createElement("div", { className: "load-more-skeleton__name" }),
502
+ React.createElement("div", { className: "load-more-skeleton__timestamp" })),
503
+ React.createElement("div", { className: "load-more-skeleton__body" },
504
+ React.createElement("div", { className: "load-more-skeleton__line", style: { width: '100%' } }),
505
+ React.createElement("div", { className: "load-more-skeleton__line", style: { width: '85%' } }),
506
+ React.createElement("div", { className: "load-more-skeleton__line", style: { width: '60%' } })))),
507
+ props.loadMoreError && props.loadMoreRetryCount <= 1 && (React.createElement("div", { className: "infinite-scroll-footer-message" },
508
+ React.createElement(Button, { variant: "secondary", onClick: props.retryLoadMore, icon: React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
509
+ React.createElement("polyline", { points: "23 4 23 10 17 10" }),
510
+ React.createElement("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })) },
511
+ React.createElement(Trans, null, "try loading again")))),
512
+ props.loadMoreError && props.loadMoreRetryCount > 1 && (React.createElement("p", { className: "infinite-scroll-end-status" },
513
+ React.createElement(Trans, null, "We're having trouble loading your comments. Please refresh or"),
514
+ ' ',
515
+ React.createElement(Button, { variant: "link", isInline: true, onClick: props.retryLoadMore },
516
+ React.createElement(Trans, null, "try again")),
517
+ ".")),
518
+ !props.hasNextPage &&
519
+ !props.isLoadingMore &&
520
+ !props.commentsLoading &&
521
+ !props.loadMoreError &&
522
+ comuteSearchedComments.length > 0 &&
523
+ props.retryLoadMore !== undefined && (React.createElement("p", { className: "infinite-scroll-end-status" },
524
+ React.createElement(Trans, null, "No more comments to load")))))));
350
525
  }
@@ -10,7 +10,7 @@ interface UseCommentDraftsParams {
10
10
  applySignature: (text: string) => void;
11
11
  }
12
12
  export declare function useCommentDrafts({ caseNumber, loggedInUserSSO, isInternal, commentSignature, canPostPrivateComments, setCommentText, setIsPublic, applySignature, }: UseCommentDraftsParams): {
13
- saveDraft: (...args: any[]) => void;
13
+ saveDraft: import("lodash").DebouncedFunc<(value: string, publicFlag: boolean) => Promise<void>>;
14
14
  clearDraft: () => Promise<void>;
15
15
  };
16
16
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"useCommentDrafts.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAsB,MAAM,OAAO,CAAC;AAMrE,UAAU,sBAAsB;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,OAAO,CAAC;IAChC,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,UAAU,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,WAAW,EACX,cAAc,GACjB,EAAE,sBAAsB;;;EA4DxB"}
1
+ {"version":3,"file":"useCommentDrafts.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAsB,MAAM,OAAO,CAAC;AAMrE,UAAU,sBAAsB;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,OAAO,CAAC;IAChC,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,UAAU,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,WAAW,EACX,cAAc,GACjB,EAAE,sBAAsB;sDAGmB,MAAM,cAAc,OAAO;;EA2DtE"}
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { debounce } from '@patternfly/react-core';
10
+ import debounce from 'lodash/debounce';
11
11
  import { useEffect, useMemo } from 'react';
12
12
  import { CaseDetailsCacheNamespaces } from '../../../../../enums/cache';
13
13
  import { CaseDetailsCacheUtils } from '../../../../../utils/caseDetailsCacheUtils';
@@ -28,6 +28,8 @@ export function useCommentDrafts({ caseNumber, loggedInUserSSO, isInternal, comm
28
28
  });
29
29
  }, 500), [caseNumber, commentSignature, loggedInUserSSO]);
30
30
  const clearDraft = () => __awaiter(this, void 0, void 0, function* () {
31
+ // RTE onChange keeps scheduling saves; cancel so submit/reset cannot resurrect the draft.
32
+ saveDraft.cancel();
31
33
  const { value: prevDraftComments = {} } = (yield CaseDetailsCacheUtils.get(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO)) || {};
32
34
  delete prevDraftComments[caseNumber];
33
35
  CaseDetailsCacheUtils.set(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO, {
@@ -1 +1 @@
1
- {"version":3,"file":"PostComment.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/PostComment.tsx"],"names":[],"mappings":"AAUA,OAAO,KAA+D,MAAM,OAAO,CAAC;AAOpF,OAAO,EAAE,WAAW,EAAE,MAAM,+CAA+C,CAAC;AAmB5E,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,qBAobxC"}
1
+ {"version":3,"file":"PostComment.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/PostComment.tsx"],"names":[],"mappings":"AAUA,OAAO,KAA+D,MAAM,OAAO,CAAC;AAOpF,OAAO,EAAE,WAAW,EAAE,MAAM,+CAA+C,CAAC;AAmB5E,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,qBAibxC"}
@@ -36,7 +36,6 @@ export function PostComment(props) {
36
36
  var _a, _b, _c, _d, _e, _f, _g;
37
37
  const { t } = useTranslation();
38
38
  let { caseNumber } = props;
39
- const [isTextAreaFocused, setIsTextAreaFocused] = useState(false);
40
39
  const canPostPrivateComments = ability.can(resourceActions.PATCH, resources.CASE_COMMENTS, CaseDiscussionFields.POST_PRIVATE_COMMENT);
41
40
  const { createComment, loading: isPostingComment } = useCreateCaseComment();
42
41
  const dispatchToAttachmentReducer = useContext(AttachmentDispatchContext);
@@ -133,11 +132,10 @@ export function PostComment(props) {
133
132
  applySignature,
134
133
  });
135
134
  const resetCommentSection = () => __awaiter(this, void 0, void 0, function* () {
136
- setCommentText('');
137
- setIsTextAreaFocused(false);
138
- resetIsPublic();
139
135
  yield clearDraft();
140
- setCommentText(commentSignature ? `<p><br></p><p>${commentSignature}</p>` : '');
136
+ const resetText = commentSignature ? `<p><br></p><p>${commentSignature}</p>` : '';
137
+ setCommentText(resetText);
138
+ resetIsPublic();
141
139
  });
142
140
  const resetAttachmentSection = () => {
143
141
  resetAttachment(dispatchToAttachmentReducer);
@@ -259,18 +257,18 @@ export function PostComment(props) {
259
257
  const onFileUploadClick = () => {
260
258
  ToastNotification.addInfoMessage(t('Uploading Attachment(s)'));
261
259
  };
262
- const onTextAreaBlur = () => {
263
- if (!commentText)
264
- setIsTextAreaFocused(false);
265
- };
266
260
  const onCommentExceedCharsLimit = (lengthExceededNew) => {
267
261
  if (isCommentTooLong !== lengthExceededNew)
268
262
  setIsCommentTooLong(lengthExceededNew);
269
263
  };
270
264
  // Submit stays independent of attachment upload so comments can be posted mid-upload (PCM-15915).
271
- const isSubmitDisabled = isCommentEmpty || isPostCommentDisabled || (isConfirmedStateSideSupport && !cssCommentAgreed) || isCaseReadOnly;
265
+ const isSubmitDisabled = isCommentEmpty ||
266
+ isPostCommentDisabled ||
267
+ isCommentTooLong ||
268
+ (isConfirmedStateSideSupport && !cssCommentAgreed) ||
269
+ isCaseReadOnly;
272
270
  return (React.createElement("div", { className: `pf-v6-u-mb-md ${isExportingPDF ? 'hide-in-pdf' : ''}`, ref: componentRef },
273
- props.replyingToAuthor && (React.createElement("div", { className: "post-comment-reply-banner pf-v6-u-mb-sm", "data-tracking-id": "reply-banner" },
271
+ props.replyingToAuthor && (React.createElement("output", { className: "post-comment-reply-banner pf-v6-u-mb-sm", "aria-label": `Replying to ${props.replyingToAuthor}`, "data-tracking-id": "reply-banner" },
274
272
  React.createElement("span", null,
275
273
  React.createElement(Trans, null, "Replying to"),
276
274
  " ",
@@ -281,11 +279,7 @@ export function PostComment(props) {
281
279
  ")"))),
282
280
  React.createElement(Button, { variant: ButtonVariant.plain, "aria-label": t('Cancel reply'), onClick: props.onClearReply, "data-tracking-id": "cancel-reply-button" },
283
281
  React.createElement(TimesIcon, null)))),
284
- React.createElement(RichTextEditor, { key: `${caseNumber}-${isCaseReadOnly}`, className: `${!isPublic && canPostPrivateComments ? 'is-private' : ''}`, disabled: isProcessing || isCaseReadOnly, hideToolbar: isCaseReadOnly, "aria-label": "comment text area", onFocus: () => {
285
- if (isCaseReadOnly)
286
- return;
287
- setIsTextAreaFocused(true);
288
- }, onBlur: onTextAreaBlur, onChange: onCommentTextChange, value: commentText, height: isTextAreaFocused ? 313 : 120, onCharLimitExceeded: onCommentExceedCharsLimit, placeholder: !isPublic && canPostPrivateComments
282
+ React.createElement(RichTextEditor, { key: `${caseNumber}-${isCaseReadOnly}`, className: `${!isPublic && canPostPrivateComments ? 'is-private' : ''}`, disabled: isProcessing || isCaseReadOnly, hideToolbar: isCaseReadOnly, "aria-label": "comment text area", onChange: onCommentTextChange, value: commentText, height: 313, onCharLimitExceeded: onCommentExceedCharsLimit, placeholder: !isPublic && canPostPrivateComments
289
283
  ? 'Add a comment or attach a file privately'
290
284
  : 'Add a comment to this case', charLimit: 32000, onImageUpload: onImageUpload, bindEditor: (editor) => {
291
285
  quillEditorRef.current = editor;
@@ -303,7 +297,7 @@ export function PostComment(props) {
303
297
  React.createElement(PlusIcon, null),
304
298
  " ",
305
299
  React.createElement(Trans, null, "Attach files")) }),
306
- React.createElement(Button, { className: "secondary-button-pill", "data-tracking-id": "postcomment-cancel-button", type: "button", variant: ButtonVariant.link, onClick: onCancelClick, isDisabled: isCommentEmpty || isProcessing || isCaseReadOnly },
300
+ React.createElement(Button, { className: "secondary-button-pill", "data-tracking-id": "postcomment-cancel-button", type: "button", variant: ButtonVariant.link, onClick: onCancelClick, isDisabled: isCommentSignatureMatch() || isProcessing || isCaseReadOnly },
307
301
  React.createElement(Trans, null, "Cancel")),
308
302
  canPostPrivateComments && (React.createElement("span", { className: "post-comment-checkbox" },
309
303
  React.createElement(Checkbox, { label: t('Private'), isChecked: !isPublic && canPostPrivateComments, isDisabled: isCaseReadOnly, onChange: handleCheckboxToggle, "aria-label": t('Private'), id: "private-comment", name: "private-comment", "data-tracking-id": "private-comment-checkbox" }))))),
@@ -1 +1 @@
1
- {"version":3,"file":"VerifyCaseStatusModal.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/VerifyCaseStatusModal/VerifyCaseStatusModal.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAa3E,UAAU,MAAM;IACZ,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,qBAiJlD"}
1
+ {"version":3,"file":"VerifyCaseStatusModal.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/VerifyCaseStatusModal/VerifyCaseStatusModal.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAa3E,UAAU,MAAM;IACZ,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,qBA8IlD"}
@@ -71,7 +71,7 @@ export function VerifyCaseStatusModal(props) {
71
71
  }
72
72
  catch (e) {
73
73
  setStatusUpdating(caseOverviewDispatch, false);
74
- caseUpdateError.showError(e, t('Status failed to update, try refreshing the page. Please contact us if the problem persists.'));
74
+ caseUpdateError.showFieldError(e, t('Status'), () => updateStatusState(statusVal, onStatusUpdate));
75
75
  }
76
76
  }
77
77
  else {