@qoretechnologies/reqraft 0.10.27-pr.74.gc5f6ca3 → 0.10.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +8 -1
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
- package/dist/components/searchFilterBar/index.d.ts +2 -0
- package/dist/components/searchFilterBar/index.d.ts.map +1 -0
- package/dist/components/searchFilterBar/index.js +18 -0
- package/dist/components/searchFilterBar/index.js.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +9 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.js +2 -2
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts +52 -6
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.js +240 -31
- package/dist/components/supportTicket/ReferencePicker.js.map +1 -1
- package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.js +132 -0
- package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
- package/dist/components/supportTicket/index.d.ts +2 -0
- package/dist/components/supportTicket/index.d.ts.map +1 -1
- package/dist/components/supportTicket/index.js +2 -0
- package/dist/components/supportTicket/index.js.map +1 -1
- package/dist/components/supportTicket/meta.d.ts +8 -0
- package/dist/components/supportTicket/meta.d.ts.map +1 -1
- package/dist/components/supportTicket/meta.js +37 -1
- package/dist/components/supportTicket/meta.js.map +1 -1
- package/dist/components/supportTicket/ticketModel.d.ts +102 -0
- package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
- package/dist/components/supportTicket/ticketModel.js +3 -0
- package/dist/components/supportTicket/ticketModel.js.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -1
- package/dist/components/ticketReferences/TicketReferences.js +109 -78
- package/dist/components/ticketReferences/TicketReferences.js.map +1 -1
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
- package/dist/components/ticketThread/TicketThread.js +19 -2
- package/dist/components/ticketThread/TicketThread.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/engine/CompactRow.tsx +7 -0
- package/src/components/form/engine/FormEngine.stories.tsx +90 -0
- package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
- package/src/components/searchFilterBar/index.ts +1 -0
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +37 -0
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +11 -0
- package/src/components/supportTicket/ReferencePicker.stories.tsx +257 -34
- package/src/components/supportTicket/ReferencePicker.tsx +421 -95
- package/src/components/supportTicket/TicketConversation.stories.tsx +0 -1
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -4
- package/src/components/supportTicket/attachmentAccess.ts +97 -0
- package/src/components/supportTicket/index.ts +2 -0
- package/src/components/supportTicket/meta.ts +40 -0
- package/src/components/supportTicket/ticketModel.ts +131 -0
- package/src/components/ticketReferences/TicketReferences.tsx +124 -145
- package/src/components/ticketThread/TicketThread.stories.tsx +24 -2
- package/src/components/ticketThread/TicketThread.tsx +23 -3
- package/src/index.tsx +3 -1
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
ReqoreDropdown,
|
|
6
6
|
ReqoreEmptyState,
|
|
7
7
|
ReqoreEntityRow,
|
|
8
|
-
ReqoreInput,
|
|
9
8
|
ReqorePanel,
|
|
10
9
|
ReqoreSpan,
|
|
11
10
|
ReqoreSpinner,
|
|
@@ -17,6 +16,7 @@ import type { IReqoreEntityRowAction } from '@qoretechnologies/reqore/dist/compo
|
|
|
17
16
|
import type { IReqorePanelSubAction } from '@qoretechnologies/reqore/dist/components/Panel';
|
|
18
17
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
19
18
|
import timeago from 'epoch-timeago';
|
|
19
|
+
import { SearchFilterBar } from '../searchFilterBar';
|
|
20
20
|
import styled from 'styled-components';
|
|
21
21
|
import { formatBytes, isImage } from '../../helpers/common';
|
|
22
22
|
import { ImageLightbox } from '../imageLightbox';
|
|
@@ -233,23 +233,23 @@ const useImageUrls = (
|
|
|
233
233
|
}
|
|
234
234
|
let active = true;
|
|
235
235
|
const created: string[] = [];
|
|
236
|
-
Promise.allSettled(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const next: Record<string, string | null> = {};
|
|
243
|
-
results.forEach((r, i) => {
|
|
244
|
-
if (r.status === 'fulfilled') {
|
|
245
|
-
next[r.value.id] = r.value.url;
|
|
246
|
-
created.push(r.value.url);
|
|
247
|
-
} else {
|
|
248
|
-
next[ids[i]] = null;
|
|
236
|
+
Promise.allSettled(ids.map((id) => fetchAttachmentUrl(id).then((url) => ({ id, url })))).then(
|
|
237
|
+
(results) => {
|
|
238
|
+
if (!active) {
|
|
239
|
+
return;
|
|
249
240
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
241
|
+
const next: Record<string, string | null> = {};
|
|
242
|
+
results.forEach((r, i) => {
|
|
243
|
+
if (r.status === 'fulfilled') {
|
|
244
|
+
next[r.value.id] = r.value.url;
|
|
245
|
+
created.push(r.value.url);
|
|
246
|
+
} else {
|
|
247
|
+
next[ids[i]] = null;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
setUrls(next);
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
253
|
return () => {
|
|
254
254
|
active = false;
|
|
255
255
|
created.forEach((url) => URL.revokeObjectURL(url));
|
|
@@ -269,17 +269,6 @@ const useImageUrls = (
|
|
|
269
269
|
* scroll container. Free-floating, it was easy to lose track of — and scrolling the
|
|
270
270
|
* sections took the search box away with them, which is exactly when you want it.
|
|
271
271
|
*/
|
|
272
|
-
const Toolbar = styled.div`
|
|
273
|
-
position: sticky;
|
|
274
|
-
top: 0;
|
|
275
|
-
z-index: 2;
|
|
276
|
-
padding: 8px;
|
|
277
|
-
border-radius: 8px;
|
|
278
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
279
|
-
background: rgba(255, 255, 255, 0.04);
|
|
280
|
-
backdrop-filter: blur(6px);
|
|
281
|
-
`;
|
|
282
|
-
|
|
283
272
|
/*
|
|
284
273
|
* A screenshot tile: the image is the button, and the overflow menu is a sibling
|
|
285
274
|
* layered over it — a <button> cannot legally contain another button, so the menu
|
|
@@ -371,19 +360,18 @@ const Screenshot = ({
|
|
|
371
360
|
}) => (
|
|
372
361
|
<ThumbTile>
|
|
373
362
|
<Thumb type='button' title={att.filename} onClick={onOpen}>
|
|
374
|
-
{url ?
|
|
363
|
+
{url ?
|
|
375
364
|
<img src={url} alt={att.filename} />
|
|
376
|
-
|
|
377
|
-
<ReqoreSpan effect={{ opacity: 0.5, textSize: 'small' }}>
|
|
365
|
+
: <ReqoreSpan effect={{ opacity: 0.5, textSize: 'small' }}>
|
|
378
366
|
{url === null ? att.filename : '…'}
|
|
379
367
|
</ReqoreSpan>
|
|
380
|
-
|
|
368
|
+
}
|
|
381
369
|
<ThumbCaption>{att.filename}</ThumbCaption>
|
|
382
370
|
</Thumb>
|
|
383
371
|
{/* One action doesn't earn a menu. With a single entry the overlay IS that
|
|
384
372
|
action, so a tile and a file row offer the same thing the same way instead of
|
|
385
373
|
hiding a download behind a caret on one surface and not the other. */}
|
|
386
|
-
{menu?.length === 1 ?
|
|
374
|
+
{menu?.length === 1 ?
|
|
387
375
|
<ReqoreButton
|
|
388
376
|
className='screenshot-menu'
|
|
389
377
|
icon={menu[0].icon}
|
|
@@ -397,7 +385,7 @@ const Screenshot = ({
|
|
|
397
385
|
menu[0].onClick?.(menu[0]);
|
|
398
386
|
}}
|
|
399
387
|
/>
|
|
400
|
-
|
|
388
|
+
: menu?.length ?
|
|
401
389
|
<ReqoreDropdown
|
|
402
390
|
className='screenshot-menu'
|
|
403
391
|
icon='More2Line'
|
|
@@ -410,7 +398,7 @@ const Screenshot = ({
|
|
|
410
398
|
items={menu}
|
|
411
399
|
onClick={(event: React.MouseEvent<HTMLElement>) => event.stopPropagation()}
|
|
412
400
|
/>
|
|
413
|
-
|
|
401
|
+
: null}
|
|
414
402
|
</ThumbTile>
|
|
415
403
|
);
|
|
416
404
|
|
|
@@ -468,8 +456,8 @@ export const TicketReferences = ({
|
|
|
468
456
|
if (!onShowInChat) {
|
|
469
457
|
return [];
|
|
470
458
|
}
|
|
471
|
-
return messageId
|
|
472
|
-
|
|
459
|
+
return messageId ?
|
|
460
|
+
[{ label: 'Show in chat', icon: 'Chat1Line', onClick: () => onShowInChat(messageId) }]
|
|
473
461
|
: [
|
|
474
462
|
{
|
|
475
463
|
label: 'Show in chat',
|
|
@@ -488,17 +476,17 @@ export const TicketReferences = ({
|
|
|
488
476
|
|
|
489
477
|
return [
|
|
490
478
|
...(primary ? [primary] : []),
|
|
491
|
-
...(menu.length
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
479
|
+
...(menu.length ?
|
|
480
|
+
[
|
|
481
|
+
{
|
|
482
|
+
icon: 'More2Line' as IReqoreIconName,
|
|
483
|
+
tooltip: 'More actions',
|
|
484
|
+
flat: true,
|
|
485
|
+
minimal: true,
|
|
486
|
+
actions: menu,
|
|
487
|
+
},
|
|
488
|
+
]
|
|
489
|
+
: []),
|
|
502
490
|
];
|
|
503
491
|
};
|
|
504
492
|
|
|
@@ -598,10 +586,10 @@ export const TicketReferences = ({
|
|
|
598
586
|
return allRefs
|
|
599
587
|
.filter((r) => matchesText(r.interface_name, r.interface_kind))
|
|
600
588
|
.sort((a, b) =>
|
|
601
|
-
sort === 'name'
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
589
|
+
sort === 'name' ?
|
|
590
|
+
a.interface_name.localeCompare(b.interface_name)
|
|
591
|
+
: a.interface_kind.localeCompare(b.interface_kind) ||
|
|
592
|
+
a.interface_name.localeCompare(b.interface_name)
|
|
605
593
|
);
|
|
606
594
|
}, [allRefs, filter, matchesText, sort]);
|
|
607
595
|
|
|
@@ -660,62 +648,53 @@ export const TicketReferences = ({
|
|
|
660
648
|
const filterOptions = FILTER_ORDER.filter((key) => key === 'all' || counts[key] > 0);
|
|
661
649
|
const nothingVisible = !visibleRefs.length && !visibleImages.length && !visibleFiles.length;
|
|
662
650
|
|
|
663
|
-
const controlBar =
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
<ReqoreControlGroup fluid wrap verticalAlign='center' gapSize='small' size='small'>
|
|
668
|
-
<ReqoreInput
|
|
669
|
-
icon='Search2Line'
|
|
670
|
-
placeholder='Search files and references…'
|
|
651
|
+
const controlBar =
|
|
652
|
+
showControls ?
|
|
653
|
+
<SearchFilterBar
|
|
654
|
+
sticky
|
|
671
655
|
value={search}
|
|
672
|
-
onChange={
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
656
|
+
onChange={setSearch}
|
|
657
|
+
placeholder='Search files and references…'
|
|
658
|
+
filters={[
|
|
659
|
+
{
|
|
660
|
+
icon: FILTER_META[filter].icon,
|
|
661
|
+
label: FILTER_META[filter].label,
|
|
662
|
+
items: filterOptions.map((key) => ({
|
|
663
|
+
selected: filter === key,
|
|
664
|
+
icon: FILTER_META[key].icon,
|
|
665
|
+
label:
|
|
666
|
+
key === 'all' ?
|
|
667
|
+
FILTER_META.all.label
|
|
668
|
+
: `${FILTER_META[key].label} (${counts[key]})`,
|
|
669
|
+
onClick: () => setFilter(key),
|
|
670
|
+
})),
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
icon: 'ArrowUpDownLine',
|
|
674
|
+
label: SORT_META[sort].label,
|
|
675
|
+
items: SORT_ORDER.map((key) => ({
|
|
676
|
+
selected: sort === key,
|
|
677
|
+
icon: SORT_META[key].icon,
|
|
678
|
+
label: SORT_META[key].label,
|
|
679
|
+
onClick: () => setSort(key),
|
|
680
|
+
})),
|
|
681
|
+
},
|
|
682
|
+
]}
|
|
689
683
|
/>
|
|
690
|
-
|
|
691
|
-
icon='ArrowUpDownLine'
|
|
692
|
-
label={SORT_META[sort].label}
|
|
693
|
-
minimal
|
|
694
|
-
fixed
|
|
695
|
-
flat
|
|
696
|
-
items={SORT_ORDER.map((key) => ({
|
|
697
|
-
selected: sort === key,
|
|
698
|
-
icon: SORT_META[key].icon,
|
|
699
|
-
label: SORT_META[key].label,
|
|
700
|
-
onClick: () => setSort(key),
|
|
701
|
-
}))}
|
|
702
|
-
/>
|
|
703
|
-
</ReqoreControlGroup>
|
|
704
|
-
</Toolbar>
|
|
705
|
-
) : null;
|
|
684
|
+
: null;
|
|
706
685
|
|
|
707
686
|
return (
|
|
708
687
|
<ReqoreControlGroup vertical fluid gapSize='big'>
|
|
709
688
|
{controlBar}
|
|
710
689
|
|
|
711
|
-
{nothingVisible ?
|
|
690
|
+
{nothingVisible ?
|
|
712
691
|
<ReqoreEmptyState
|
|
713
692
|
icon='Search2Line'
|
|
714
693
|
title='No matching references or files'
|
|
715
694
|
description={
|
|
716
|
-
query
|
|
717
|
-
|
|
718
|
-
|
|
695
|
+
query ?
|
|
696
|
+
`Nothing here matches “${search.trim()}”.`
|
|
697
|
+
: 'Nothing matches the current filter.'
|
|
719
698
|
}
|
|
720
699
|
flat
|
|
721
700
|
transparent
|
|
@@ -732,9 +711,9 @@ export const TicketReferences = ({
|
|
|
732
711
|
</ReqoreButton>
|
|
733
712
|
}
|
|
734
713
|
/>
|
|
735
|
-
|
|
714
|
+
: null}
|
|
736
715
|
|
|
737
|
-
{visibleImages.length ?
|
|
716
|
+
{visibleImages.length ?
|
|
738
717
|
<ReqorePanel
|
|
739
718
|
label='Screenshots'
|
|
740
719
|
icon='ImageLine'
|
|
@@ -752,29 +731,29 @@ export const TicketReferences = ({
|
|
|
752
731
|
att={att}
|
|
753
732
|
url={urls[att.attachment_id]}
|
|
754
733
|
onOpen={() =>
|
|
755
|
-
urls[att.attachment_id]
|
|
756
|
-
|
|
757
|
-
|
|
734
|
+
urls[att.attachment_id] ?
|
|
735
|
+
setLightbox(i)
|
|
736
|
+
: onDownloadAttachment?.(att.attachment_id, att.filename)
|
|
758
737
|
}
|
|
759
738
|
menu={[
|
|
760
|
-
...(onDownloadAttachment
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
739
|
+
...(onDownloadAttachment ?
|
|
740
|
+
[
|
|
741
|
+
{
|
|
742
|
+
label: 'Download',
|
|
743
|
+
icon: 'DownloadLine' as IReqoreIconName,
|
|
744
|
+
onClick: () => onDownloadAttachment(att.attachment_id, att.filename),
|
|
745
|
+
},
|
|
746
|
+
]
|
|
747
|
+
: []),
|
|
769
748
|
...showInChatItem(att.message_id),
|
|
770
749
|
]}
|
|
771
750
|
/>
|
|
772
751
|
))}
|
|
773
752
|
</ReqoreColumns>
|
|
774
753
|
</ReqorePanel>
|
|
775
|
-
|
|
754
|
+
: null}
|
|
776
755
|
|
|
777
|
-
{visibleRefs.length ?
|
|
756
|
+
{visibleRefs.length ?
|
|
778
757
|
<ReqorePanel
|
|
779
758
|
label='Linked interfaces'
|
|
780
759
|
icon='LinksLine'
|
|
@@ -808,18 +787,18 @@ export const TicketReferences = ({
|
|
|
808
787
|
}
|
|
809
788
|
flat
|
|
810
789
|
tooltip={
|
|
811
|
-
onInterfaceClick
|
|
812
|
-
|
|
813
|
-
|
|
790
|
+
onInterfaceClick ?
|
|
791
|
+
`Open ${reference.interface_kind} ${reference.interface_name}`
|
|
792
|
+
: undefined
|
|
814
793
|
}
|
|
815
794
|
actions={rowActions(
|
|
816
|
-
onInterfaceClick
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
795
|
+
onInterfaceClick ?
|
|
796
|
+
{
|
|
797
|
+
icon: 'ArrowRightUpLine',
|
|
798
|
+
tooltip: 'Open',
|
|
799
|
+
onClick: () => onInterfaceClick(reference),
|
|
800
|
+
}
|
|
801
|
+
: undefined,
|
|
823
802
|
reference.message_id
|
|
824
803
|
)}
|
|
825
804
|
/>
|
|
@@ -827,9 +806,9 @@ export const TicketReferences = ({
|
|
|
827
806
|
})}
|
|
828
807
|
</ReqoreControlGroup>
|
|
829
808
|
</ReqorePanel>
|
|
830
|
-
|
|
809
|
+
: null}
|
|
831
810
|
|
|
832
|
-
{visibleFiles.length ?
|
|
811
|
+
{visibleFiles.length ?
|
|
833
812
|
<ReqorePanel
|
|
834
813
|
label='Files'
|
|
835
814
|
icon='FileList3Line'
|
|
@@ -840,16 +819,16 @@ export const TicketReferences = ({
|
|
|
840
819
|
labelSize={4}
|
|
841
820
|
collapsible
|
|
842
821
|
actions={
|
|
843
|
-
visibleFiles.length > 1 && onDownloadAttachment
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
822
|
+
visibleFiles.length > 1 && onDownloadAttachment ?
|
|
823
|
+
[
|
|
824
|
+
{
|
|
825
|
+
icon: 'DownloadCloud2Line',
|
|
826
|
+
label: 'Download all',
|
|
827
|
+
onClick: () =>
|
|
828
|
+
visibleFiles.forEach((f) => onDownloadAttachment(f.attachment_id, f.filename)),
|
|
829
|
+
},
|
|
830
|
+
]
|
|
831
|
+
: []
|
|
853
832
|
}
|
|
854
833
|
>
|
|
855
834
|
{/* matches the interface rows above — the two lists read as one surface */}
|
|
@@ -876,13 +855,13 @@ export const TicketReferences = ({
|
|
|
876
855
|
metadata={<ReqoreTextEffect effect={META_EFFECT}>{provenance}</ReqoreTextEffect>}
|
|
877
856
|
flat
|
|
878
857
|
actions={rowActions(
|
|
879
|
-
onDownloadAttachment
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
858
|
+
onDownloadAttachment ?
|
|
859
|
+
{
|
|
860
|
+
icon: 'DownloadLine',
|
|
861
|
+
tooltip: 'Download',
|
|
862
|
+
onClick: () => onDownloadAttachment(f.attachment_id, f.filename),
|
|
863
|
+
}
|
|
864
|
+
: undefined,
|
|
886
865
|
f.message_id
|
|
887
866
|
)}
|
|
888
867
|
/>
|
|
@@ -890,9 +869,9 @@ export const TicketReferences = ({
|
|
|
890
869
|
})}
|
|
891
870
|
</ReqoreControlGroup>
|
|
892
871
|
</ReqorePanel>
|
|
893
|
-
|
|
872
|
+
: null}
|
|
894
873
|
|
|
895
|
-
{lightbox !== null && fetchAttachmentUrl ?
|
|
874
|
+
{lightbox !== null && fetchAttachmentUrl ?
|
|
896
875
|
<ImageLightbox
|
|
897
876
|
images={visibleImages}
|
|
898
877
|
index={lightbox}
|
|
@@ -901,7 +880,7 @@ export const TicketReferences = ({
|
|
|
901
880
|
fetchAttachmentUrl={fetchAttachmentUrl}
|
|
902
881
|
onDownload={onDownloadAttachment}
|
|
903
882
|
/>
|
|
904
|
-
|
|
883
|
+
: null}
|
|
905
884
|
</ReqoreControlGroup>
|
|
906
885
|
);
|
|
907
886
|
};
|
|
@@ -329,11 +329,11 @@ export const Loading: Story = {
|
|
|
329
329
|
docs: {
|
|
330
330
|
description: {
|
|
331
331
|
story:
|
|
332
|
-
"
|
|
332
|
+
"The FIRST load — loading with nothing to show yet — renders a conversation-shaped skeleton: aria-busy, labelled 'Loading conversation', carrying no message content, rather than a spinner. `messages` is empty deliberately; that is what makes this a first load rather than a refetch (see RefetchKeepsTheConversation).",
|
|
333
333
|
},
|
|
334
334
|
},
|
|
335
335
|
},
|
|
336
|
-
args: { loading: true },
|
|
336
|
+
args: { loading: true, messages: [] },
|
|
337
337
|
async play({ canvasElement }) {
|
|
338
338
|
// the loading state is a conversation-shaped skeleton, not a spinner
|
|
339
339
|
const skeleton = canvasElement.querySelector('[aria-busy="true"]');
|
|
@@ -344,6 +344,28 @@ export const Loading: Story = {
|
|
|
344
344
|
},
|
|
345
345
|
};
|
|
346
346
|
|
|
347
|
+
export const RefetchKeepsTheConversation: Story = {
|
|
348
|
+
parameters: {
|
|
349
|
+
docs: {
|
|
350
|
+
description: {
|
|
351
|
+
story:
|
|
352
|
+
'Loading with messages already on screen is a REFETCH, not a first load, and must leave the conversation alone. Every send triggers one — and where the thread is live, so does the other party sending — so blanking it here replaces a conversation the reader is in the middle of with pulsing placeholders, at the worst possible moment. The new message simply appears when the refetch resolves.',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
args: { loading: true },
|
|
357
|
+
async play({ canvasElement }) {
|
|
358
|
+
const canvas = within(canvasElement);
|
|
359
|
+
// no skeleton: the thread is already showing something worth keeping
|
|
360
|
+
await expect(canvasElement.querySelector('[aria-busy="true"]')).toBeNull();
|
|
361
|
+
// ...and the existing messages are still on screen. Asserted on message bodies
|
|
362
|
+
// rather than author labels, which vary with `viewerRole`; getAllByText because a
|
|
363
|
+
// body can legitimately appear in more than one node.
|
|
364
|
+
await expect(canvas.getAllByText(/nightly/).length).toBeGreaterThan(0);
|
|
365
|
+
await expect(canvas.getAllByText(/datasource pool/).length).toBeGreaterThan(0);
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
347
369
|
export const Empty: Story = {
|
|
348
370
|
parameters: {
|
|
349
371
|
docs: {
|
|
@@ -610,7 +610,12 @@ export const TicketThread = ({
|
|
|
610
610
|
? openingRefs
|
|
611
611
|
: message.referenced_interfaces;
|
|
612
612
|
|
|
613
|
-
|
|
613
|
+
// Only the FIRST load gets the skeleton. A refetch while messages are already on
|
|
614
|
+
// screen must leave them there: every send triggers one (and, where the thread is
|
|
615
|
+
// live, so does the other party sending), and blanking a conversation you are
|
|
616
|
+
// reading — replacing it with pulsing placeholders — is the single worst moment to
|
|
617
|
+
// do it. The new message simply appears when the refetch resolves.
|
|
618
|
+
if (loading && !messages.length) {
|
|
614
619
|
return <ConversationSkeleton />;
|
|
615
620
|
}
|
|
616
621
|
|
|
@@ -647,8 +652,23 @@ export const TicketThread = ({
|
|
|
647
652
|
avatar={{ icon: authorIcon(message) }}
|
|
648
653
|
label={authorLabel(message, viewerRole)}
|
|
649
654
|
timestamp={message.created ? <ReqoreTimeAgo time={message.created} /> : undefined}
|
|
650
|
-
|
|
651
|
-
|
|
655
|
+
// Own messages read as info-blue, the other party's as the purple
|
|
656
|
+
// accent — so the two sides of the conversation are told apart by
|
|
657
|
+
// colour as well as by side, and "which of these did I write?" is
|
|
658
|
+
// answerable at a glance. Deliberately keyed off `viewerRole`, so it
|
|
659
|
+
// stays correct on the staff surface too (staff replies are the blue
|
|
660
|
+
// ones there). Internal notes keep their warning intent and are not
|
|
661
|
+
// re-tinted — they are neither side of the conversation.
|
|
662
|
+
intent={
|
|
663
|
+
message.internal
|
|
664
|
+
? 'warning'
|
|
665
|
+
: message.author_type === viewerRole
|
|
666
|
+
? 'info'
|
|
667
|
+
: undefined
|
|
668
|
+
}
|
|
669
|
+
customTheme={
|
|
670
|
+
message.internal || message.author_type === viewerRole ? undefined : ACCENT
|
|
671
|
+
}
|
|
652
672
|
flat={false}
|
|
653
673
|
minimal
|
|
654
674
|
>
|
package/src/index.tsx
CHANGED
|
@@ -35,7 +35,7 @@ export type {
|
|
|
35
35
|
ICurrentUser,
|
|
36
36
|
ICurrentUserStore,
|
|
37
37
|
} from './stores/currentUser/currentUser';
|
|
38
|
-
export { query } from './utils/fetch';
|
|
38
|
+
export { fetchConfig, query } from './utils/fetch';
|
|
39
39
|
export * from './utils/websocket';
|
|
40
40
|
export { ReqraftLspClient } from './utils/lspClient';
|
|
41
41
|
export type { IReqraftLspClientOptions } from './utils/lspClient';
|
|
@@ -60,3 +60,5 @@ export * from './components/dpqlEditor';
|
|
|
60
60
|
export * from './components/qonsoleSmartInput';
|
|
61
61
|
export * from './components/supportTicket';
|
|
62
62
|
export * from './components/ticketThread';
|
|
63
|
+
export * from './components/ticketReferences';
|
|
64
|
+
export * from './components/searchFilterBar';
|