diff --git a/app/components/post_list/post/header/header.tsx b/app/components/post_list/post/header/header.tsx
index a78ef9286..8602e1209 100644
--- a/app/components/post_list/post/header/header.tsx
+++ b/app/components/post_list/post/header/header.tsx
@@ -102,7 +102,7 @@ const Header = (props: HeaderProps) => {
const usernameOverride = ensureString(post.props?.override_username);
const isUnrevealedPost = useMemo(() => isUnrevealedBoRPost(post), [post, post.metadata?.expire_at]);
- const ownBoRPost = useMemo(() => isOwnBoRPost(post, currentUser), [currentUser, post]);
+ const ownBoRPost = useMemo(() => isOwnBoRPost(post, currentUser?.id), [currentUser?.id, post]);
const showBoRIcon = isUnrevealedPost || ownBoRPost;
const borExpireAt = post.metadata?.expire_at;
const serverUrl = useServerUrl();
diff --git a/app/screens/post_options/index.test.tsx b/app/screens/post_options/index.test.tsx
index d23d5f48f..9b6b228f3 100644
--- a/app/screens/post_options/index.test.tsx
+++ b/app/screens/post_options/index.test.tsx
@@ -28,7 +28,38 @@ describe('PostOptions', () => {
database = server.database;
});
- it('should show limited options for unrevealed BoR post', async () => {
+ it('should show limited options for own BoR post', async () => {
+ const unrevealedBoRPost = TestHelper.fakePostModel({
+ type: PostTypes.BURN_ON_READ,
+ channelId: TestHelper.basicChannel!.id,
+ userId: TestHelper.basicUser!.id,
+ });
+
+ renderWithEverything(
+ ,
+ {database},
+ );
+
+ await waitFor(() => {
+ expect(screen.queryByText('Save')).toBeVisible();
+ });
+
+ expect(screen.queryByText('Copy Link')).toBeVisible();
+
+ expect(screen.queryByText('Pin to Channel')).not.toBeVisible();
+ expect(screen.queryByText('Copy Text')).not.toBeVisible();
+ expect(screen.queryByText('Edit')).not.toBeVisible();
+ expect(screen.queryByText('Reply')).not.toBeVisible();
+ expect(screen.queryByText('Follow Message')).not.toBeVisible();
+ });
+
+ it('should show limited options for received unrevealed BoR post', async () => {
const unrevealedBoRPost = TestHelper.fakePostModel({
type: PostTypes.BURN_ON_READ,
channelId: TestHelper.basicChannel!.id,
@@ -54,9 +85,8 @@ describe('PostOptions', () => {
expect(screen.getByText('Mark as Unread')).toBeVisible();
});
- expect(screen.queryByText('Copy Link')).toBeVisible();
- expect(screen.queryByText('Save')).toBeVisible();
-
+ expect(screen.queryByText('Copy Link')).not.toBeVisible();
+ expect(screen.queryByText('Save')).not.toBeVisible();
expect(screen.queryByText('Pin to Channel')).not.toBeVisible();
expect(screen.queryByText('Copy Text')).not.toBeVisible();
expect(screen.queryByText('Edit')).not.toBeVisible();
@@ -64,7 +94,7 @@ describe('PostOptions', () => {
expect(screen.queryByText('Follow Message')).not.toBeVisible();
});
- it('should show limited options for revealed BoR post', async () => {
+ it('should show limited options for someone else\'s revealed BoR post', async () => {
const unrevealedBoRPost = TestHelper.fakePostModel({
type: PostTypes.BURN_ON_READ,
channelId: TestHelper.basicChannel!.id,
@@ -91,11 +121,10 @@ describe('PostOptions', () => {
await waitFor(() => {
expect(screen.getByText('Mark as Unread')).toBeVisible();
+ expect(screen.queryByText('Save')).toBeVisible();
});
- expect(screen.queryByText('Copy Link')).toBeVisible();
- expect(screen.queryByText('Save')).toBeVisible();
-
+ expect(screen.queryByText('Copy Link')).not.toBeVisible();
expect(screen.queryByText('Copy Text')).not.toBeVisible();
expect(screen.queryByText('Pin to Channel')).not.toBeVisible();
expect(screen.queryByText('Edit')).not.toBeVisible();
diff --git a/app/screens/post_options/index.ts b/app/screens/post_options/index.ts
index ccbd2a09c..8d270fb74 100644
--- a/app/screens/post_options/index.ts
+++ b/app/screens/post_options/index.ts
@@ -3,7 +3,7 @@
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
import {combineLatest, of as of$, Observable} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
+import {distinctUntilChanged, switchMap} from 'rxjs/operators';
import {Permissions, Post, Screens} from '@constants';
import {AppBindingLocations} from '@constants/apps';
@@ -146,14 +146,20 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, sour
}),
);
- const canDelete = combineLatest([canDeletePostPermission, channelIsArchived, channelIsReadOnly, canPostPermission]).pipe(switchMap(([permission, isArchived, isReadOnly, canPost]) => {
- return of$(permission && !isArchived && !isReadOnly && canPost);
+ const canDelete = combineLatest([canDeletePostPermission, channelIsArchived, channelIsReadOnly, canPostPermission, currentUser]).pipe(switchMap(([permission, isArchived, isReadOnly, canPost, user]) => {
+ const canDeleteBoRPost = borPost ? post.userId === user?.id : true;
+ return of$(permission && !isArchived && !isReadOnly && canPost && canDeleteBoRPost);
}));
const thread = observeIsCRTEnabled(database).pipe(
switchMap((enabled) => (enabled ? observeThreadById(database, post.id) : of$(undefined))),
);
+ const currentUserId = currentUser.pipe(
+ switchMap((u) => of$(u?.id)),
+ distinctUntilChanged(),
+ );
+
return {
canMarkAsUnread,
canAddReaction,
@@ -167,6 +173,7 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, sour
thread,
bindings,
isBoRPost: of$(borPost),
+ currentUserId,
};
});
diff --git a/app/screens/post_options/post_options.tsx b/app/screens/post_options/post_options.tsx
index a9ef4c568..6b225a13c 100644
--- a/app/screens/post_options/post_options.tsx
+++ b/app/screens/post_options/post_options.tsx
@@ -16,6 +16,7 @@ import {useIsTablet} from '@hooks/device';
import useNavButtonPressed from '@hooks/navigation_button_pressed';
import BottomSheet from '@screens/bottom_sheet';
import {dismissBottomSheet} from '@screens/navigation';
+import {isOwnBoRPost, isUnrevealedBoRPost} from '@utils/bor';
import {bottomSheetSnapPoint} from '@utils/helpers';
import {isSystemMessage} from '@utils/post';
@@ -48,13 +49,14 @@ type PostOptionsProps = {
bindings: AppBinding[];
serverUrl: string;
isBoRPost?: boolean;
+ currentUserId?: string;
};
const PostOptions = ({
canAddReaction, canDelete, canEdit,
canMarkAsUnread, canPin, canReply,
combinedPost, componentId, isSaved,
sourceScreen, post, thread, bindings, serverUrl,
- isBoRPost,
+ isBoRPost, currentUserId,
}: PostOptionsProps) => {
const managedConfig = useManagedConfig();
const isTablet = useIsTablet();
@@ -69,9 +71,12 @@ const PostOptions = ({
const isSystemPost = isSystemMessage(post);
- const canCopyPermalink = !isSystemPost && managedConfig?.copyAndPasteProtection !== 'true';
+ const canCopyBoRPostPermalink = isBoRPost ? post.userId === currentUserId : true;
+ const canCopyPermalink = !isSystemPost && managedConfig?.copyAndPasteProtection !== 'true' && canCopyBoRPostPermalink;
const canCopyText = canCopyPermalink && post.message && !isBoRPost;
+ const canSavePost = !isSystemPost && (!isUnrevealedBoRPost(post) || isOwnBoRPost(post, currentUserId));
+
const shouldRenderFollow = !(sourceScreen !== Screens.CHANNEL || !thread);
const shouldShowBindings = bindings.length > 0 && !isSystemPost;
@@ -79,7 +84,7 @@ const PostOptions = ({
const items: Array = [1];
const optionsCount = [
canCopyPermalink, canCopyText, canDelete, canEdit,
- canMarkAsUnread, canPin, canReply, !isSystemPost, shouldRenderFollow,
+ canMarkAsUnread, canPin, canReply, canSavePost, shouldRenderFollow,
].reduce((acc, v) => {
return v ? acc + 1 : acc;
}, 0) + (shouldShowBindings ? 0.5 : 0);
@@ -94,7 +99,7 @@ const PostOptions = ({
}, [
canAddReaction, canCopyPermalink, canCopyText,
canDelete, canEdit, shouldRenderFollow, shouldShowBindings,
- canMarkAsUnread, canPin, canReply, isSystemPost,
+ canMarkAsUnread, canPin, canReply, canSavePost,
]);
const renderContent = () => {
@@ -136,7 +141,7 @@ const PostOptions = ({
sourceScreen={sourceScreen}
/>
}
- {!isSystemPost &&
+ {canSavePost &&
{
userId: 'user123',
} as PostModel;
- expect(isOwnBoRPost(ownBorPost, mockUser)).toBe(true);
+ expect(isOwnBoRPost(ownBorPost, mockUser.id)).toBe(true);
});
it('should return false for BoR posts not owned by current user', () => {
@@ -109,7 +109,7 @@ describe('BoR utility functions', () => {
userId: 'user456',
} as PostModel;
- expect(isOwnBoRPost(othersBorPost, mockUser)).toBe(false);
+ expect(isOwnBoRPost(othersBorPost, mockUser.id)).toBe(false);
});
it('should return false for non-BoR posts', () => {
@@ -118,7 +118,7 @@ describe('BoR utility functions', () => {
userId: 'user123',
} as PostModel;
- expect(isOwnBoRPost(ownRegularPost, mockUser)).toBe(false);
+ expect(isOwnBoRPost(ownRegularPost, mockUser.id)).toBe(false);
});
it('should return false when no current user is provided', () => {
diff --git a/app/utils/bor.ts b/app/utils/bor.ts
index d00d9229f..19c04bb52 100644
--- a/app/utils/bor.ts
+++ b/app/utils/bor.ts
@@ -17,8 +17,8 @@ export function isUnrevealedBoRPost(post: Post | PostModel): boolean {
return isBoRPost(post) && Boolean(!post.metadata?.expire_at);
}
-export function isOwnBoRPost(post: PostModel, currentUser?: UserModel): boolean {
- return isBoRPost(post) && Boolean(currentUser && post.userId === currentUser.id);
+export function isOwnBoRPost(post: PostModel, currentUserId?: UserModel['id']): boolean {
+ return isBoRPost(post) && post.userId === currentUserId;
}
function isBoRPostExpiredForMe(post: PostModel): boolean {