@codingfactory/socialkit-vue 0.7.23 → 0.7.24
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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/circles.d.ts +7 -0
- package/dist/services/circles.d.ts.map +1 -1
- package/dist/services/circles.js +34 -5
- package/dist/services/circles.js.map +1 -1
- package/dist/stores/__tests__/discussion.spec.d.ts +2 -0
- package/dist/stores/__tests__/discussion.spec.d.ts.map +1 -0
- package/dist/stores/__tests__/discussion.spec.js +768 -0
- package/dist/stores/__tests__/discussion.spec.js.map +1 -0
- package/dist/stores/circles.d.ts +144 -0
- package/dist/stores/circles.d.ts.map +1 -1
- package/dist/stores/circles.js +7 -1
- package/dist/stores/circles.js.map +1 -1
- package/dist/stores/content.d.ts.map +1 -1
- package/dist/stores/content.js +6 -3
- package/dist/stores/content.js.map +1 -1
- package/dist/stores/discussion.d.ts +714 -15
- package/dist/stores/discussion.d.ts.map +1 -1
- package/dist/stores/discussion.js +272 -65
- package/dist/stores/discussion.js.map +1 -1
- package/dist/types/content.d.ts +3 -2
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/content.js +2 -1
- package/dist/types/content.js.map +1 -1
- package/dist/types/discussion.d.ts +38 -0
- package/dist/types/discussion.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/services/circles.ts +45 -5
- package/src/stores/__tests__/discussion.spec.ts +945 -0
- package/src/stores/circles.ts +7 -1
- package/src/stores/content.ts +6 -3
- package/src/stores/discussion.ts +333 -76
- package/src/types/content.ts +3 -2
- package/src/types/discussion.ts +43 -0
|
@@ -124,6 +124,8 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
124
124
|
const spaceMemberships = ref({});
|
|
125
125
|
const spaceMembershipLoading = ref({});
|
|
126
126
|
const threads = ref([]);
|
|
127
|
+
const browseThreadList = ref([]);
|
|
128
|
+
const currentBrowseMode = ref(null);
|
|
127
129
|
const currentThread = ref(null);
|
|
128
130
|
const replies = ref([]);
|
|
129
131
|
const currentReplySort = ref('best');
|
|
@@ -131,8 +133,10 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
131
133
|
const threadsLoadingState = createLoadingState();
|
|
132
134
|
const repliesLoadingState = createLoadingState();
|
|
133
135
|
const loading = ref(false);
|
|
136
|
+
let activeLoadingRequests = 0;
|
|
134
137
|
const error = ref(null);
|
|
135
138
|
const nextCursor = ref(null);
|
|
139
|
+
const browseNextCursor = ref(null);
|
|
136
140
|
const repliesNextCursor = ref(null);
|
|
137
141
|
const latestSpaceSlug = ref(null);
|
|
138
142
|
const latestThreadId = ref(null);
|
|
@@ -237,6 +241,17 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
237
241
|
}
|
|
238
242
|
return hasQuotedReplyBody(incomingQuotedReply) ? incomingQuotedReply : null;
|
|
239
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Check whether a reply-like object carries a truthy `is_author` flag.
|
|
246
|
+
* The field is not part of the Reply TS interface (it is user-specific
|
|
247
|
+
* metadata injected by the API) so we access it reflectively.
|
|
248
|
+
*/
|
|
249
|
+
function hasReplyAuthorFlag(reply) {
|
|
250
|
+
return Reflect.get(reply, 'is_author') === true;
|
|
251
|
+
}
|
|
252
|
+
function setReplyAuthorFlag(reply, value) {
|
|
253
|
+
Reflect.set(reply, 'is_author', value);
|
|
254
|
+
}
|
|
240
255
|
function normalizeReplyPayload(reply, existingReply) {
|
|
241
256
|
const normalized = { ...reply };
|
|
242
257
|
const hasQuotedReplyId = typeof normalized.quoted_reply_id === 'string'
|
|
@@ -448,6 +463,14 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
448
463
|
THREAD: 'thread',
|
|
449
464
|
REPLY: 'reply',
|
|
450
465
|
};
|
|
466
|
+
function beginLoading() {
|
|
467
|
+
activeLoadingRequests += 1;
|
|
468
|
+
loading.value = true;
|
|
469
|
+
}
|
|
470
|
+
function endLoading() {
|
|
471
|
+
activeLoadingRequests = Math.max(0, activeLoadingRequests - 1);
|
|
472
|
+
loading.value = activeLoadingRequests > 0;
|
|
473
|
+
}
|
|
451
474
|
function flattenTree(tree) {
|
|
452
475
|
const result = [];
|
|
453
476
|
function walk(nodes) {
|
|
@@ -467,12 +490,30 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
467
490
|
function leafSpaces() {
|
|
468
491
|
return spaces.value.filter((space) => space.is_leaf !== false);
|
|
469
492
|
}
|
|
470
|
-
|
|
493
|
+
function buildSpaceQueryString(options) {
|
|
494
|
+
const params = new URLSearchParams();
|
|
495
|
+
params.set('tree', options?.tree === false ? '0' : '1');
|
|
496
|
+
if (options?.kind) {
|
|
497
|
+
params.set('kind', options.kind);
|
|
498
|
+
}
|
|
499
|
+
if (typeof options?.scope_type === 'string' && options.scope_type.trim().length > 0) {
|
|
500
|
+
params.set('scope_type', options.scope_type.trim());
|
|
501
|
+
}
|
|
502
|
+
if (typeof options?.scope_id === 'string' && options.scope_id.trim().length > 0) {
|
|
503
|
+
params.set('scope_id', options.scope_id.trim());
|
|
504
|
+
}
|
|
505
|
+
if (typeof options?.parent_id === 'string' && options.parent_id.trim().length > 0) {
|
|
506
|
+
params.set('parent_id', options.parent_id.trim());
|
|
507
|
+
}
|
|
508
|
+
return params.toString();
|
|
509
|
+
}
|
|
510
|
+
async function loadSpaces(options) {
|
|
471
511
|
spacesLoadingState.setLoading(true);
|
|
472
|
-
|
|
512
|
+
beginLoading();
|
|
473
513
|
error.value = null;
|
|
474
514
|
try {
|
|
475
|
-
const
|
|
515
|
+
const queryString = buildSpaceQueryString(options);
|
|
516
|
+
const response = await client.get(`/v1/discussion/spaces?${queryString}`);
|
|
476
517
|
const responseData = toRecord(response.data);
|
|
477
518
|
const treeData = Array.isArray(responseData?.items)
|
|
478
519
|
? responseData.items
|
|
@@ -481,9 +522,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
481
522
|
: [];
|
|
482
523
|
spaceTree.value = treeData;
|
|
483
524
|
spaces.value = flattenTree(treeData);
|
|
484
|
-
|
|
485
|
-
spacesLoadingState.setEmpty(true);
|
|
486
|
-
}
|
|
525
|
+
spacesLoadingState.setEmpty(treeData.length === 0);
|
|
487
526
|
return response.data;
|
|
488
527
|
}
|
|
489
528
|
catch (err) {
|
|
@@ -495,7 +534,64 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
495
534
|
}
|
|
496
535
|
finally {
|
|
497
536
|
spacesLoadingState.setLoading(false);
|
|
498
|
-
|
|
537
|
+
endLoading();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function createSpace(input) {
|
|
541
|
+
beginLoading();
|
|
542
|
+
error.value = null;
|
|
543
|
+
try {
|
|
544
|
+
const response = await client.post('/v1/discussion/spaces', {
|
|
545
|
+
...input,
|
|
546
|
+
...(typeof input.parent_id === 'string' ? { parent_id: input.parent_id } : { parent_id: null }),
|
|
547
|
+
...(typeof input.description === 'string' && input.description.trim().length > 0
|
|
548
|
+
? { description: input.description.trim() }
|
|
549
|
+
: {}),
|
|
550
|
+
...(input.meta ? { meta: input.meta } : {}),
|
|
551
|
+
});
|
|
552
|
+
return response.data;
|
|
553
|
+
}
|
|
554
|
+
catch (err) {
|
|
555
|
+
logger.error('Failed to create discussion space:', err);
|
|
556
|
+
error.value = getErrorMessage(err, 'Failed to create discussion space');
|
|
557
|
+
throw err;
|
|
558
|
+
}
|
|
559
|
+
finally {
|
|
560
|
+
endLoading();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function moveSpace(spaceId, parentId) {
|
|
564
|
+
beginLoading();
|
|
565
|
+
error.value = null;
|
|
566
|
+
try {
|
|
567
|
+
const response = await client.post(`/v1/discussion/spaces/${spaceId}/move`, {
|
|
568
|
+
parent_id: parentId ?? null,
|
|
569
|
+
});
|
|
570
|
+
return response.data;
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
logger.error('Failed to move discussion space:', err);
|
|
574
|
+
error.value = getErrorMessage(err, 'Failed to move discussion space');
|
|
575
|
+
throw err;
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
endLoading();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function reorderSpaces(ids) {
|
|
582
|
+
beginLoading();
|
|
583
|
+
error.value = null;
|
|
584
|
+
try {
|
|
585
|
+
const response = await client.post('/v1/discussion/spaces/reorder', { ids });
|
|
586
|
+
return response.data;
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
logger.error('Failed to reorder discussion spaces:', err);
|
|
590
|
+
error.value = getErrorMessage(err, 'Failed to reorder discussion spaces');
|
|
591
|
+
throw err;
|
|
592
|
+
}
|
|
593
|
+
finally {
|
|
594
|
+
endLoading();
|
|
499
595
|
}
|
|
500
596
|
}
|
|
501
597
|
async function loadSpaceDetail(slug, options) {
|
|
@@ -577,9 +673,8 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
577
673
|
latestSpaceSlug.value = spaceSlug;
|
|
578
674
|
}
|
|
579
675
|
threadsLoadingState.setLoading(true);
|
|
580
|
-
|
|
676
|
+
beginLoading();
|
|
581
677
|
error.value = null;
|
|
582
|
-
let isStale = false;
|
|
583
678
|
try {
|
|
584
679
|
const queryParams = new URLSearchParams();
|
|
585
680
|
if (cursor) {
|
|
@@ -588,6 +683,9 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
588
683
|
if (options?.sort) {
|
|
589
684
|
queryParams.set('sort', options.sort);
|
|
590
685
|
}
|
|
686
|
+
if (options?.filter && options.filter !== 'all') {
|
|
687
|
+
queryParams.set('filter', options.filter);
|
|
688
|
+
}
|
|
591
689
|
const queryString = queryParams.toString();
|
|
592
690
|
const url = `/v1/discussion/spaces/${spaceSlug}/threads${queryString.length > 0 ? `?${queryString}` : ''}`;
|
|
593
691
|
const requestConfig = {
|
|
@@ -604,7 +702,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
604
702
|
response = await client.get(url, requestConfig);
|
|
605
703
|
}
|
|
606
704
|
if (latestSpaceSlug.value !== spaceSlug) {
|
|
607
|
-
isStale = true;
|
|
608
705
|
return response.data;
|
|
609
706
|
}
|
|
610
707
|
const newThreads = getThreadsFromPayload(response.data).map((thread) => normalizeThreadReplyCount(thread));
|
|
@@ -630,7 +727,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
630
727
|
}
|
|
631
728
|
catch (err) {
|
|
632
729
|
if (options?.signal?.aborted || latestSpaceSlug.value !== spaceSlug) {
|
|
633
|
-
isStale = true;
|
|
634
730
|
return undefined;
|
|
635
731
|
}
|
|
636
732
|
if (isAxiosError(err) && err.response?.status === 404) {
|
|
@@ -654,10 +750,67 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
654
750
|
throw err;
|
|
655
751
|
}
|
|
656
752
|
finally {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
753
|
+
threadsLoadingState.setLoading(false);
|
|
754
|
+
endLoading();
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async function browseThreads(view, cursor, options) {
|
|
758
|
+
if (!cursor || currentBrowseMode.value !== view) {
|
|
759
|
+
currentBrowseMode.value = view;
|
|
760
|
+
}
|
|
761
|
+
threadsLoadingState.setLoading(true);
|
|
762
|
+
beginLoading();
|
|
763
|
+
error.value = null;
|
|
764
|
+
try {
|
|
765
|
+
const queryParams = new URLSearchParams();
|
|
766
|
+
if (cursor) {
|
|
767
|
+
queryParams.set('cursor', cursor);
|
|
660
768
|
}
|
|
769
|
+
const queryString = queryParams.toString();
|
|
770
|
+
const url = `/v1/discussion/threads/${view}${queryString.length > 0 ? `?${queryString}` : ''}`;
|
|
771
|
+
const requestConfig = {
|
|
772
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
773
|
+
};
|
|
774
|
+
let response;
|
|
775
|
+
try {
|
|
776
|
+
response = await client.get(url, requestConfig);
|
|
777
|
+
}
|
|
778
|
+
catch (requestError) {
|
|
779
|
+
if (!isTransientThreadListError(requestError) || options?.signal?.aborted) {
|
|
780
|
+
throw requestError;
|
|
781
|
+
}
|
|
782
|
+
response = await client.get(url, requestConfig);
|
|
783
|
+
}
|
|
784
|
+
if (currentBrowseMode.value !== view) {
|
|
785
|
+
return response.data;
|
|
786
|
+
}
|
|
787
|
+
const newThreads = getThreadsFromPayload(response.data).map((thread) => normalizeThreadReplyCount(thread));
|
|
788
|
+
if (cursor) {
|
|
789
|
+
browseThreadList.value = mergeUniqueById(browseThreadList.value, newThreads);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
browseThreadList.value = newThreads;
|
|
793
|
+
if (newThreads.length === 0) {
|
|
794
|
+
threadsLoadingState.setEmpty(true);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
browseNextCursor.value = getThreadNextCursorFromPayload(response.data);
|
|
798
|
+
currentSpace.value = null;
|
|
799
|
+
return response.data;
|
|
800
|
+
}
|
|
801
|
+
catch (err) {
|
|
802
|
+
if (options?.signal?.aborted || currentBrowseMode.value !== view) {
|
|
803
|
+
return undefined;
|
|
804
|
+
}
|
|
805
|
+
logger.error('Failed to browse discussion threads:', err);
|
|
806
|
+
const errorMessage = getErrorMessage(err, 'Failed to load discussion threads');
|
|
807
|
+
threadsLoadingState.setError(new Error(errorMessage));
|
|
808
|
+
error.value = errorMessage;
|
|
809
|
+
throw err;
|
|
810
|
+
}
|
|
811
|
+
finally {
|
|
812
|
+
threadsLoadingState.setLoading(false);
|
|
813
|
+
endLoading();
|
|
661
814
|
}
|
|
662
815
|
}
|
|
663
816
|
function isValidUUID(uuid) {
|
|
@@ -672,24 +825,25 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
672
825
|
async function loadThread(spaceSlug, threadId) {
|
|
673
826
|
latestThreadId.value = threadId;
|
|
674
827
|
cleanupRealtimeChannels();
|
|
675
|
-
|
|
828
|
+
beginLoading();
|
|
676
829
|
error.value = null;
|
|
830
|
+
replies.value = [];
|
|
831
|
+
repliesNextCursor.value = null;
|
|
832
|
+
currentReplySort.value = 'best';
|
|
677
833
|
if (!threadId) {
|
|
678
834
|
error.value = 'Missing thread identifier';
|
|
679
|
-
|
|
835
|
+
endLoading();
|
|
680
836
|
return null;
|
|
681
837
|
}
|
|
682
838
|
// Accept both UUID and slug identifiers — the backend resolves by ID or slug
|
|
683
839
|
if (!/^[\w-]+$/u.test(threadId)) {
|
|
684
840
|
error.value = 'Invalid thread identifier format';
|
|
685
|
-
|
|
841
|
+
endLoading();
|
|
686
842
|
return null;
|
|
687
843
|
}
|
|
688
|
-
let isStale = false;
|
|
689
844
|
try {
|
|
690
845
|
const response = await client.get(`/v1/discussion/threads/${threadId}`);
|
|
691
846
|
if (latestThreadId.value !== threadId) {
|
|
692
|
-
isStale = true;
|
|
693
847
|
return null;
|
|
694
848
|
}
|
|
695
849
|
const responseData = toRecord(response.data);
|
|
@@ -739,7 +893,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
739
893
|
}
|
|
740
894
|
catch (err) {
|
|
741
895
|
if (latestThreadId.value !== threadId) {
|
|
742
|
-
isStale = true;
|
|
743
896
|
return null;
|
|
744
897
|
}
|
|
745
898
|
const errorResponseData = getErrorResponseData(err);
|
|
@@ -765,18 +918,15 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
765
918
|
throw err;
|
|
766
919
|
}
|
|
767
920
|
finally {
|
|
768
|
-
|
|
769
|
-
loading.value = false;
|
|
770
|
-
}
|
|
921
|
+
endLoading();
|
|
771
922
|
}
|
|
772
923
|
}
|
|
773
924
|
async function loadReplies(threadId, cursor, sortBy = 'best') {
|
|
774
925
|
if (latestThreadId.value === null) {
|
|
775
926
|
latestThreadId.value = threadId;
|
|
776
927
|
}
|
|
777
|
-
|
|
928
|
+
beginLoading();
|
|
778
929
|
error.value = null;
|
|
779
|
-
let isStale = false;
|
|
780
930
|
try {
|
|
781
931
|
const queryParts = [];
|
|
782
932
|
if (cursor) {
|
|
@@ -788,7 +938,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
788
938
|
const queryString = queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
|
|
789
939
|
const response = await client.get(`/v1/discussion/threads/${threadId}/replies${queryString}`);
|
|
790
940
|
if (latestThreadId.value !== threadId) {
|
|
791
|
-
isStale = true;
|
|
792
941
|
return undefined;
|
|
793
942
|
}
|
|
794
943
|
const responseData = toRecord(response.data);
|
|
@@ -848,7 +997,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
848
997
|
}
|
|
849
998
|
catch (err) {
|
|
850
999
|
if (latestThreadId.value !== threadId) {
|
|
851
|
-
isStale = true;
|
|
852
1000
|
return undefined;
|
|
853
1001
|
}
|
|
854
1002
|
logger.error('Failed to load replies:', err);
|
|
@@ -856,9 +1004,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
856
1004
|
throw err;
|
|
857
1005
|
}
|
|
858
1006
|
finally {
|
|
859
|
-
|
|
860
|
-
loading.value = false;
|
|
861
|
-
}
|
|
1007
|
+
endLoading();
|
|
862
1008
|
}
|
|
863
1009
|
}
|
|
864
1010
|
function insertTopLevelReply(reply) {
|
|
@@ -873,10 +1019,17 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
873
1019
|
if (existingReplyIndex !== -1) {
|
|
874
1020
|
const existingReply = replies.value[existingReplyIndex];
|
|
875
1021
|
if (existingReply) {
|
|
876
|
-
|
|
1022
|
+
const merged = {
|
|
877
1023
|
...existingReply,
|
|
878
1024
|
...reply,
|
|
879
1025
|
};
|
|
1026
|
+
// Broadcast payloads lack an authenticated user context, so
|
|
1027
|
+
// is_author is always false in them. Preserve the value from
|
|
1028
|
+
// the original authenticated API response when it was true.
|
|
1029
|
+
if (hasReplyAuthorFlag(existingReply) && !hasReplyAuthorFlag(reply)) {
|
|
1030
|
+
setReplyAuthorFlag(merged, true);
|
|
1031
|
+
}
|
|
1032
|
+
replies.value[existingReplyIndex] = merged;
|
|
880
1033
|
}
|
|
881
1034
|
return false;
|
|
882
1035
|
}
|
|
@@ -914,7 +1067,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
914
1067
|
}
|
|
915
1068
|
async function createThread(spaceSlug, input) {
|
|
916
1069
|
cleanupRealtimeChannels();
|
|
917
|
-
|
|
1070
|
+
beginLoading();
|
|
918
1071
|
error.value = null;
|
|
919
1072
|
try {
|
|
920
1073
|
const response = await client.post(`/v1/discussion/spaces/${spaceSlug}/threads`, {
|
|
@@ -942,11 +1095,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
942
1095
|
throw err;
|
|
943
1096
|
}
|
|
944
1097
|
finally {
|
|
945
|
-
|
|
1098
|
+
endLoading();
|
|
946
1099
|
}
|
|
947
1100
|
}
|
|
948
1101
|
async function loadTagCategories(query = '') {
|
|
949
|
-
|
|
1102
|
+
beginLoading();
|
|
950
1103
|
error.value = null;
|
|
951
1104
|
try {
|
|
952
1105
|
const normalizedQuery = query.trim();
|
|
@@ -980,7 +1133,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
980
1133
|
throw err;
|
|
981
1134
|
}
|
|
982
1135
|
finally {
|
|
983
|
-
|
|
1136
|
+
endLoading();
|
|
984
1137
|
}
|
|
985
1138
|
}
|
|
986
1139
|
function cleanupRealtimeChannels() {
|
|
@@ -1222,10 +1375,17 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1222
1375
|
if (!existingReply) {
|
|
1223
1376
|
return;
|
|
1224
1377
|
}
|
|
1225
|
-
|
|
1378
|
+
const merged = {
|
|
1226
1379
|
...existingReply,
|
|
1227
1380
|
...payload,
|
|
1228
|
-
}
|
|
1381
|
+
};
|
|
1382
|
+
// Broadcast payloads lack an authenticated user context, so
|
|
1383
|
+
// is_author is always false in them. Preserve the value from
|
|
1384
|
+
// the original authenticated API response when it was true.
|
|
1385
|
+
if (hasReplyAuthorFlag(existingReply) && !hasReplyAuthorFlag(payload)) {
|
|
1386
|
+
setReplyAuthorFlag(merged, true);
|
|
1387
|
+
}
|
|
1388
|
+
replies.value[replyIndex] = normalizeReplyPayload(merged, existingReply);
|
|
1229
1389
|
}
|
|
1230
1390
|
function handleRealtimeReplyReaction(payload) {
|
|
1231
1391
|
if (!payload?.reply_id || !payload.thread_id || !payload.user_id) {
|
|
@@ -1344,40 +1504,49 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1344
1504
|
throw err;
|
|
1345
1505
|
}
|
|
1346
1506
|
}
|
|
1347
|
-
async function searchThreads(query, spaceSlug) {
|
|
1348
|
-
|
|
1507
|
+
async function searchThreads(query, spaceSlug, options) {
|
|
1508
|
+
beginLoading();
|
|
1349
1509
|
error.value = null;
|
|
1350
1510
|
try {
|
|
1351
1511
|
const params = new URLSearchParams({ q: query });
|
|
1352
1512
|
if (spaceSlug) {
|
|
1353
1513
|
params.append('space', spaceSlug);
|
|
1354
1514
|
}
|
|
1355
|
-
const
|
|
1515
|
+
const requestConfig = options?.signal
|
|
1516
|
+
? { signal: options.signal }
|
|
1517
|
+
: undefined;
|
|
1518
|
+
const response = await client.get(`/v1/discussion/search/threads?${params.toString()}`, requestConfig);
|
|
1356
1519
|
return response.data;
|
|
1357
1520
|
}
|
|
1358
1521
|
catch (err) {
|
|
1522
|
+
if (options?.signal?.aborted) {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1359
1525
|
logger.error('Failed to search threads:', err);
|
|
1360
1526
|
error.value = getErrorMessage(err, 'Failed to search');
|
|
1361
1527
|
throw err;
|
|
1362
1528
|
}
|
|
1363
1529
|
finally {
|
|
1364
|
-
|
|
1530
|
+
endLoading();
|
|
1365
1531
|
}
|
|
1366
1532
|
}
|
|
1367
|
-
async function searchThreadsInSpace(query, spaceId) {
|
|
1533
|
+
async function searchThreadsInSpace(query, spaceId, options) {
|
|
1368
1534
|
const normalizedQuery = query.trim();
|
|
1369
1535
|
if (normalizedQuery.length < 2) {
|
|
1370
1536
|
return [];
|
|
1371
1537
|
}
|
|
1372
|
-
|
|
1538
|
+
beginLoading();
|
|
1373
1539
|
error.value = null;
|
|
1374
1540
|
try {
|
|
1541
|
+
const requestConfig = options?.signal
|
|
1542
|
+
? { signal: options.signal }
|
|
1543
|
+
: undefined;
|
|
1375
1544
|
const response = await client.post('/v1/discussion/search', {
|
|
1376
1545
|
query: normalizedQuery,
|
|
1377
1546
|
space_id: spaceId,
|
|
1378
1547
|
sort_by: 'relevance',
|
|
1379
1548
|
limit: 50,
|
|
1380
|
-
});
|
|
1549
|
+
}, requestConfig);
|
|
1381
1550
|
const rows = extractThreadSearchRows(response.data);
|
|
1382
1551
|
if (rows.length > 0) {
|
|
1383
1552
|
return rows.map((row) => mapSearchRowToThread(row, spaceId));
|
|
@@ -1385,6 +1554,9 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1385
1554
|
return filterLoadedThreadsByQuery(normalizedQuery, spaceId);
|
|
1386
1555
|
}
|
|
1387
1556
|
catch (err) {
|
|
1557
|
+
if (options?.signal?.aborted) {
|
|
1558
|
+
return [];
|
|
1559
|
+
}
|
|
1388
1560
|
logger.error('Failed to search threads in space:', err);
|
|
1389
1561
|
error.value = isAxiosError(err)
|
|
1390
1562
|
? err.response?.data?.message ?? 'Failed to search threads'
|
|
@@ -1392,25 +1564,31 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1392
1564
|
throw err;
|
|
1393
1565
|
}
|
|
1394
1566
|
finally {
|
|
1395
|
-
|
|
1567
|
+
endLoading();
|
|
1396
1568
|
}
|
|
1397
1569
|
}
|
|
1398
|
-
async function searchThreadsGlobally(query) {
|
|
1570
|
+
async function searchThreadsGlobally(query, options) {
|
|
1399
1571
|
const normalizedQuery = query.trim();
|
|
1400
1572
|
if (normalizedQuery.length < 2) {
|
|
1401
1573
|
return [];
|
|
1402
1574
|
}
|
|
1403
|
-
|
|
1575
|
+
beginLoading();
|
|
1404
1576
|
error.value = null;
|
|
1405
1577
|
try {
|
|
1578
|
+
const requestConfig = options?.signal
|
|
1579
|
+
? { signal: options.signal }
|
|
1580
|
+
: undefined;
|
|
1406
1581
|
const response = await client.post('/v1/discussion/search', {
|
|
1407
1582
|
query: normalizedQuery,
|
|
1408
1583
|
sort_by: 'relevance',
|
|
1409
1584
|
limit: 50,
|
|
1410
|
-
});
|
|
1585
|
+
}, requestConfig);
|
|
1411
1586
|
return extractThreadSearchRows(response.data).map((row) => mapSearchRowToThread(row));
|
|
1412
1587
|
}
|
|
1413
1588
|
catch (err) {
|
|
1589
|
+
if (options?.signal?.aborted) {
|
|
1590
|
+
return [];
|
|
1591
|
+
}
|
|
1414
1592
|
logger.error('Failed to search threads globally:', err);
|
|
1415
1593
|
error.value = isAxiosError(err)
|
|
1416
1594
|
? err.response?.data?.message ?? 'Failed to search threads'
|
|
@@ -1418,7 +1596,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1418
1596
|
throw err;
|
|
1419
1597
|
}
|
|
1420
1598
|
finally {
|
|
1421
|
-
|
|
1599
|
+
endLoading();
|
|
1422
1600
|
}
|
|
1423
1601
|
}
|
|
1424
1602
|
function mapSearchRowToThread(row, fallbackSpaceId) {
|
|
@@ -1446,6 +1624,14 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1446
1624
|
if (typeof row.slug === 'string') {
|
|
1447
1625
|
thread.slug = row.slug;
|
|
1448
1626
|
}
|
|
1627
|
+
const spaceId = row.space_id ?? fallbackSpaceId ?? '';
|
|
1628
|
+
if (typeof row.space_name === 'string' && typeof row.space_slug === 'string' && spaceId) {
|
|
1629
|
+
thread.space = {
|
|
1630
|
+
id: spaceId,
|
|
1631
|
+
slug: row.space_slug,
|
|
1632
|
+
name: row.space_name,
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1449
1635
|
return thread;
|
|
1450
1636
|
}
|
|
1451
1637
|
function extractThreadSearchRows(responseBody) {
|
|
@@ -1481,7 +1667,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1481
1667
|
return spaces.value.filter((space) => space.meta?.is_featured);
|
|
1482
1668
|
}
|
|
1483
1669
|
async function setThreadPinned(threadId, pinned) {
|
|
1484
|
-
|
|
1670
|
+
beginLoading();
|
|
1485
1671
|
error.value = null;
|
|
1486
1672
|
try {
|
|
1487
1673
|
await client.post(`/v1/discussion/threads/${threadId}/pin`, { pinned });
|
|
@@ -1500,11 +1686,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1500
1686
|
throw err;
|
|
1501
1687
|
}
|
|
1502
1688
|
finally {
|
|
1503
|
-
|
|
1689
|
+
endLoading();
|
|
1504
1690
|
}
|
|
1505
1691
|
}
|
|
1506
1692
|
async function setThreadLocked(threadId, locked) {
|
|
1507
|
-
|
|
1693
|
+
beginLoading();
|
|
1508
1694
|
error.value = null;
|
|
1509
1695
|
try {
|
|
1510
1696
|
await client.post(`/v1/discussion/threads/${threadId}/lock`, { locked });
|
|
@@ -1523,11 +1709,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1523
1709
|
throw err;
|
|
1524
1710
|
}
|
|
1525
1711
|
finally {
|
|
1526
|
-
|
|
1712
|
+
endLoading();
|
|
1527
1713
|
}
|
|
1528
1714
|
}
|
|
1529
1715
|
async function moveThread(threadId, toSpaceSlug) {
|
|
1530
|
-
|
|
1716
|
+
beginLoading();
|
|
1531
1717
|
error.value = null;
|
|
1532
1718
|
try {
|
|
1533
1719
|
const response = await client.post(`/v1/discussion/threads/${threadId}/move`, {
|
|
@@ -1536,11 +1722,21 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1536
1722
|
const responseData = toRecord(response.data);
|
|
1537
1723
|
const movedThread = (responseData?.data ?? null);
|
|
1538
1724
|
const destinationSpace = spaces.value.find((space) => space.slug === toSpaceSlug);
|
|
1725
|
+
const destinationSpaceSummary = destinationSpace
|
|
1726
|
+
? {
|
|
1727
|
+
id: destinationSpace.id,
|
|
1728
|
+
slug: destinationSpace.slug,
|
|
1729
|
+
name: destinationSpace.name,
|
|
1730
|
+
}
|
|
1731
|
+
: null;
|
|
1539
1732
|
if (currentThread.value?.id === threadId && currentThread.value) {
|
|
1540
1733
|
currentThread.value = {
|
|
1541
1734
|
...currentThread.value,
|
|
1542
1735
|
...(movedThread ?? {}),
|
|
1543
|
-
...(
|
|
1736
|
+
...(destinationSpaceSummary ? {
|
|
1737
|
+
space_id: destinationSpaceSummary.id,
|
|
1738
|
+
space: destinationSpaceSummary,
|
|
1739
|
+
} : {}),
|
|
1544
1740
|
};
|
|
1545
1741
|
}
|
|
1546
1742
|
const currentSpaceSlug = currentSpace.value?.slug ?? null;
|
|
@@ -1557,7 +1753,10 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1557
1753
|
threads.value[threadIndex] = {
|
|
1558
1754
|
...existingThread,
|
|
1559
1755
|
...(movedThread ?? {}),
|
|
1560
|
-
...(
|
|
1756
|
+
...(destinationSpaceSummary ? {
|
|
1757
|
+
space_id: destinationSpaceSummary.id,
|
|
1758
|
+
space: destinationSpaceSummary,
|
|
1759
|
+
} : {}),
|
|
1561
1760
|
};
|
|
1562
1761
|
}
|
|
1563
1762
|
}
|
|
@@ -1569,11 +1768,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1569
1768
|
throw err;
|
|
1570
1769
|
}
|
|
1571
1770
|
finally {
|
|
1572
|
-
|
|
1771
|
+
endLoading();
|
|
1573
1772
|
}
|
|
1574
1773
|
}
|
|
1575
1774
|
async function updateThread(threadId, updates) {
|
|
1576
|
-
|
|
1775
|
+
beginLoading();
|
|
1577
1776
|
error.value = null;
|
|
1578
1777
|
try {
|
|
1579
1778
|
const response = await client.patch(`/v1/discussion/threads/${threadId}`, {
|
|
@@ -1603,11 +1802,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1603
1802
|
throw err;
|
|
1604
1803
|
}
|
|
1605
1804
|
finally {
|
|
1606
|
-
|
|
1805
|
+
endLoading();
|
|
1607
1806
|
}
|
|
1608
1807
|
}
|
|
1609
1808
|
async function deleteThread(threadId) {
|
|
1610
|
-
|
|
1809
|
+
beginLoading();
|
|
1611
1810
|
error.value = null;
|
|
1612
1811
|
try {
|
|
1613
1812
|
const response = await client.delete(`/v1/discussion/threads/${threadId}`);
|
|
@@ -1623,11 +1822,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1623
1822
|
throw err;
|
|
1624
1823
|
}
|
|
1625
1824
|
finally {
|
|
1626
|
-
|
|
1825
|
+
endLoading();
|
|
1627
1826
|
}
|
|
1628
1827
|
}
|
|
1629
1828
|
async function restoreThread(threadId) {
|
|
1630
|
-
|
|
1829
|
+
beginLoading();
|
|
1631
1830
|
error.value = null;
|
|
1632
1831
|
try {
|
|
1633
1832
|
const response = await client.post(`/v1/discussion/threads/${threadId}/restore`);
|
|
@@ -1668,11 +1867,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1668
1867
|
throw err;
|
|
1669
1868
|
}
|
|
1670
1869
|
finally {
|
|
1671
|
-
|
|
1870
|
+
endLoading();
|
|
1672
1871
|
}
|
|
1673
1872
|
}
|
|
1674
1873
|
async function deleteReply(replyId) {
|
|
1675
|
-
|
|
1874
|
+
beginLoading();
|
|
1676
1875
|
error.value = null;
|
|
1677
1876
|
const replyIndex = replies.value.findIndex((reply) => reply.id === replyId);
|
|
1678
1877
|
const replyToRestore = replyIndex >= 0 ? replies.value[replyIndex] ?? null : null;
|
|
@@ -1730,7 +1929,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1730
1929
|
throw err;
|
|
1731
1930
|
}
|
|
1732
1931
|
finally {
|
|
1733
|
-
|
|
1932
|
+
endLoading();
|
|
1734
1933
|
}
|
|
1735
1934
|
}
|
|
1736
1935
|
async function updateReply(replyId, body) {
|
|
@@ -1963,16 +2162,23 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1963
2162
|
spaceMembershipLoading,
|
|
1964
2163
|
cleanupRealtimeChannels,
|
|
1965
2164
|
threads,
|
|
2165
|
+
browseThreadList,
|
|
2166
|
+
currentBrowseMode,
|
|
1966
2167
|
currentThread,
|
|
1967
2168
|
replies,
|
|
2169
|
+
currentReplySort,
|
|
1968
2170
|
loading,
|
|
1969
2171
|
error,
|
|
1970
2172
|
nextCursor,
|
|
2173
|
+
browseNextCursor,
|
|
1971
2174
|
repliesNextCursor,
|
|
1972
2175
|
spacesLoadingState,
|
|
1973
2176
|
threadsLoadingState,
|
|
1974
2177
|
repliesLoadingState,
|
|
1975
2178
|
loadSpaces,
|
|
2179
|
+
createSpace,
|
|
2180
|
+
moveSpace,
|
|
2181
|
+
reorderSpaces,
|
|
1976
2182
|
loadSpaceDetail,
|
|
1977
2183
|
loadSpaceMembership,
|
|
1978
2184
|
joinSpace,
|
|
@@ -1982,6 +2188,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1982
2188
|
rootSpaces,
|
|
1983
2189
|
leafSpaces,
|
|
1984
2190
|
loadThreads,
|
|
2191
|
+
browseThreads,
|
|
1985
2192
|
loadThread,
|
|
1986
2193
|
loadReplies,
|
|
1987
2194
|
createThread,
|