178 lines
5.6 KiB
JavaScript
178 lines
5.6 KiB
JavaScript
import {
|
|
useCallback, useContext, useEffect, useMemo,
|
|
} from 'react';
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
|
|
|
import { EndorsementStatus } from '../../../data/constants';
|
|
import useDispatchWithState from '../../../data/hooks';
|
|
import DiscussionContext from '../../common/context';
|
|
import { selectThread } from '../../posts/data/selectors';
|
|
import { markThreadAsRead } from '../../posts/data/thunks';
|
|
import { filterPosts } from '../../utils';
|
|
import {
|
|
selectCommentSortOrder, selectDraftComments, selectDraftResponses,
|
|
selectThreadComments, selectThreadCurrentPage, selectThreadHasMorePages,
|
|
} from './selectors';
|
|
import { fetchThreadComments } from './thunks';
|
|
|
|
const trackLoadMoreEvent = (postId, params) => (
|
|
sendTrackEvent(
|
|
'edx.forum.responses.loadMore',
|
|
{
|
|
postId,
|
|
params,
|
|
},
|
|
)
|
|
);
|
|
|
|
export function usePost(postId) {
|
|
const dispatch = useDispatch();
|
|
const thread = useSelector(selectThread(postId));
|
|
|
|
useEffect(() => {
|
|
if (thread && !thread.read) {
|
|
dispatch(markThreadAsRead(postId));
|
|
}
|
|
}, [postId]);
|
|
|
|
return thread || {};
|
|
}
|
|
|
|
export function usePostComments(threadType) {
|
|
const { enableInContextSidebar, postId } = useContext(DiscussionContext);
|
|
const [isLoading, dispatch] = useDispatchWithState();
|
|
const comments = useSelector(selectThreadComments(postId));
|
|
const reverseOrder = useSelector(selectCommentSortOrder);
|
|
const hasMorePages = useSelector(selectThreadHasMorePages(postId));
|
|
const currentPage = useSelector(selectThreadCurrentPage(postId));
|
|
|
|
const endorsedCommentsIds = useMemo(() => (
|
|
[...filterPosts(comments, 'endorsed')].map(comment => comment.id)
|
|
), [comments]);
|
|
|
|
const unEndorsedCommentsIds = useMemo(() => (
|
|
[...filterPosts(comments, 'unendorsed')].map(comment => comment.id)
|
|
), [comments]);
|
|
|
|
const handleLoadMoreResponses = useCallback(async () => {
|
|
const params = {
|
|
threadType,
|
|
page: currentPage + 1,
|
|
reverseOrder,
|
|
};
|
|
await dispatch(fetchThreadComments(postId, params));
|
|
trackLoadMoreEvent(postId, params);
|
|
}, [currentPage, threadType, postId, reverseOrder]);
|
|
|
|
useEffect(() => {
|
|
const abortController = new AbortController();
|
|
|
|
dispatch(fetchThreadComments(postId, {
|
|
threadType,
|
|
page: 1,
|
|
reverseOrder,
|
|
enableInContextSidebar,
|
|
signal: abortController.signal,
|
|
}));
|
|
|
|
return () => {
|
|
abortController.abort();
|
|
};
|
|
}, [postId, threadType, reverseOrder, enableInContextSidebar]);
|
|
|
|
return {
|
|
endorsedCommentsIds,
|
|
unEndorsedCommentsIds,
|
|
hasMorePages,
|
|
isLoading,
|
|
handleLoadMoreResponses,
|
|
};
|
|
}
|
|
|
|
export function useCommentsCount(postId) {
|
|
const discussions = useSelector(selectThreadComments(postId, EndorsementStatus.DISCUSSION));
|
|
const endorsedQuestions = useSelector(selectThreadComments(postId, EndorsementStatus.ENDORSED));
|
|
const unendorsedQuestions = useSelector(selectThreadComments(postId, EndorsementStatus.UNENDORSED));
|
|
|
|
const commentsLength = useMemo(() => (
|
|
[...discussions, ...endorsedQuestions, ...unendorsedQuestions].length
|
|
), [discussions, endorsedQuestions, unendorsedQuestions]);
|
|
|
|
return commentsLength;
|
|
}
|
|
|
|
export const useDraftContent = () => {
|
|
const comments = useSelector(selectDraftComments);
|
|
const responses = useSelector(selectDraftResponses);
|
|
|
|
const getObjectByParentId = (data, parentId, isComment, id) => Object.values(data)
|
|
.find(draft => (isComment ? draft.parentId === parentId && (id ? draft.id === id : draft.isNewContent === true)
|
|
: draft.threadId === parentId && (id ? draft.id === id : draft.isNewContent === true)));
|
|
|
|
const updateDraftData = (draftData, newDraftObject) => ({
|
|
...draftData,
|
|
[newDraftObject.id]: newDraftObject,
|
|
});
|
|
|
|
const addDraftContent = (content, parentId, id, threadId) => {
|
|
const data = parentId ? comments : responses;
|
|
const draftParentId = parentId || threadId;
|
|
const isComment = !!parentId;
|
|
const existingObj = getObjectByParentId(data, draftParentId, isComment, id);
|
|
const newObject = existingObj
|
|
? { ...existingObj, content }
|
|
: {
|
|
threadId,
|
|
content,
|
|
parentId,
|
|
id: id || uuidv4(),
|
|
isNewContent: !id,
|
|
};
|
|
|
|
const updatedComments = parentId ? updateDraftData(comments, newObject) : comments;
|
|
const updatedResponses = !parentId ? updateDraftData(responses, newObject) : responses;
|
|
|
|
return { updatedComments, updatedResponses };
|
|
};
|
|
|
|
const getDraftContent = (parentId, threadId, id) => {
|
|
if (id) {
|
|
return parentId ? comments?.[id]?.content : responses?.[id]?.content;
|
|
}
|
|
|
|
const data = parentId ? comments : responses;
|
|
const draftParentId = parentId || threadId;
|
|
const isComment = !!parentId;
|
|
|
|
return getObjectByParentId(data, draftParentId, isComment, id)?.content;
|
|
};
|
|
|
|
const removeItem = (draftData, objId) => {
|
|
/* eslint-disable-next-line @typescript-eslint/naming-convention */
|
|
const { [objId]: _, ...newDraftData } = draftData;
|
|
return newDraftData;
|
|
};
|
|
|
|
const updateContent = (items, itemId, parentId, isComment) => {
|
|
const itemObj = itemId ? items[itemId] : getObjectByParentId(items, parentId, isComment, itemId);
|
|
return itemObj ? removeItem(items, itemObj.id) : items;
|
|
};
|
|
|
|
const removeDraftContent = (parentId, id, threadId) => {
|
|
const updatedResponses = !parentId ? updateContent(responses, id, threadId, false) : responses;
|
|
const updatedComments = parentId ? updateContent(comments, id, parentId, true) : comments;
|
|
|
|
return { updatedResponses, updatedComments };
|
|
};
|
|
|
|
return {
|
|
addDraftContent,
|
|
getDraftContent,
|
|
removeDraftContent,
|
|
};
|
|
};
|