fastcomments-react-native-sdk
Version:
React Native FastComments Components. Add live commenting to any React Native application.
308 lines (304 loc) • 14.9 kB
JavaScript
import { subscribeToChanges } from "./subscribe-to-changes";
import { checkBlockedComments } from "./blocking";
import { addCommentToUserPresenceState, handleNewRemoteUser, setupUserPresenceState } from "./user-presense";
import { none } from "@hookstate/core";
import { broadcastIdsSent } from "./broadcast-id";
import { removeCommentOnClient } from "./remove-comment-on-client";
import { repositionComment } from "./comment-positioning";
import { addCommentToTree } from "./comment-trees";
import { incOverallCommentCount } from "./comment-count";
import { handleNewCustomConfig } from "./custom-config";
const SubscriberInstanceById = {};
export function handleLiveEvent(state, dataJSON) {
// console.log('handleLiveEvent', dataJSON);
if ('broadcastId' in dataJSON && broadcastIdsSent.includes(dataJSON.broadcastId)) {
return;
}
if ('bId' in dataJSON && broadcastIdsSent.includes(dataJSON.bId)) {
return;
}
switch (dataJSON.type) {
case 'new-badge':
for (const comment of state.allComments) {
if (comment.userId.get() === dataJSON.badge.userId) {
if (!comment.badges.get()) {
comment.badges.merge([dataJSON.badge]);
}
else if (!comment.badges.get().some(function (badge) {
return badge.id === dataJSON.badge.id;
})) {
comment.badges.merge([dataJSON.badge]);
}
}
}
broadcastIdsSent.push(dataJSON.broadcastId);
break;
case 'removed-badge':
for (const comment of state.allComments) {
if (comment.userId.get() === dataJSON.badge.userId && comment.badges.get()) {
const newBadges = [];
for (const badge of comment.badges.get()) {
if (badge.id !== dataJSON.badge.id) {
newBadges.push(badge);
}
}
comment.badges.merge(newBadges);
}
}
broadcastIdsSent.push(dataJSON.broadcastId);
break;
case 'notification':
state.userNotificationState.count.set((count) => {
if (count) {
count++;
}
else {
count = 1;
}
return count;
});
if (state.userNotificationState.notifications.get()) {
state.userNotificationState.notifications.set((notifications) => {
notifications.unshift(dataJSON.notification);
return notifications;
});
}
break;
case 'presence-update':
if (dataJSON.uj) {
for (const userJoined of dataJSON.uj) {
state.userPresenceState.usersOnlineMap[userJoined].set(true);
}
}
if (dataJSON.ul) {
for (const userLeft of dataJSON.ul) {
state.userPresenceState.usersOnlineMap[userLeft].set(false);
}
}
break;
case 'new-vote':
const newVoteComment = state.commentsById[dataJSON.vote.commentId];
if (newVoteComment.get()) {
newVoteComment.votes.set((votes) => {
if (votes === null || votes === undefined) {
votes = 0;
}
else {
votes += dataJSON.vote.direction;
}
return votes;
});
if (dataJSON.vote.direction > 0) {
newVoteComment.votesUp.set((votesUp) => {
if (votesUp === null || votesUp === undefined) {
votesUp = 0;
}
else {
votesUp++;
}
return votesUp;
});
if (state.currentUser && 'id' in state.currentUser && state.currentUser.id && dataJSON.vote.userId === state.currentUser.id.get()) {
newVoteComment.isVotedUp.set(true);
}
}
else {
newVoteComment.votesDown.set((votesDown) => {
if (votesDown === null || votesDown === undefined) {
votesDown = 0;
}
else {
votesDown++;
}
return votesDown;
});
if (state.currentUser && 'id' in state.currentUser && state.currentUser.id && dataJSON.vote.userId === state.currentUser.id.get()) {
newVoteComment.isVotedDown.set(true);
}
}
}
break;
case 'deleted-vote':
const deletedVoteComment = state.commentsById[dataJSON.vote.commentId];
if (deletedVoteComment.get()) {
deletedVoteComment.votes.set((votes) => {
// votes always set as vote was originally populated for it to be deleted
votes += (dataJSON.vote.direction * -1);
return votes;
});
if (dataJSON.vote.direction > 0) {
deletedVoteComment.votesUp.set((votesUp) => {
if (votesUp) {
votesUp--;
}
return votesUp;
});
if (state.currentUser && 'id' in state.currentUser && state.currentUser.id && deletedVoteComment.isVotedUp && dataJSON.vote.userId === state.currentUser.id.get()) {
deletedVoteComment.isVotedUp.set(none);
}
}
else {
deletedVoteComment.votesDown.set((votesDown) => {
if (votesDown) {
votesDown--;
}
return votesDown;
});
if (state.currentUser && 'id' in state.currentUser && state.currentUser.id && deletedVoteComment.isVotedDown && dataJSON.vote.userId === state.currentUser.id.get()) {
deletedVoteComment.isVotedDown.set(none);
}
}
}
break;
case 'deleted-comment':
removeCommentOnClient(state, state.commentsById[dataJSON.comment._id]);
break;
case 'new-comment':
case 'updated-comment':
const dataJSONComment = dataJSON.comment;
const dataJSONExtraInfo = dataJSON.extraInfo;
const showLiveRightAway = state.config.showLiveRightAway.get();
const commentsById = state.commentsById;
// the hidden check here is for approving, un-approving, and then re-approving a comment
if (dataJSON.type === 'new-comment' && commentsById[dataJSONComment._id].get()) {
if (!commentsById[dataJSONComment._id].approved.get() && dataJSONComment.approved) {
dataJSON.type = 'updated-comment'; // we'll just set the comment as approved
}
else {
return;
}
}
/*
What we want to do is show a count on the first visible parent comment like:
"8 New Comments - Click to show"
This means that if you could have:
Visible Comment (2 new comments, click to show)
- Hidden New Comment
- Hidden New Comment
So we need to walk the tree to find the first visible parent comment, and also
calculate how many hidden comments are under that parent.
If parentId is null - like when replying to a page instead of a parent comment - we can just
treat the page as the root and don't have to walk any tree.
ASSUMPTION: We should always receive the comments in order so that we can just walk
the tree backwards.
Then we just need to render that text/button. Clicking it then goes and changes the hidden
flag on said comments and re-renders that part of the tree.
*/
const isNew = !(commentsById[dataJSONComment._id].get({ stealth: true }));
if (isNew) {
console.log('adding new comment to commentsById');
commentsById.merge({
[dataJSONComment._id]: dataJSONComment
});
// commentsById[dataJSONComment._id].set(dataJSONComment);
}
else {
commentsById[dataJSONComment._id].merge(dataJSONComment);
}
console.log('????? isNew 2', isNew);
if (!isNew) {
if (dataJSONExtraInfo) {
if (dataJSONExtraInfo.commentPositions) {
repositionComment(dataJSONComment._id, dataJSONExtraInfo.commentPositions, state);
}
}
}
else {
const presenceChanges = {};
addCommentToUserPresenceState(state.userPresenceState, dataJSONComment, presenceChanges);
dataJSONComment.isLive = true; // so we can animate the background color
// commentsVisible check is here because if comments are hidden, we want this comment to show as soon as comments are un-hidden.
const newCommentHidden = state.commentsVisible.get({ stealth: true }) && !showLiveRightAway;
dataJSONComment.hidden = newCommentHidden; // don't irritate the user with content popping in and out.
let updateRoot = false, currentParent = null;
if (!dataJSONComment.parentId) {
updateRoot = true;
}
else {
currentParent = commentsById[dataJSONComment.parentId];
let iteratedCount = 0;
while (((!currentParent || !currentParent.get({ stealth: true }))
|| currentParent.hidden.get({ stealth: true }) === true)
&& iteratedCount < 5000) {
const nextParentId = currentParent ? currentParent.parentId.get({ stealth: true }) : null;
if (currentParent && currentParent.get({ stealth: true }) && !nextParentId) {
updateRoot = true;
break; // reached top of page
}
currentParent = nextParentId ? commentsById[nextParentId] : null;
iteratedCount++;
}
if (iteratedCount >= 4998) {
console.warn('FC - iteration hit limit', iteratedCount);
}
}
addCommentToTree(state.allComments, state.commentsTree, commentsById, dataJSONComment, !!state.config.newCommentsToBottom.get());
incOverallCommentCount(state.config.countAll.get(), state, dataJSONComment.parentId);
if (updateRoot) {
if (newCommentHidden) {
state.newRootCommentCount.set((newRootCommentCount) => {
newRootCommentCount++;
return newRootCommentCount;
});
}
}
else if (currentParent && currentParent?.get()) {
if (newCommentHidden) {
currentParent.hiddenChildrenCount.set((hiddenChildrenCount) => {
if (!hiddenChildrenCount) {
hiddenChildrenCount = 1;
}
else {
hiddenChildrenCount++;
}
return hiddenChildrenCount;
});
}
}
const userIdsChanged = Object.keys(presenceChanges);
if (userIdsChanged.length > 0) {
// noinspection JSIgnoredPromiseFromCall - fire and forget
handleNewRemoteUser(state.config.get(), state.urlIdWS.get(), state.userPresenceState, userIdsChanged);
}
}
break;
case 'new-config':
handleNewCustomConfig(state, dataJSON.config, true);
break;
}
}
export function persistSubscriberState(state, newUrlIdWS, newTenantIdWS, newUserIdWS) {
const didChange = state.urlIdWS.get() !== newUrlIdWS || state.tenantIdWS.get() !== newTenantIdWS || state.userIdWS.get() !== newUserIdWS;
if (!didChange) {
return;
}
state.urlIdWS.set(newUrlIdWS);
state.tenantIdWS.set(newTenantIdWS);
state.userIdWS.set(newUserIdWS);
const instanceId = state.instanceId.get();
const prevInstance = SubscriberInstanceById[instanceId];
if (prevInstance) {
prevInstance.close();
}
SubscriberInstanceById[instanceId] = subscribeToChanges(state.config.get(), state.wsHost.get(), state.tenantIdWS.get(), state.config.urlId.get(), state.urlIdWS.get(), state.userIdWS.get(), async (commentIds) => {
return await checkBlockedComments(state.get(), commentIds);
}, (dataJSON) => {
handleLiveEvent(state, dataJSON);
}, function connectionStatusChange(isConnected, lastEventTime) {
// if we have a current user, update the status icon on their comments!
if (state.currentUser && 'id' in state.currentUser) {
state.userPresenceState.usersOnlineMap[state.currentUser.id.get()].set(isConnected);
}
// if we are reconnecting, re-fetch all user statuses and render them
if (isConnected) {
const isReconnect = !!lastEventTime;
if (isReconnect) {
// reset user presence state because we know nothing since we disconnected
state.userPresenceState.userIdsToCommentIds.set({});
state.userPresenceState.usersOnlineMap.set({});
}
// noinspection JSIgnoredPromiseFromCall
setupUserPresenceState(state, newUrlIdWS); // TODO can cause exception if handled event after component unmounted, how to detect?
}
});
}