@selfcommunity/react-ui
Version:
React UI Components to integrate a Community created with SelfCommunity Platform.
199 lines (189 loc) • 10.2 kB
JavaScript
import { __rest } from "tslib";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import React, { useContext, useState } from 'react';
import Widget from '../Widget';
import { FormattedMessage } from 'react-intl';
import { Avatar, Box, CardContent, Typography, styled } from '@mui/material';
import Bullet from '../../shared/Bullet';
import classNames from 'classnames';
import { SCOPE_SC_UI } from '../../constants/Errors';
import CommentObjectSkeleton from './Skeleton';
import CommentObjectReply from '../CommentObjectReply';
import DateTimeAgo from '../../shared/DateTimeAgo';
import { getCommentContributionHtml } from '../../utils/contribution';
import { useSnackbar } from 'notistack';
import { useThemeProps } from '@mui/system';
import BaseItem from '../../shared/BaseItem';
import { Endpoints, http } from '@selfcommunity/api-services';
import { CacheStrategies, Logger, LRUCache } from '@selfcommunity/utils';
import { Link, SCCache, SCRoutes, SCUserContext, UserUtils, useSCFetchLessonCommentObject, useSCRouting } from '@selfcommunity/react-core';
import UserDeletedSnackBar from '../../shared/UserDeletedSnackBar';
import UserAvatar from '../../shared/UserAvatar';
import { PREFIX } from './constants';
import LessonCommentActionsMenu from '../../shared/LessonCommentActionsMenu';
import LessonFilePreview from '../../shared/LessonFilePreview';
const classes = {
root: `${PREFIX}-root`,
comment: `${PREFIX}-comment`,
avatar: `${PREFIX}-avatar`,
content: `${PREFIX}-content`,
author: `${PREFIX}-author`,
textContent: `${PREFIX}-text-content`,
mediaContent: `${PREFIX}-media-content`,
commentActionsMenu: `${PREFIX}-comment-actions-menu`
};
const Root = styled(Box, {
name: PREFIX,
slot: 'Root'
})(() => ({}));
/**
* > API documentation for the Community-JS Comment Object component. Learn about the available props and the CSS API.
*
*
* This component renders a comment item.
* Take a look at our <strong>demo</strong> component [here](/docs/sdk/community-js/react-ui/Components/CommentObject)
#### Import
```jsx
import {CommentObject} from '@selfcommunity/react-ui';
```
#### Component Name
The name `SCCommentObject` can be used when providing style overrides in the theme.
#### CSS
|Rule Name|Global class|Description|
|---|---|---|
|root|.SCCommentObject-root|Styles applied to the root element.|
|comment|.SCCommentObject-comment|Styles applied to comment element.|
|avatar|.SCCommentObject-avatar|Styles applied to the avatar element.|
|author|.SCCommentObject-author|Styles applied to the author section.|
|content|.SCCommentObject-content|Styles applied to content section.|
|textContent|.SCCommentObject-text-content|Styles applied to text content section.|
|mediaContent|.SCCommentObject-media-content|Styles applied to media content section.|
|commentActionsMenu|.SCCommentObject-comment-actions-menu|Styles applied to comment action menu element.|
* @param inProps
*/
export default function LessonCommentObject(inProps) {
// PROPS
const props = useThemeProps({
props: inProps,
name: PREFIX
});
const { id = `lesson_comment_object_${props.commentObjectId ? props.commentObjectId : props.commentObject ? props.commentObject.id : ''}`, className, commentObjectId, commentObject, isEditing, lessonObjectId, lessonObject, onDelete, elevation = 0, CommentObjectSkeletonProps = { elevation }, CommentObjectReplyProps = { elevation }, cacheStrategy = CacheStrategies.NETWORK_ONLY } = props, rest = __rest(props, ["id", "className", "commentObjectId", "commentObject", "isEditing", "lessonObjectId", "lessonObject", "onDelete", "elevation", "CommentObjectSkeletonProps", "CommentObjectReplyProps", "cacheStrategy"]);
// CONTEXT
const scUserContext = useContext(SCUserContext);
const scRoutingContext = useSCRouting();
const { enqueueSnackbar } = useSnackbar();
// STATE
const { obj, setObj } = useSCFetchLessonCommentObject({ id: commentObjectId, commentObject: commentObject, lesson: lessonObject, cacheStrategy });
const [isSavingComment, setIsSavingComment] = useState(false);
const [editComment, setEditComment] = useState(null);
const [openAlert, setOpenAlert] = useState(false);
// HANDLERS
/**
* Update state object
* @param newObj
*/
function updateObject(newObj) {
LRUCache.set(SCCache.getLessonCommentCacheKey(obj.id), newObj);
setObj(newObj);
LRUCache.deleteKeysWithPrefix(SCCache.getLessonCommentsCachePrefixKeys(lessonObject.id));
}
/**
* Handle comment delete
*/
function handleDelete(comment) {
updateObject(comment);
onDelete && onDelete(comment);
}
/**
* Handle edit comment
*/
function handleEdit(comment) {
setEditComment(comment);
isEditing && isEditing(true);
}
function handleCancel() {
setEditComment(null);
isEditing && isEditing(false);
}
/**
* Perform save/update comment
*/
const performUpdate = (comment, medias) => {
const mediaIds = medias ? medias.map((media) => media.id) : [];
return http
.request({
url: Endpoints.UpdateCourseComment.url({
id: lessonObject.course_id,
section_id: lessonObject.section_id,
lesson_id: lessonObject.id,
comment_id: commentObject.id
}),
method: Endpoints.UpdateCourseComment.method,
data: { text: comment, medias: mediaIds }
})
.then((res) => {
if (res.status >= 300) {
return Promise.reject(res);
}
return Promise.resolve(res.data);
});
};
/**
* Handle save comment
*/
function handleUpdate(comment, medias) {
if (UserUtils.isBlocked(scUserContext.user)) {
enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.common.userBlocked", defaultMessage: "ui.common.userBlocked" }), {
variant: 'warning',
autoHideDuration: 3000
});
}
else {
setIsSavingComment(true);
performUpdate(comment, medias)
.then((data) => {
const newObj = Object.assign({}, obj, {
text: data.text,
html: data.html,
created_at: data.created_at,
medias: medias
});
updateObject(newObj);
setEditComment(null);
setIsSavingComment(false);
isEditing && isEditing(false);
})
.catch((error) => {
Logger.error(SCOPE_SC_UI, error);
enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.common.error.action", defaultMessage: "ui.common.error.action" }), {
variant: 'error',
autoHideDuration: 3000
});
});
}
}
/**
* Render comment & latest activities
* @param comment
*/
function renderComment(comment) {
const summaryHtml = getCommentContributionHtml(comment.html, scRoutingContext.url);
return (_jsx(React.Fragment, { children: editComment && editComment.id === comment.id ? (_jsx(Box, Object.assign({ className: classes.comment }, { children: _jsx(CommentObjectReply, Object.assign({ text: comment.html, medias: comment.medias, autoFocus: true, id: `edit-${comment.id}`, onSave: handleUpdate, onCancel: handleCancel, editable: !isSavingComment, EditorProps: { uploadFile: true, uploadImage: false, isLessonCommentEditor: true } }, CommentObjectReplyProps)) }))) : (_jsx(BaseItem, { elevation: 0, className: classes.comment, image: _jsx(Link, Object.assign({}, (!comment.created_by.deleted && { to: scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, comment.created_by) }), { onClick: comment.created_by.deleted ? () => setOpenAlert(true) : null }, { children: _jsx(UserAvatar, Object.assign({ hide: !obj.created_by.community_badge }, { children: _jsx(Avatar, { alt: obj.created_by.username, variant: "circular", src: comment.created_by.avatar, className: classes.avatar }) })) })), disableTypography: true, primary: _jsx(_Fragment, { children: _jsxs(Widget, Object.assign({ className: classes.content, elevation: elevation }, rest, { children: [_jsxs(CardContent, { children: [_jsx(Link, Object.assign({ className: classes.author }, (!comment.created_by.deleted && { to: scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, comment.created_by) }), { onClick: comment.created_by.deleted ? () => setOpenAlert(true) : null }, { children: _jsx(Typography, Object.assign({ component: "span" }, { children: comment.created_by.username })) })), _jsxs(_Fragment, { children: [_jsx(Bullet, {}), _jsx(DateTimeAgo, { date: comment.created_at, showStartIcon: false })] }), _jsx(Typography, { className: classes.textContent, variant: "body2", gutterBottom: true, dangerouslySetInnerHTML: { __html: summaryHtml } }), obj.medias && obj.medias.length > 0 && (_jsx(_Fragment, { children: obj.medias.map((media) => {
return _jsx(LessonFilePreview, { className: classes.mediaContent, media: media }, media.id);
}) }))] }), scUserContext.user && scUserContext.user.id === comment.created_by.id && (_jsx(Box, Object.assign({ className: classes.commentActionsMenu }, { children: _jsx(LessonCommentActionsMenu, { lesson: lessonObject, commentObject: comment, onDelete: handleDelete, onEdit: handleEdit }) })))] })) }) })) }, comment.id));
}
/**
* Render comments
*/
let comment;
if (obj) {
comment = renderComment(obj);
}
else {
comment = _jsx(CommentObjectSkeleton, Object.assign({}, CommentObjectSkeletonProps));
}
/**
* Render object
*/
return (_jsxs(_Fragment, { children: [_jsx(Root, Object.assign({ id: id, className: classNames(classes.root, className) }, { children: comment })), openAlert && _jsx(UserDeletedSnackBar, { open: openAlert, handleClose: () => setOpenAlert(false) })] }));
}