UNPKG

fastcomments-react-native-sdk

Version:

React Native FastComments Components. Add live commenting to any React Native application.

229 lines (228 loc) 8.38 kB
import { none } from "@hookstate/core"; export function getCommentsTreeAndCommentsById(collapseRepliesByDefault, rawComments) { const commentsById = {}; const commentsLength = rawComments.length; const resultComments = []; let comment, i; for (i = 0; i < commentsLength; i++) { const comment = rawComments[i]; commentsById[comment._id] = comment; } for (i = 0; i < commentsLength; i++) { comment = rawComments[i]; comment.nestedChildrenCount = 0; //!commentsById[comment.parentId] check for user profile feed if (collapseRepliesByDefault && (!(comment.parentId) || !commentsById[comment.parentId]) && (comment.repliesHidden === undefined)) { comment.repliesHidden = true; } const parentId = comment.parentId; if (parentId && commentsById[parentId]) { if (!commentsById[parentId].children) { commentsById[parentId].children = [comment]; } else { // checking if comment is already in children causes lag commentsById[parentId].children.push(comment); } } else { resultComments.push(comment); } } for (i = 0; i < commentsLength; i++) { comment = rawComments[i]; let parentId = comment.parentId; while (parentId) { comment = commentsById[parentId]; if (comment) { comment.nestedChildrenCount++; parentId = comment.parentId; } else { break; } } } return { comments: resultComments, commentsById: commentsById }; } export function ensureRepliesOpenToComment(commentsById, commentId) { let parentId = commentId; let iterations = 0; while (parentId && iterations < 100) { iterations++; const parent = commentsById.nested(parentId); if (parent) { parent.repliesHidden.set(true); parentId = parent.parentId.get(); } else { break; } } } export function addCommentToTree(allComments, commentsTree, commentsById, comment, newCommentsToBottom) { if (comment.parentId && !(commentsById[comment.parentId]?.get())) { // don't use memory for this comment since its parent is not visible. they should be received in-order to the client. return; } allComments.merge([comment]); if (comment.parentId) { commentsById[comment.parentId].children.set((children) => { if (!children) { children = [comment]; } else if (newCommentsToBottom) { children.push(comment); } else { children.unshift(comment); } return [...children]; }); // We do this to tell the tree to re-render. // commentsTree and commentsById use the same references to comment objects. commentsById has "ownership" of the comments. However, adding a child does not trigger the UI, which is based on the tree, to re-render. // we could always have both contain a reference to a State<Comment> that is unwrapped every time we use it, but this has extra overhead. // It may be better to store the children in state.commentState[id], but this adds a hashmap lookup on render (which is still better than N time here!). // *Another* solution would be for commentsTree to just be a tree of ids (like an index) and the actual documents are owned by commentsById. This would have constant time updates/iteration, but slower overall. commentsTree.set((tree) => { return [...tree]; }); } else { const stealth = { stealth: true }; // avoid extra object allocation // ensure pinned comments stay at the top if (commentsTree.length > 0 && commentsTree[0].isPinned.get(stealth)) { let found = false; for (let i = 0; i < commentsTree.length; i++) { if (!commentsTree[i]) { continue; // tombstone } if (!commentsTree[i].isPinned.get(stealth)) { commentsTree.set((commentsTree) => { commentsTree.splice(i, 0, comment); return [...commentsTree]; }); found = true; break; } } // means they're all pinned if (!found) { commentsTree.merge([comment]); } } else { if (newCommentsToBottom) { commentsTree.merge([comment]); } else { commentsTree.set((commentsTree) => { // commentsTree.unshift(comment); return [ ...[comment], ...commentsTree ]; }); } } } updateNestedChildrenCountInTree(commentsById, comment.parentId, 1); } export function removeCommentFromTree(allComments, commentsTree, commentsById, comment) { const commentBeforeRemoval = JSON.parse(JSON.stringify(comment)); const allCommentsIndex = allComments.get().findIndex((otherComment) => otherComment._id === commentBeforeRemoval._id); if (allCommentsIndex > -1) { allComments[allCommentsIndex].set(none); } if (commentBeforeRemoval.parentId && commentsById[commentBeforeRemoval.parentId].get()) { const parentChildrenState = commentsById[commentBeforeRemoval.parentId].children; const parentChildren = parentChildrenState?.get(); if (parentChildren) { const index = parentChildren.findIndex((otherComment) => otherComment._id === commentBeforeRemoval._id); if (index > -1) { // @ts-ignore parentChildrenState[index].set(none); } } } else { const index = commentsTree.get().findIndex((otherComment) => otherComment?._id === commentBeforeRemoval._id); if (index > -1) { commentsTree[index].set(none); } } updateNestedChildrenCountInTree(commentsById, commentBeforeRemoval.parentId, -1); commentsById[commentBeforeRemoval._id].set(none); } /** * * @param {Object.<string, Object>} commentsById * @param {string} parentId * @param {number} inc */ export function updateNestedChildrenCountInTree(commentsById, parentId, inc) { while (parentId) { const comment = commentsById[parentId]; if (comment) { comment.nestedChildrenCount.set((nestedChildrenCount) => { if (nestedChildrenCount === undefined) { nestedChildrenCount = 1; } else { nestedChildrenCount += inc; } return nestedChildrenCount; }); parentId = comment.parentId?.get(); } else { break; } } } export function iterateCommentsTree(nodes, cb) { for (const comment of nodes) { const result = cb(comment); if (result === false) { break; } if (comment.children) { iterateCommentsTree(comment.children, cb); } } } export function iterateCommentsTreeMut(nodes, cb) { for (const comment of nodes) { const result = cb(comment); if (result === false) { break; } if (comment.children) { iterateCommentsTreeMut(comment.children, cb); } } } export function iterateCommentsTreeWithDepth(nodes, depth, cb) { for (const comment of nodes) { const result = cb(comment, depth); if (result === false) { break; } if (comment.children) { iterateCommentsTreeWithDepth(comment.children, depth + 1, cb); } } } export function iterateCommentsTreeState(nodes, cb) { for (const comment of nodes) { const result = cb(comment); if (result === false) { break; } if (comment.children) { // @ts-ignore iterateCommentsTreeState(comment.children, cb); } } }