@codingfactory/socialkit-vue 0.7.22 → 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 +274 -66
- 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 +335 -77
- 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,23 +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
|
|
839
|
+
if (!/^[\w-]+$/u.test(threadId)) {
|
|
683
840
|
error.value = 'Invalid thread identifier format';
|
|
684
|
-
|
|
841
|
+
endLoading();
|
|
685
842
|
return null;
|
|
686
843
|
}
|
|
687
|
-
let isStale = false;
|
|
688
844
|
try {
|
|
689
845
|
const response = await client.get(`/v1/discussion/threads/${threadId}`);
|
|
690
846
|
if (latestThreadId.value !== threadId) {
|
|
691
|
-
isStale = true;
|
|
692
847
|
return null;
|
|
693
848
|
}
|
|
694
849
|
const responseData = toRecord(response.data);
|
|
@@ -738,7 +893,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
738
893
|
}
|
|
739
894
|
catch (err) {
|
|
740
895
|
if (latestThreadId.value !== threadId) {
|
|
741
|
-
isStale = true;
|
|
742
896
|
return null;
|
|
743
897
|
}
|
|
744
898
|
const errorResponseData = getErrorResponseData(err);
|
|
@@ -764,18 +918,15 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
764
918
|
throw err;
|
|
765
919
|
}
|
|
766
920
|
finally {
|
|
767
|
-
|
|
768
|
-
loading.value = false;
|
|
769
|
-
}
|
|
921
|
+
endLoading();
|
|
770
922
|
}
|
|
771
923
|
}
|
|
772
924
|
async function loadReplies(threadId, cursor, sortBy = 'best') {
|
|
773
925
|
if (latestThreadId.value === null) {
|
|
774
926
|
latestThreadId.value = threadId;
|
|
775
927
|
}
|
|
776
|
-
|
|
928
|
+
beginLoading();
|
|
777
929
|
error.value = null;
|
|
778
|
-
let isStale = false;
|
|
779
930
|
try {
|
|
780
931
|
const queryParts = [];
|
|
781
932
|
if (cursor) {
|
|
@@ -787,7 +938,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
787
938
|
const queryString = queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
|
|
788
939
|
const response = await client.get(`/v1/discussion/threads/${threadId}/replies${queryString}`);
|
|
789
940
|
if (latestThreadId.value !== threadId) {
|
|
790
|
-
isStale = true;
|
|
791
941
|
return undefined;
|
|
792
942
|
}
|
|
793
943
|
const responseData = toRecord(response.data);
|
|
@@ -847,7 +997,6 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
847
997
|
}
|
|
848
998
|
catch (err) {
|
|
849
999
|
if (latestThreadId.value !== threadId) {
|
|
850
|
-
isStale = true;
|
|
851
1000
|
return undefined;
|
|
852
1001
|
}
|
|
853
1002
|
logger.error('Failed to load replies:', err);
|
|
@@ -855,9 +1004,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
855
1004
|
throw err;
|
|
856
1005
|
}
|
|
857
1006
|
finally {
|
|
858
|
-
|
|
859
|
-
loading.value = false;
|
|
860
|
-
}
|
|
1007
|
+
endLoading();
|
|
861
1008
|
}
|
|
862
1009
|
}
|
|
863
1010
|
function insertTopLevelReply(reply) {
|
|
@@ -872,10 +1019,17 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
872
1019
|
if (existingReplyIndex !== -1) {
|
|
873
1020
|
const existingReply = replies.value[existingReplyIndex];
|
|
874
1021
|
if (existingReply) {
|
|
875
|
-
|
|
1022
|
+
const merged = {
|
|
876
1023
|
...existingReply,
|
|
877
1024
|
...reply,
|
|
878
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;
|
|
879
1033
|
}
|
|
880
1034
|
return false;
|
|
881
1035
|
}
|
|
@@ -913,7 +1067,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
913
1067
|
}
|
|
914
1068
|
async function createThread(spaceSlug, input) {
|
|
915
1069
|
cleanupRealtimeChannels();
|
|
916
|
-
|
|
1070
|
+
beginLoading();
|
|
917
1071
|
error.value = null;
|
|
918
1072
|
try {
|
|
919
1073
|
const response = await client.post(`/v1/discussion/spaces/${spaceSlug}/threads`, {
|
|
@@ -941,11 +1095,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
941
1095
|
throw err;
|
|
942
1096
|
}
|
|
943
1097
|
finally {
|
|
944
|
-
|
|
1098
|
+
endLoading();
|
|
945
1099
|
}
|
|
946
1100
|
}
|
|
947
1101
|
async function loadTagCategories(query = '') {
|
|
948
|
-
|
|
1102
|
+
beginLoading();
|
|
949
1103
|
error.value = null;
|
|
950
1104
|
try {
|
|
951
1105
|
const normalizedQuery = query.trim();
|
|
@@ -979,7 +1133,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
979
1133
|
throw err;
|
|
980
1134
|
}
|
|
981
1135
|
finally {
|
|
982
|
-
|
|
1136
|
+
endLoading();
|
|
983
1137
|
}
|
|
984
1138
|
}
|
|
985
1139
|
function cleanupRealtimeChannels() {
|
|
@@ -1221,10 +1375,17 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1221
1375
|
if (!existingReply) {
|
|
1222
1376
|
return;
|
|
1223
1377
|
}
|
|
1224
|
-
|
|
1378
|
+
const merged = {
|
|
1225
1379
|
...existingReply,
|
|
1226
1380
|
...payload,
|
|
1227
|
-
}
|
|
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);
|
|
1228
1389
|
}
|
|
1229
1390
|
function handleRealtimeReplyReaction(payload) {
|
|
1230
1391
|
if (!payload?.reply_id || !payload.thread_id || !payload.user_id) {
|
|
@@ -1343,40 +1504,49 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1343
1504
|
throw err;
|
|
1344
1505
|
}
|
|
1345
1506
|
}
|
|
1346
|
-
async function searchThreads(query, spaceSlug) {
|
|
1347
|
-
|
|
1507
|
+
async function searchThreads(query, spaceSlug, options) {
|
|
1508
|
+
beginLoading();
|
|
1348
1509
|
error.value = null;
|
|
1349
1510
|
try {
|
|
1350
1511
|
const params = new URLSearchParams({ q: query });
|
|
1351
1512
|
if (spaceSlug) {
|
|
1352
1513
|
params.append('space', spaceSlug);
|
|
1353
1514
|
}
|
|
1354
|
-
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);
|
|
1355
1519
|
return response.data;
|
|
1356
1520
|
}
|
|
1357
1521
|
catch (err) {
|
|
1522
|
+
if (options?.signal?.aborted) {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1358
1525
|
logger.error('Failed to search threads:', err);
|
|
1359
1526
|
error.value = getErrorMessage(err, 'Failed to search');
|
|
1360
1527
|
throw err;
|
|
1361
1528
|
}
|
|
1362
1529
|
finally {
|
|
1363
|
-
|
|
1530
|
+
endLoading();
|
|
1364
1531
|
}
|
|
1365
1532
|
}
|
|
1366
|
-
async function searchThreadsInSpace(query, spaceId) {
|
|
1533
|
+
async function searchThreadsInSpace(query, spaceId, options) {
|
|
1367
1534
|
const normalizedQuery = query.trim();
|
|
1368
1535
|
if (normalizedQuery.length < 2) {
|
|
1369
1536
|
return [];
|
|
1370
1537
|
}
|
|
1371
|
-
|
|
1538
|
+
beginLoading();
|
|
1372
1539
|
error.value = null;
|
|
1373
1540
|
try {
|
|
1541
|
+
const requestConfig = options?.signal
|
|
1542
|
+
? { signal: options.signal }
|
|
1543
|
+
: undefined;
|
|
1374
1544
|
const response = await client.post('/v1/discussion/search', {
|
|
1375
1545
|
query: normalizedQuery,
|
|
1376
1546
|
space_id: spaceId,
|
|
1377
1547
|
sort_by: 'relevance',
|
|
1378
1548
|
limit: 50,
|
|
1379
|
-
});
|
|
1549
|
+
}, requestConfig);
|
|
1380
1550
|
const rows = extractThreadSearchRows(response.data);
|
|
1381
1551
|
if (rows.length > 0) {
|
|
1382
1552
|
return rows.map((row) => mapSearchRowToThread(row, spaceId));
|
|
@@ -1384,6 +1554,9 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1384
1554
|
return filterLoadedThreadsByQuery(normalizedQuery, spaceId);
|
|
1385
1555
|
}
|
|
1386
1556
|
catch (err) {
|
|
1557
|
+
if (options?.signal?.aborted) {
|
|
1558
|
+
return [];
|
|
1559
|
+
}
|
|
1387
1560
|
logger.error('Failed to search threads in space:', err);
|
|
1388
1561
|
error.value = isAxiosError(err)
|
|
1389
1562
|
? err.response?.data?.message ?? 'Failed to search threads'
|
|
@@ -1391,25 +1564,31 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1391
1564
|
throw err;
|
|
1392
1565
|
}
|
|
1393
1566
|
finally {
|
|
1394
|
-
|
|
1567
|
+
endLoading();
|
|
1395
1568
|
}
|
|
1396
1569
|
}
|
|
1397
|
-
async function searchThreadsGlobally(query) {
|
|
1570
|
+
async function searchThreadsGlobally(query, options) {
|
|
1398
1571
|
const normalizedQuery = query.trim();
|
|
1399
1572
|
if (normalizedQuery.length < 2) {
|
|
1400
1573
|
return [];
|
|
1401
1574
|
}
|
|
1402
|
-
|
|
1575
|
+
beginLoading();
|
|
1403
1576
|
error.value = null;
|
|
1404
1577
|
try {
|
|
1578
|
+
const requestConfig = options?.signal
|
|
1579
|
+
? { signal: options.signal }
|
|
1580
|
+
: undefined;
|
|
1405
1581
|
const response = await client.post('/v1/discussion/search', {
|
|
1406
1582
|
query: normalizedQuery,
|
|
1407
1583
|
sort_by: 'relevance',
|
|
1408
1584
|
limit: 50,
|
|
1409
|
-
});
|
|
1585
|
+
}, requestConfig);
|
|
1410
1586
|
return extractThreadSearchRows(response.data).map((row) => mapSearchRowToThread(row));
|
|
1411
1587
|
}
|
|
1412
1588
|
catch (err) {
|
|
1589
|
+
if (options?.signal?.aborted) {
|
|
1590
|
+
return [];
|
|
1591
|
+
}
|
|
1413
1592
|
logger.error('Failed to search threads globally:', err);
|
|
1414
1593
|
error.value = isAxiosError(err)
|
|
1415
1594
|
? err.response?.data?.message ?? 'Failed to search threads'
|
|
@@ -1417,7 +1596,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1417
1596
|
throw err;
|
|
1418
1597
|
}
|
|
1419
1598
|
finally {
|
|
1420
|
-
|
|
1599
|
+
endLoading();
|
|
1421
1600
|
}
|
|
1422
1601
|
}
|
|
1423
1602
|
function mapSearchRowToThread(row, fallbackSpaceId) {
|
|
@@ -1445,6 +1624,14 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1445
1624
|
if (typeof row.slug === 'string') {
|
|
1446
1625
|
thread.slug = row.slug;
|
|
1447
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
|
+
}
|
|
1448
1635
|
return thread;
|
|
1449
1636
|
}
|
|
1450
1637
|
function extractThreadSearchRows(responseBody) {
|
|
@@ -1480,7 +1667,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1480
1667
|
return spaces.value.filter((space) => space.meta?.is_featured);
|
|
1481
1668
|
}
|
|
1482
1669
|
async function setThreadPinned(threadId, pinned) {
|
|
1483
|
-
|
|
1670
|
+
beginLoading();
|
|
1484
1671
|
error.value = null;
|
|
1485
1672
|
try {
|
|
1486
1673
|
await client.post(`/v1/discussion/threads/${threadId}/pin`, { pinned });
|
|
@@ -1499,11 +1686,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1499
1686
|
throw err;
|
|
1500
1687
|
}
|
|
1501
1688
|
finally {
|
|
1502
|
-
|
|
1689
|
+
endLoading();
|
|
1503
1690
|
}
|
|
1504
1691
|
}
|
|
1505
1692
|
async function setThreadLocked(threadId, locked) {
|
|
1506
|
-
|
|
1693
|
+
beginLoading();
|
|
1507
1694
|
error.value = null;
|
|
1508
1695
|
try {
|
|
1509
1696
|
await client.post(`/v1/discussion/threads/${threadId}/lock`, { locked });
|
|
@@ -1522,11 +1709,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1522
1709
|
throw err;
|
|
1523
1710
|
}
|
|
1524
1711
|
finally {
|
|
1525
|
-
|
|
1712
|
+
endLoading();
|
|
1526
1713
|
}
|
|
1527
1714
|
}
|
|
1528
1715
|
async function moveThread(threadId, toSpaceSlug) {
|
|
1529
|
-
|
|
1716
|
+
beginLoading();
|
|
1530
1717
|
error.value = null;
|
|
1531
1718
|
try {
|
|
1532
1719
|
const response = await client.post(`/v1/discussion/threads/${threadId}/move`, {
|
|
@@ -1535,11 +1722,21 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1535
1722
|
const responseData = toRecord(response.data);
|
|
1536
1723
|
const movedThread = (responseData?.data ?? null);
|
|
1537
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;
|
|
1538
1732
|
if (currentThread.value?.id === threadId && currentThread.value) {
|
|
1539
1733
|
currentThread.value = {
|
|
1540
1734
|
...currentThread.value,
|
|
1541
1735
|
...(movedThread ?? {}),
|
|
1542
|
-
...(
|
|
1736
|
+
...(destinationSpaceSummary ? {
|
|
1737
|
+
space_id: destinationSpaceSummary.id,
|
|
1738
|
+
space: destinationSpaceSummary,
|
|
1739
|
+
} : {}),
|
|
1543
1740
|
};
|
|
1544
1741
|
}
|
|
1545
1742
|
const currentSpaceSlug = currentSpace.value?.slug ?? null;
|
|
@@ -1556,7 +1753,10 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1556
1753
|
threads.value[threadIndex] = {
|
|
1557
1754
|
...existingThread,
|
|
1558
1755
|
...(movedThread ?? {}),
|
|
1559
|
-
...(
|
|
1756
|
+
...(destinationSpaceSummary ? {
|
|
1757
|
+
space_id: destinationSpaceSummary.id,
|
|
1758
|
+
space: destinationSpaceSummary,
|
|
1759
|
+
} : {}),
|
|
1560
1760
|
};
|
|
1561
1761
|
}
|
|
1562
1762
|
}
|
|
@@ -1568,11 +1768,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1568
1768
|
throw err;
|
|
1569
1769
|
}
|
|
1570
1770
|
finally {
|
|
1571
|
-
|
|
1771
|
+
endLoading();
|
|
1572
1772
|
}
|
|
1573
1773
|
}
|
|
1574
1774
|
async function updateThread(threadId, updates) {
|
|
1575
|
-
|
|
1775
|
+
beginLoading();
|
|
1576
1776
|
error.value = null;
|
|
1577
1777
|
try {
|
|
1578
1778
|
const response = await client.patch(`/v1/discussion/threads/${threadId}`, {
|
|
@@ -1602,11 +1802,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1602
1802
|
throw err;
|
|
1603
1803
|
}
|
|
1604
1804
|
finally {
|
|
1605
|
-
|
|
1805
|
+
endLoading();
|
|
1606
1806
|
}
|
|
1607
1807
|
}
|
|
1608
1808
|
async function deleteThread(threadId) {
|
|
1609
|
-
|
|
1809
|
+
beginLoading();
|
|
1610
1810
|
error.value = null;
|
|
1611
1811
|
try {
|
|
1612
1812
|
const response = await client.delete(`/v1/discussion/threads/${threadId}`);
|
|
@@ -1622,11 +1822,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1622
1822
|
throw err;
|
|
1623
1823
|
}
|
|
1624
1824
|
finally {
|
|
1625
|
-
|
|
1825
|
+
endLoading();
|
|
1626
1826
|
}
|
|
1627
1827
|
}
|
|
1628
1828
|
async function restoreThread(threadId) {
|
|
1629
|
-
|
|
1829
|
+
beginLoading();
|
|
1630
1830
|
error.value = null;
|
|
1631
1831
|
try {
|
|
1632
1832
|
const response = await client.post(`/v1/discussion/threads/${threadId}/restore`);
|
|
@@ -1667,11 +1867,11 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1667
1867
|
throw err;
|
|
1668
1868
|
}
|
|
1669
1869
|
finally {
|
|
1670
|
-
|
|
1870
|
+
endLoading();
|
|
1671
1871
|
}
|
|
1672
1872
|
}
|
|
1673
1873
|
async function deleteReply(replyId) {
|
|
1674
|
-
|
|
1874
|
+
beginLoading();
|
|
1675
1875
|
error.value = null;
|
|
1676
1876
|
const replyIndex = replies.value.findIndex((reply) => reply.id === replyId);
|
|
1677
1877
|
const replyToRestore = replyIndex >= 0 ? replies.value[replyIndex] ?? null : null;
|
|
@@ -1729,7 +1929,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1729
1929
|
throw err;
|
|
1730
1930
|
}
|
|
1731
1931
|
finally {
|
|
1732
|
-
|
|
1932
|
+
endLoading();
|
|
1733
1933
|
}
|
|
1734
1934
|
}
|
|
1735
1935
|
async function updateReply(replyId, body) {
|
|
@@ -1962,16 +2162,23 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1962
2162
|
spaceMembershipLoading,
|
|
1963
2163
|
cleanupRealtimeChannels,
|
|
1964
2164
|
threads,
|
|
2165
|
+
browseThreadList,
|
|
2166
|
+
currentBrowseMode,
|
|
1965
2167
|
currentThread,
|
|
1966
2168
|
replies,
|
|
2169
|
+
currentReplySort,
|
|
1967
2170
|
loading,
|
|
1968
2171
|
error,
|
|
1969
2172
|
nextCursor,
|
|
2173
|
+
browseNextCursor,
|
|
1970
2174
|
repliesNextCursor,
|
|
1971
2175
|
spacesLoadingState,
|
|
1972
2176
|
threadsLoadingState,
|
|
1973
2177
|
repliesLoadingState,
|
|
1974
2178
|
loadSpaces,
|
|
2179
|
+
createSpace,
|
|
2180
|
+
moveSpace,
|
|
2181
|
+
reorderSpaces,
|
|
1975
2182
|
loadSpaceDetail,
|
|
1976
2183
|
loadSpaceMembership,
|
|
1977
2184
|
joinSpace,
|
|
@@ -1981,6 +2188,7 @@ export function createDiscussionStoreDefinition(config) {
|
|
|
1981
2188
|
rootSpaces,
|
|
1982
2189
|
leafSpaces,
|
|
1983
2190
|
loadThreads,
|
|
2191
|
+
browseThreads,
|
|
1984
2192
|
loadThread,
|
|
1985
2193
|
loadReplies,
|
|
1986
2194
|
createThread,
|