Skip to content

Commit d732b5a

Browse files
Ability to show attachment if the permalink post contains attachments. (#9101)
* 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 * Address review comments * Minor * Merge fixes * Addressed review comments * Some more review comments * linter fixes * UX review and remove show more height not require * Fix tests * Review fixes, dev and ux --------- Co-authored-by: yasserfaraazkhan <[email protected]>
1 parent 71fed8a commit d732b5a

File tree

9 files changed

+511
-120
lines changed

9 files changed

+511
-120
lines changed

app/components/files/file.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ type FileProps = {
4545
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
4646
return {
4747
fileWrapper: {
48-
flex: 1,
4948
flexDirection: 'row',
5049
alignItems: 'center',
5150
borderWidth: 1,
@@ -64,7 +63,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
6463
margin: 4,
6564
},
6665
audioFile: {
67-
flex: 1,
6866
flexDirection: 'row',
6967
alignItems: 'center',
7068
},

app/components/files/files.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@ type FilesProps = {
2626
isReplyPost: boolean;
2727
postId?: string;
2828
postProps?: Record<string, unknown>;
29+
isPermalinkPreview?: boolean;
2930
}
3031

3132
const MAX_VISIBLE_ROW_IMAGES = 4;
3233
const styles = StyleSheet.create({
3334
row: {
34-
flex: 1,
3535
flexDirection: 'row',
3636
marginTop: 5,
37+
flexShrink: 0,
3738
},
38-
container: {
39+
rowItemContainer: {
3940
flex: 1,
4041
},
4142
gutter: {
@@ -59,6 +60,7 @@ const Files = ({
5960
location,
6061
postId,
6162
postProps,
63+
isPermalinkPreview = false,
6264
}: FilesProps) => {
6365
const galleryIdentifier = `${postId}-fileAttachments-${location}`;
6466
const [inViewPort, setInViewPort] = useState(false);
@@ -84,9 +86,14 @@ const Files = ({
8486

8587
const isSingleImage = useMemo(() => filesInfo.filter((f) => isImage(f) || isVideo(f)).length === 1, [filesInfo]);
8688

87-
const renderItems = (items: FileInfo[], moreImagesCount = 0, includeGutter = false) => {
89+
const renderItems = (items: FileInfo[], moreImagesCount = 0, includeGutter = false, isImageRow = false) => {
8890
let nonVisibleImagesCount: number;
89-
let container: StyleProp<ViewStyle> = items.length > 1 ? styles.container : undefined;
91+
92+
// Apply flex: 1 for multiple items, except in permalink previews where vertical
93+
// document lists should not use flex to prevent shrinking under maxHeight constraints.
94+
// Horizontal image rows (includeGutter=true) still need flex for space distribution.
95+
const shouldApplyFlex = items.length > 1 && !(isPermalinkPreview && !includeGutter);
96+
let container: StyleProp<ViewStyle> = shouldApplyFlex ? styles.rowItemContainer : undefined;
9097
const containerWithGutter = [container, styles.gutter];
9198
const wrapperWidth = getViewPortWidth(isReplyPost, isTablet) - 6;
9299

@@ -98,9 +105,13 @@ const Files = ({
98105
if (idx !== 0 && includeGutter) {
99106
container = containerWithGutter;
100107
}
108+
const shouldRemoveMarginTop = isPermalinkPreview && (
109+
(isImageRow) || // Remove marginTop for all images in image row
110+
(!isImageRow && idx === 0 && imageAttachments.length === 0) // Remove marginTop for first non-image only if no images present
111+
);
101112
return (
102113
<View
103-
style={[container, styles.marginTop]}
114+
style={[container, styles.marginTop, shouldRemoveMarginTop && {marginTop: 0}]}
104115
testID={`${file.id}-file-container`}
105116
key={file.id}
106117
>
@@ -138,10 +149,14 @@ const Files = ({
138149

139150
return (
140151
<View
141-
style={[styles.row, {width: portraitPostWidth}]}
152+
style={[
153+
styles.row,
154+
{width: portraitPostWidth},
155+
isPermalinkPreview && {marginTop: 0},
156+
]}
142157
testID='image-row'
143158
>
144-
{ renderItems(visibleImages, nonVisibleImagesCount, true) }
159+
{ renderItems(visibleImages, nonVisibleImagesCount, true, true) }
145160
</View>
146161
);
147162
};
@@ -164,7 +179,10 @@ const Files = ({
164179
<GalleryInit galleryIdentifier={galleryIdentifier}>
165180
<Animated.View
166181
testID='files-container'
167-
style={failed ? styles.failed : undefined}
182+
style={[
183+
failed ? styles.failed : undefined,
184+
isPermalinkPreview && {marginTop: 0},
185+
]}
168186
>
169187
{renderImageRow()}
170188
{renderItems(nonImageAttachments)}

app/components/post_list/post/body/content/content.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ const Content = ({isReplyPost, layoutWidth, location, post, theme, showPermalink
116116
<PermalinkPreview
117117
embedData={post.metadata!.embeds![0].data as PermalinkEmbedData}
118118
location={location}
119+
parentLocation={location}
120+
parentPostId={post.id}
119121
/>
120122
);
121123
}

app/components/post_list/post/body/content/permalink_preview/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,21 @@ const enhance = withObservables(['embedData'], ({database, embedData}: WithDatab
2424
const userId = embedData?.post?.user_id;
2525
const author = userId ? observeUser(database, userId) : of$(undefined);
2626

27-
const isOriginPostDeleted = embedData?.post_id ? observePost(database, embedData.post_id).pipe(
27+
const post = embedData?.post_id ? observePost(database, embedData.post_id) : of$(undefined);
28+
const isOriginPostDeleted = post.pipe(
2829
switchMap((p) => {
2930
const initialDeleted = Boolean(embedData?.post?.delete_at > 0 || embedData?.post?.state === 'DELETED');
3031
return of$(p ? p.deleteAt > 0 : initialDeleted);
3132
}),
3233
distinctUntilChanged(),
33-
) : of$(false);
34+
);
3435

3536
return {
3637
teammateNameDisplay,
3738
currentUser,
3839
isMilitaryTime,
3940
author,
41+
post,
4042
isOriginPostDeleted,
4143
};
4244
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2+
// See LICENSE.txt for license information.
3+
4+
import React, {useEffect, useState, useCallback} from 'react';
5+
import {DeviceEventEmitter, View, type LayoutChangeEvent, StyleSheet} from 'react-native';
6+
7+
import Files from '@components/files';
8+
import {Events} from '@constants';
9+
10+
import type PostModel from '@typings/database/models/servers/post';
11+
12+
type PermalinkFilesProps = {
13+
post: PostModel;
14+
location: string;
15+
isReplyPost: boolean;
16+
failed?: boolean;
17+
parentLocation?: string;
18+
parentPostId?: string;
19+
};
20+
21+
const styles = StyleSheet.create({
22+
container: {
23+
marginBottom: 8,
24+
},
25+
});
26+
27+
const PermalinkFiles = (props: PermalinkFilesProps) => {
28+
const {parentLocation, parentPostId, post, location, isReplyPost, failed} = props;
29+
const [layoutWidth, setLayoutWidth] = useState(0);
30+
31+
const listener = useCallback((viewableItemsMap: Record<string, boolean>) => {
32+
if (!parentLocation || !parentPostId) {
33+
return;
34+
}
35+
36+
const parentKey = `${parentLocation}-${parentPostId}`;
37+
if (viewableItemsMap[parentKey]) {
38+
const viewableItems = {[`${location}-${post.id}`]: true};
39+
DeviceEventEmitter.emit(Events.ITEM_IN_VIEWPORT, viewableItems);
40+
}
41+
}, [parentLocation, parentPostId, location, post.id]);
42+
43+
useEffect(() => {
44+
if (!parentLocation || !parentPostId) {
45+
return undefined;
46+
}
47+
48+
const subscription = DeviceEventEmitter.addListener(Events.ITEM_IN_VIEWPORT, listener);
49+
return () => subscription.remove();
50+
}, [listener, parentLocation, parentPostId]);
51+
52+
const onLayout = useCallback((event: LayoutChangeEvent) => {
53+
setLayoutWidth(event.nativeEvent.layout.width);
54+
}, []);
55+
56+
return (
57+
<View
58+
onLayout={onLayout}
59+
testID='permalink-files-container'
60+
style={styles.container}
61+
>
62+
<Files
63+
post={post}
64+
location={location}
65+
isReplyPost={isReplyPost}
66+
failed={failed}
67+
layoutWidth={layoutWidth}
68+
isPermalinkPreview={true}
69+
/>
70+
</View>
71+
);
72+
};
73+
74+
export default PermalinkFiles;

0 commit comments

Comments
 (0)