From 4e95364344a6e641ae3a0d8d78371fdb257299ea Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 12 Sep 2025 17:46:39 +0530 Subject: [PATCH] Navigation for permalink and Tablet fixes (#9116) * typescript and view component for permalink with user and message * Old post edited handling in permalink * Added test and update flag value to EnablePermalinkPreview * Added test for permalink_preview component * Added test for content/index.tsx for permalink * Addressed review comments * Unit test for missing file and review comments * Added test to check handlePostEdited permalink sync only calls one time only * Change TouchableOpacity to Pressable * When user not found fetch the user from the server * Removed the redundant test in the test for permalink_preview/index? * ts to tsx * Removed the circular dependency * Address review comments * displayname fallback * remove permalink when permalink post is deleted * UX review comments * Linter fixes * Test fixes * File attachment in permalink preview component * Fix the width and height of the image in permalink * Added gredient when exceeds height of permalink container * Minor * Updated tests * Minor * Review comments * Minor review comments * type fixes * Review comments * Minor * Mention ability in permalink preview * Support for external link in permalink * Handle device not connected update permalink post * test fixes * Address review comments * Minor * Merge fixes * Addressed review comments * Fix content.test.tsx after permalink branch merge - Updated test expectations to use embedData prop instead of post prop - Fixed assertions to match the new PermalinkPreview API - All content tests now pass * review comments * Review comments * Some more review comments * Minor * Fixed the undefined issue for opengraph component metadata * More fixes * Navigation for permalink * Tablet fixes * type chech for site name * linter fixes * UX review and remove show more height not require * Fix tests * Fixed the navigation issue for DM's * Minor fixes due to merge main --------- Co-authored-by: yasserfaraazkhan --- app/components/files/files.tsx | 3 ++ .../permalink_preview.test.tsx | 44 +++++++++++++++++++ .../permalink_preview/permalink_preview.tsx | 32 ++++++++++++-- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/app/components/files/files.tsx b/app/components/files/files.tsx index 508c1ee54..336fc6211 100644 --- a/app/components/files/files.tsx +++ b/app/components/files/files.tsx @@ -48,6 +48,9 @@ const styles = StyleSheet.create({ marginTop: { marginTop: 10, }, + rowPermalinkPreview: { + marginTop: 0, + }, }); const Files = ({ diff --git a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx index 430c4967b..67256df80 100644 --- a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx +++ b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx @@ -4,6 +4,7 @@ import {fireEvent} from '@testing-library/react-native'; import React from 'react'; +import {showPermalink} from '@actions/remote/permalink'; import Markdown from '@components/markdown'; import {Screens} from '@constants'; import DatabaseManager from '@database/manager'; @@ -15,6 +16,10 @@ import PermalinkPreview from './permalink_preview'; import type ServerDataOperator from '@database/operator/server_data_operator'; import type {Database} from '@nozbe/watermelondb'; +jest.mock('@actions/remote/permalink', () => ({ + showPermalink: jest.fn(), +})); + jest.mock('@components/markdown', () => ({ __esModule: true, default: jest.fn(), @@ -29,6 +34,8 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev const serverUrl = 'http://localhost:8065'; beforeEach(async () => { + jest.clearAllMocks(); + await DatabaseManager.init([serverUrl]); const serverDatabaseAndOperator = DatabaseManager.getServerDatabaseAndOperator(serverUrl); database = serverDatabaseAndOperator.database; @@ -152,6 +159,43 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev expect(getByTestId('permalink-preview-container')).toBeTruthy(); }); + it('should call showPermalink with correct parameters when container is pressed', () => { + const mockShowPermalink = jest.mocked(showPermalink); + const {getByTestId} = renderPermalinkPreview(baseProps); + const permalinkContainer = getByTestId('permalink-preview-container'); + + fireEvent.press(permalinkContainer); + + expect(mockShowPermalink).toHaveBeenCalledWith( + serverUrl, + baseProps.embedData.team_name, + baseProps.embedData.post_id, + ); + }); + + it('should call showPermalink even when team_name is empty for DM posts', () => { + const mockShowPermalink = jest.mocked(showPermalink); + const dmProps = { + ...baseProps, + embedData: { + ...baseProps.embedData, + team_name: '', + channel_type: 'D', + channel_display_name: 'testuser', + }, + }; + const {getByTestId} = renderPermalinkPreview(dmProps); + const permalinkContainer = getByTestId('permalink-preview-container'); + + fireEvent.press(permalinkContainer); + + expect(mockShowPermalink).toHaveBeenCalledWith( + serverUrl, + '', + baseProps.embedData.post_id, + ); + }); + it('should display author name from user model', () => { const {getByText} = renderPermalinkPreview(baseProps); diff --git a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx index 9d7e01ce4..586dc7c28 100644 --- a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx +++ b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx @@ -5,16 +5,18 @@ import {LinearGradient} from 'expo-linear-gradient'; import React, {useMemo, useCallback, useEffect, useState} from 'react'; import {Text, View, Pressable, type LayoutChangeEvent} from 'react-native'; +import {showPermalink} from '@actions/remote/permalink'; import {fetchUsersByIds} from '@actions/remote/user'; import EditedIndicator from '@components/edited_indicator'; import FormattedText from '@components/formatted_text'; import FormattedTime from '@components/formatted_time'; import Markdown from '@components/markdown'; import ProfilePicture from '@components/profile_picture'; +import {View as ViewConstants} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useUserLocale} from '@context/user_locale'; -import {useWindowDimensions} from '@hooks/device'; +import {useIsTablet, useWindowDimensions} from '@hooks/device'; import {usePreventDoubleTap} from '@hooks/utils'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -33,6 +35,8 @@ import type {AvailableScreens} from '@typings/screens/navigation'; const MAX_PERMALINK_PREVIEW_CHARACTERS = 150; const EDITED_INDICATOR_CONTEXT = ['paragraph']; const EMPTY_MENTION_KEYS: UserMentionKey[] = []; +const MIN_PERMALINK_WIDTH = 340; +const TABLET_PADDING_OFFSET = 40; type PermalinkPreviewProps = { embedData: PermalinkEmbedData; @@ -134,9 +138,21 @@ const PermalinkPreview = ({ const serverUrl = useServerUrl(); const locale = useUserLocale(); const dimensions = useWindowDimensions(); + const isTablet = useIsTablet(); const styles = getStyleSheet(theme); const [showGradient, setShowGradient] = useState(false); + const maxWidth = useMemo(() => { + if (!isTablet) { + return undefined; + } + + const deviceSize = Math.min(dimensions.width, dimensions.height); + const availableWidth = deviceSize - ViewConstants.TABLET_SIDEBAR_WIDTH; + + return Math.max(availableWidth - TABLET_PADDING_OFFSET, MIN_PERMALINK_WIDTH); + }, [dimensions.width, dimensions.height, isTablet]); + const maxPermalinkHeight = Math.round(dimensions.height * 0.5); const textStyles = getMarkdownTextStyles(theme); const blockStyles = getMarkdownBlockStyles(theme); @@ -192,8 +208,13 @@ const PermalinkPreview = ({ const hasFiles = filesInfo.length > 0; const handlePress = usePreventDoubleTap(useCallback(() => { - // Navigation will be implemented in Task 5 - }, [])); + const teamName = embedData.team_name; + const postId = embedData.post_id; + + if (postId) { + showPermalink(serverUrl, teamName, postId); + } + }, [embedData.team_name, embedData.post_id, serverUrl])); const handleContentLayout = useCallback((event: LayoutChangeEvent) => { const {height} = event.nativeEvent.layout; @@ -208,7 +229,10 @@ const PermalinkPreview = ({ [ styles.container, - {opacity: pressed ? 0.8 : 1}, + { + opacity: pressed ? 0.8 : 1, + maxWidth, + }, ]} onPress={handlePress} testID='permalink-preview-container'