* Added scaffolding for unrevealed BoR post * Displayed reveal UI * Displayed expiry timer * WIP * Displayed own post indicator and blur text * restored button * Ungrouped BoR posts * WIP * WIP * DIsplayed error message on revealing * Added last run check on mobile app cleanup job * Cleanup * lint fix * i18n-fix * Added tests * test: add test suite for revealBoRPost function * test: add unrevealed burn on read post test file * feat: add tests for UnrevealedBurnOnReadPost component * test: update UnrevealedBurnOnReadPost test with PostModel type * test: replace toBeTruthy with toBeVisible for component visibility assertions * test: add initial test file for expiry timer component * test: add comprehensive tests for ExpiryCountdown component * refactor: clean up test formatting and remove redundant test case * fix: adjust test timing for ExpiryCountdown onExpiry callback * test: fix timer test by advancing timers in smaller increments * Added tests * Updated tests * fixed accidental change * restored package.resolved * WIP review fixes * Review fixes * Review fixes * Fixed tests * restored package.resolved * WIP * test: add test for skipping BoR post cleanup within 15 minutes * test: add comprehensive test cases for expiredBoRPostCleanup * WIP * WIP * test: add bor.test.ts placeholder file * test: add comprehensive tests for BoR utility functions * test: add comprehensive tests for formatTime function * WIP * test: add comprehensive tests for getLastBoRPostCleanupRun function * Added tests * removed a commented code * post list optimization * test: add test case for updating unrevealed burn-on-read post * fix: handle error logging in expiredBoRPostCleanup test * test: add test case for updateLastBoRCleanupRun error handling * review fixes * lint fixes * Added WS event handling (#9320) * Added WS event handling * test: add burn on read websocket action test * test: add tests for handleBoRPostRevealedEvent in burn_on_read * Added tests * test: add comprehensive error handling test cases for burn on read * Added tests and error handling * BoR post - restricted actions (#9315) * Restricted post actions for BoR post type * Prevent opening thread fr nBoR post * WIP * fix: remove redundant observable wrapping in post options * fixed a change * removed broken * restored package.resolved * Added tests * Awaiting for last run to be set * Added tests for validating expiry timer behaviour (#9338) * Added tests for validating expiry timer behaviour * No need of use event setup * Bor ux fixes (#9336) * WIP * UI and delete fixes * lint fixes * fixed tests * Improved mocking * Improved test
164 lines
6.9 KiB
TypeScript
164 lines
6.9 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
|
import React from 'react';
|
|
import {of as of$, combineLatest} from 'rxjs';
|
|
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
|
|
|
import {Permissions, Preferences, Screens} from '@constants';
|
|
import {queryFilesForPost} from '@queries/servers/file';
|
|
import {observePost, observePostAuthor, queryPostsBetween, observeIsPostPriorityEnabled} from '@queries/servers/post';
|
|
import {queryReactionsForPost} from '@queries/servers/reaction';
|
|
import {observeCanManageChannelMembers, observePermissionForPost} from '@queries/servers/role';
|
|
import {observeThreadById} from '@queries/servers/thread';
|
|
import {observeCurrentUser} from '@queries/servers/user';
|
|
import {isBoRPost} from '@utils/bor';
|
|
import {areConsecutivePosts, isPostEphemeral} from '@utils/post';
|
|
|
|
import Post from './post';
|
|
|
|
import type {Database} from '@nozbe/watermelondb';
|
|
import type {WithDatabaseArgs} from '@typings/database/database';
|
|
import type PostModel from '@typings/database/models/servers/post';
|
|
import type PostsInThreadModel from '@typings/database/models/servers/posts_in_thread';
|
|
import type UserModel from '@typings/database/models/servers/user';
|
|
|
|
type PropsInput = WithDatabaseArgs & {
|
|
currentUser?: UserModel;
|
|
isCRTEnabled?: boolean;
|
|
nextPost: PostModel | undefined;
|
|
post: PostModel;
|
|
previousPost: PostModel | undefined;
|
|
location: string;
|
|
}
|
|
|
|
function observeShouldHighlightReplyBar(database: Database, currentUser: UserModel, post: PostModel, postsInThread: PostsInThreadModel) {
|
|
const myPostsCount = queryPostsBetween(database, postsInThread.earliest, postsInThread.latest, null, currentUser.id, '', post.rootId || post.id).observeCount();
|
|
const root = observePost(database, post.rootId);
|
|
|
|
return combineLatest([myPostsCount, root]).pipe(
|
|
switchMap(([mpc, r]) => {
|
|
const threadRepliedToByCurrentUser = mpc > 0;
|
|
let threadCreatedByCurrentUser = false;
|
|
if (r?.userId === currentUser.id) {
|
|
threadCreatedByCurrentUser = true;
|
|
}
|
|
let commentsNotifyLevel = Preferences.COMMENTS_NEVER;
|
|
if (currentUser.notifyProps?.comments) {
|
|
commentsNotifyLevel = currentUser.notifyProps.comments;
|
|
}
|
|
|
|
const notCurrentUser = post.userId !== currentUser.id || Boolean(post.props?.from_webhook);
|
|
if (notCurrentUser) {
|
|
if (commentsNotifyLevel === Preferences.COMMENTS_ANY && (threadCreatedByCurrentUser || threadRepliedToByCurrentUser)) {
|
|
return of$(true);
|
|
} else if (commentsNotifyLevel === Preferences.COMMENTS_ROOT && threadCreatedByCurrentUser) {
|
|
return of$(true);
|
|
}
|
|
}
|
|
|
|
return of$(false);
|
|
}),
|
|
);
|
|
}
|
|
|
|
function observeHasReplies(database: Database, post: PostModel) {
|
|
if (!post.rootId) {
|
|
return post.postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0)));
|
|
}
|
|
|
|
return observePost(database, post.rootId).pipe(switchMap((r) => {
|
|
if (r) {
|
|
return r.postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0)));
|
|
}
|
|
return of$(false);
|
|
}));
|
|
}
|
|
|
|
function isFirstReply(post: PostModel, previousPost?: PostModel) {
|
|
if (post.rootId) {
|
|
if (previousPost) {
|
|
return post.rootId !== previousPost.id && post.rootId !== previousPost.rootId;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const withSystem = withObservables([], ({database}: WithDatabaseArgs) => ({
|
|
currentUser: observeCurrentUser(database),
|
|
}));
|
|
|
|
const withPost = withObservables(
|
|
['currentUser', 'isCRTEnabled', 'post', 'previousPost', 'nextPost'],
|
|
({currentUser, database, isCRTEnabled, post, previousPost, nextPost, location}: PropsInput) => {
|
|
let isLastReply = of$(true);
|
|
let isPostAddChannelMember = of$(false);
|
|
const isOwner = currentUser?.id === post.userId;
|
|
const author = post.userId ? observePostAuthor(database, post) : of$(undefined);
|
|
const canDelete = observePermissionForPost(database, post, currentUser, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false);
|
|
const isEphemeral = of$(isPostEphemeral(post));
|
|
|
|
if (post.props?.add_channel_member && isPostEphemeral(post) && currentUser) {
|
|
isPostAddChannelMember = observeCanManageChannelMembers(database, post.channelId, currentUser);
|
|
}
|
|
|
|
let highlightReplyBar = of$(false);
|
|
if (!isCRTEnabled && location === Screens.CHANNEL) {
|
|
highlightReplyBar = post.postsInThread.observe().pipe(
|
|
switchMap((postsInThreads: PostsInThreadModel[]) => {
|
|
if (postsInThreads.length && currentUser) {
|
|
return observeShouldHighlightReplyBar(database, currentUser, post, postsInThreads[0]);
|
|
}
|
|
return of$(false);
|
|
}),
|
|
distinctUntilChanged(),
|
|
);
|
|
}
|
|
|
|
let differentThreadSequence = true;
|
|
if (post.rootId) {
|
|
differentThreadSequence = previousPost?.rootId ? previousPost?.rootId !== post.rootId : previousPost?.id !== post.rootId;
|
|
isLastReply = of$(!(nextPost?.rootId === post.rootId));
|
|
}
|
|
|
|
const hasReplies = observeHasReplies(database, post);//Need to review and understand
|
|
|
|
// Don't combine consecutive Burn on Read posts as we want each BoR post
|
|
// to display its header to allow displaying the remaining time.
|
|
const isConsecutivePost = isBoRPost(post) ? of$(false) : author.pipe(
|
|
switchMap((user) => of$(Boolean(post && previousPost && !user?.isBot && areConsecutivePosts(post, previousPost)))),
|
|
distinctUntilChanged(),
|
|
);
|
|
|
|
const hasFiles = queryFilesForPost(database, post.id).observeCount().pipe(
|
|
switchMap((c) => of$(c > 0)),
|
|
distinctUntilChanged(),
|
|
);
|
|
|
|
const hasReactions = queryReactionsForPost(database, post.id).observeCount().pipe(
|
|
switchMap((c) => of$(c > 0)),
|
|
distinctUntilChanged(),
|
|
);
|
|
|
|
return {
|
|
canDelete,
|
|
differentThreadSequence: of$(differentThreadSequence),
|
|
hasFiles,
|
|
hasReplies,
|
|
highlightReplyBar,
|
|
isConsecutivePost,
|
|
isEphemeral,
|
|
isFirstReply: of$(isFirstReply(post, previousPost)),
|
|
isLastReply,
|
|
isPostAddChannelMember,
|
|
isPostPriorityEnabled: observeIsPostPriorityEnabled(database),
|
|
post: post.observe(),
|
|
thread: isCRTEnabled ? observeThreadById(database, post.id) : of$(undefined),
|
|
hasReactions,
|
|
isLastPost: of$(!nextPost),
|
|
};
|
|
});
|
|
|
|
export default React.memo(withDatabase(withSystem(withPost(Post))));
|