fastcomments-react-native-sdk
Version:
React Native FastComments Components. Add live commenting to any React Native application.
229 lines (228 loc) • 8.38 kB
JavaScript
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);
}
}
}