diff --git a/src/components/node/NodeComments/index.tsx b/src/components/node/NodeComments/index.tsx index 5d37cd9d..a0da7ee0 100644 --- a/src/components/node/NodeComments/index.tsx +++ b/src/components/node/NodeComments/index.tsx @@ -9,34 +9,59 @@ import { IUser } from '~/redux/auth/types'; import { canEditComment } from '~/utils/node'; import { nodeLockComment, nodeEditComment } from '~/redux/node/actions'; import { INodeState } from '~/redux/node/reducer'; +import { COMMENTS_DISPLAY } from '~/redux/node/constants'; +import { plural } from '~/utils/dom'; interface IProps { comments?: IComment[]; comment_data: INodeState['comment_data']; + comment_count: INodeState['comment_count']; user: IUser; onDelete: typeof nodeLockComment; onEdit: typeof nodeEditComment; + order?: 'ASC' | 'DESC'; } -const NodeComments: FC = memo(({ comments, comment_data, user, onDelete, onEdit }) => { - const groupped: ICommentGroup[] = useMemo(() => comments.reduce(groupCommentsByUser, []), [ - comments, - ]); +const NodeComments: FC = memo( + ({ comments, comment_data, user, onDelete, onEdit, comment_count = 0, order = 'DESC' }) => { + const comments_left = useMemo(() => Math.max(0, comment_count - comments.length), [ + comments, + comment_count, + ]); - return ( -
- {groupped.map(group => ( - - ))} -
- ); -}); + const groupped: ICommentGroup[] = useMemo( + () => (order === 'DESC' ? [...comments].reverse() : comments).reduce(groupCommentsByUser, []), + [comments, order] + ); + + return ( +
+ {comment_count > 0 && ( +
+ Показать ещё{' '} + {plural( + Math.min(comments_left, COMMENTS_DISPLAY), + 'комментарий', + 'комментария', + 'комментариев' + )} + {comments_left > COMMENTS_DISPLAY ? ` из ${comments_left} оставшихся` : ''} +
+ )} + + {groupped.map(group => ( + + ))} +
+ ); + } +); export { NodeComments }; diff --git a/src/components/node/NodeComments/styles.scss b/src/components/node/NodeComments/styles.scss index 401c1465..4da06029 100644 --- a/src/components/node/NodeComments/styles.scss +++ b/src/components/node/NodeComments/styles.scss @@ -7,3 +7,17 @@ } } } + +.more { + padding: $gap; + box-sizing: border-box; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + background: darken($comment_bg, 8%); + border-radius: $radius; + text-transform: uppercase; + color: darken(white, 50%); + font: $font_14_regular; +} diff --git a/src/containers/node/BorisLayout/index.tsx b/src/containers/node/BorisLayout/index.tsx index fa4ca8da..62d682cc 100644 --- a/src/containers/node/BorisLayout/index.tsx +++ b/src/containers/node/BorisLayout/index.tsx @@ -31,7 +31,7 @@ type IProps = ReturnType & const id = 696; const BorisLayoutUnconnected: FC = ({ - node: { is_loading, is_loading_comments, comments = [], comment_data }, + node: { is_loading, is_loading_comments, comments = [], comment_data, comment_count }, user, user: { is_user }, nodeLoadNode, @@ -88,6 +88,7 @@ const BorisLayoutUnconnected: FC = ({ = memo( match: { params: { id }, }, - node: { is_loading, is_loading_comments, comments = [], current: node, related, comment_data }, + node: { + is_loading, + is_loading_comments, + comments = [], + current: node, + related, + comment_data, + comment_count, + }, user, user: { is_user }, nodeGotoNode, @@ -136,9 +144,11 @@ const NodeLayoutUnconnected: FC = memo( )} diff --git a/src/redux/node/api.ts b/src/redux/node/api.ts index 4c30ef5c..1ff0c0ec 100644 --- a/src/redux/node/api.ts +++ b/src/redux/node/api.ts @@ -3,6 +3,7 @@ import { INode, IResultWithStatus, IComment } from '../types'; import { API } from '~/constants/api'; import { nodeUpdateTags, nodeLike, nodeStar, nodeLock, nodeLockComment } from './actions'; import { INodeState } from './reducer'; +import { COMMENTS_DISPLAY } from './constants'; export const postNode = ({ access, @@ -95,14 +96,16 @@ export const postNodeComment = ({ export const getNodeComments = ({ id, access, - order = 'ASC', + take = COMMENTS_DISPLAY, + skip = 0, }: { id: number; access: string; - order: 'ASC' | 'DESC'; + take?: number; + skip?: number; }): Promise> => api - .get(API.NODE.COMMENT(id), configWithToken(access, { params: { order } })) + .get(API.NODE.COMMENT(id), configWithToken(access, { params: { take, skip } })) .then(resultMiddleware) .catch(errorMiddleware); diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts index 0f5b4bcd..a3f9b253 100644 --- a/src/redux/node/constants.ts +++ b/src/redux/node/constants.ts @@ -97,94 +97,6 @@ export const EMPTY_COMMENT: IComment = { temp_ids: [], is_private: false, user: null, - - /* - files: [ - { - name: 'screenshot_2019-09-29_21-13-38_502253296-1572589001092.png', - path: 'uploads/2019/10/image/', - full_path: - 'public/uploads/2019/10/image/screenshot_2019-09-29_21-13-38_502253296-1572589001092.png', - url: - 'REMOTE_CURRENT://uploads/2019/10/image/screenshot_2019-09-29_21-13-38_502253296-1572589001092.png', - size: 994331, - type: 'image', - mime: 'image/png', - metadata: { - width: 1919, - height: 1079, - }, - id: 8709, - }, - { - name: 'screenshot_2019-09-29_19-05-41_148603009-1572589001080.png', - path: 'uploads/2019/10/image/', - full_path: - 'public/uploads/2019/10/image/screenshot_2019-09-29_19-05-41_148603009-1572589001080.png', - url: - 'REMOTE_CURRENT://uploads/2019/10/image/screenshot_2019-09-29_19-05-41_148603009-1572589001080.png', - size: 2145, - type: 'image', - mime: 'image/png', - metadata: { - width: 445, - height: 446, - }, - id: 8708, - }, - { - name: 'screenshot_2019-09-29_21-13-26_924738012-1572589001110.png', - path: 'uploads/2019/10/image/', - full_path: - 'public/uploads/2019/10/image/screenshot_2019-09-29_21-13-26_924738012-1572589001110.png', - url: - 'REMOTE_CURRENT://uploads/2019/10/image/screenshot_2019-09-29_21-13-26_924738012-1572589001110.png', - size: 881224, - type: 'image', - mime: 'image/png', - metadata: { - width: 1919, - height: 1079, - }, - id: 8710, - }, - { - name: - 'Advent_Chamber_Orchestra_-_05_-_Dvorak_-_Serenade_for_Strings_Op22_in_E_Major_larghetto-1572597841834.mp3', - path: 'uploads/2019/10/audio/', - full_path: - 'public/uploads/2019/10/audio/Advent_Chamber_Orchestra_-_05_-_Dvorak_-_Serenade_for_Strings_Op22_in_E_Major_larghetto-1572597841834.mp3', - url: - 'REMOTE_CURRENT://uploads/2019/10/audio/Advent_Chamber_Orchestra_-_05_-_Dvorak_-_Serenade_for_Strings_Op22_in_E_Major_larghetto-1572597841834.mp3', - size: 11155009, - type: 'audio', - mime: 'audio/mp3', - metadata: { - duration: 343.3795918367347, - id3title: 'Dvorak - Serenade for Strings Op22 in E Major larghetto', - id3artist: 'Advent Chamber Orchestra', - }, - id: 8714, - }, - { - name: '182a0d234aef882a58f240c5c0812bb2cad9506a875ca4c7c07d8f9f077ebb00-1572597841829.mp3', - path: 'uploads/2019/10/audio/', - full_path: - 'public/uploads/2019/10/audio/182a0d234aef882a58f240c5c0812bb2cad9506a875ca4c7c07d8f9f077ebb00-1572597841829.mp3', - url: - 'REMOTE_CURRENT://uploads/2019/10/audio/182a0d234aef882a58f240c5c0812bb2cad9506a875ca4c7c07d8f9f077ebb00-1572597841829.mp3', - size: 6038673, - type: 'audio', - mime: 'audio/mp3', - metadata: { - duration: 251.58530612244897, - id3title: null, - id3artist: null, - }, - id: 8713, - }, - ], - */ }; export const NODE_EDITORS = { @@ -219,3 +131,5 @@ export const NODE_SETTINGS = { MAX_FILES: 16, MAX_IMAGE_ASPECT: 1.2, }; + +export const COMMENTS_DISPLAY = 25; diff --git a/src/redux/node/reducer.ts b/src/redux/node/reducer.ts index 97e50eb2..eb0b3ecb 100644 --- a/src/redux/node/reducer.ts +++ b/src/redux/node/reducer.ts @@ -12,6 +12,7 @@ export type INodeState = Readonly<{ similar: Partial; }; comment_data: Record; + comment_count: number; current_cover_image: IFile; error: string; @@ -35,6 +36,7 @@ const INITIAL_STATE: INodeState = { ...EMPTY_COMMENT, }, }, + comment_count: 0, comments: [], related: null, current_cover_image: null, diff --git a/src/redux/node/sagas.ts b/src/redux/node/sagas.ts index 860eddf2..9ba52d47 100644 --- a/src/redux/node/sagas.ts +++ b/src/redux/node/sagas.ts @@ -2,7 +2,13 @@ import { takeLatest, call, put, select, delay, all } from 'redux-saga/effects'; import { push } from 'connected-react-router'; import omit from 'ramda/es/omit'; -import { NODE_ACTIONS, EMPTY_NODE, EMPTY_COMMENT, NODE_EDITOR_DATA } from './constants'; +import { + NODE_ACTIONS, + EMPTY_NODE, + EMPTY_COMMENT, + NODE_EDITOR_DATA, + COMMENTS_DISPLAY, +} from './constants'; import { nodeSave, nodeSetSaveErrors, @@ -132,19 +138,20 @@ function* onNodeLoad({ id, order = 'ASC' }: ReturnType) { const { comments: { - data: { comments }, + data: { comments, comment_count }, }, related: { data: { related }, }, } = yield all({ - comments: call(reqWrapper, getNodeComments, { id, order }), + comments: call(reqWrapper, getNodeComments, { id, take: COMMENTS_DISPLAY, skip: 0 }), related: call(reqWrapper, getNodeRelated, { id }), }); yield put( nodeSet({ comments, + comment_count, related, is_loading_comments: false, comment_data: { 0: { ...EMPTY_COMMENT } }, diff --git a/src/utils/dom.ts b/src/utils/dom.ts index a9a2fe80..3d658418 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -152,3 +152,13 @@ export const getYoutubeThumb = (url: string) => { return match && match[1] ? `https://i.ytimg.com/vi/${match[1]}/hqdefault.jpg` : null; }; + +export function plural(n: number, one: string, two: string, five: string) { + if (n % 10 === 1 && n % 100 !== 11) { + return `${n} ${one}`; + } else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) { + return `${n} ${two}`; + } else { + return `${n} ${five}`; + } +}