[Gekidou] Markdown and Touchables (#6047)

* Fix markdown formatting and touchable interaction

* open gallery as overlay instead of modal

* update snapshots

* Add missing dependencies to useMemo
This commit is contained in:
Elias Nahum
2022-03-14 16:32:06 -03:00
committed by GitHub
parent acf4cbde8d
commit a43dad53e1
48 changed files with 793 additions and 825 deletions

View File

@@ -5,12 +5,12 @@ import {Parser} from 'commonmark';
import Renderer from 'commonmark-react-renderer';
import React, {ReactElement, useRef} from 'react';
import {useIntl} from 'react-intl';
import {GestureResponderEvent, Platform, StyleProp, Text, TextStyle} from 'react-native';
import {GestureResponderEvent, StyleProp, Text, TextStyle} from 'react-native';
import AtMention from '@components/markdown/at_mention';
import MarkdownLink from '@components/markdown/markdown_link';
import {useTheme} from '@context/theme';
import {getMarkdownTextStyles} from '@utils/markdown';
import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown';
import {concatStyles, changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {PrimitiveType} from 'intl-messageformat';
@@ -31,6 +31,11 @@ const TARGET_BLANK_URL_PREFIX = '!';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
block: {
alignItems: 'flex-start',
flexDirection: 'row',
flexWrap: 'wrap',
},
message: {
color: changeOpacity(theme.centerChannelColor, 0.8),
fontSize: 16,
@@ -39,9 +44,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
atMentionOpacity: {
opacity: 1,
},
touchableStyle: {
top: Platform.select({ios: 2, default: 4}),
},
};
});
@@ -86,7 +88,6 @@ const FormattedMarkdownText = ({baseTextStyle, defaultMessage, id, onPostPress,
mentionName={mentionName}
onPostPress={onPostPress}
textStyle={[computeTextStyle(baseTextStyle, context), styles.atMentionOpacity]}
touchableStyle={styles.touchableStyle}
/>
);
};
@@ -110,8 +111,18 @@ const FormattedMarkdownText = ({baseTextStyle, defaultMessage, id, onPostPress,
return <MarkdownLink href={url}>{children}</MarkdownLink>;
};
const renderParagraph = ({children}: {children: ReactElement}) => {
return <Text>{children}</Text>;
const renderParagraph = ({children, first}: {children: ReactElement; first: boolean}) => {
const blockStyle = [styles.block];
if (!first) {
const blockS = getMarkdownBlockStyles(theme);
blockStyle.push(blockS.adjacentParagraph);
}
return (
<Text style={blockStyle}>
{children}
</Text>
);
};
const renderText = ({context, literal}: {context: string[]; literal: string}) => {

View File

@@ -2,30 +2,20 @@
// See LICENSE.txt for license information.
import {useManagedConfig} from '@mattermost/react-native-emm';
import {Database, Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {Database} from '@nozbe/watermelondb';
import Clipboard from '@react-native-community/clipboard';
import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {GestureResponderEvent, StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {GestureResponderEvent, StyleProp, StyleSheet, Text, TextStyle, View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
import {Preferences} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {MM_TABLES} from '@constants/database';
import {useTheme} from '@context/theme';
import UserModel from '@database/models/server/user';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {bottomSheet, dismissBottomSheet, showModal} from '@screens/navigation';
import {displayUsername, getUsersByUsername} from '@utils/user';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModelType from '@typings/database/models/servers/user';
type AtMentionProps = {
@@ -39,16 +29,13 @@ type AtMentionProps = {
onPostPress?: (e: GestureResponderEvent) => void;
teammateNameDisplay: string;
textStyle?: StyleProp<TextStyle>;
touchableStyle?: StyleProp<ViewStyle>;
users: UserModelType[];
}
const {SERVER: {PREFERENCE, SYSTEM, USER}} = MM_TABLES;
const {SERVER: {USER}} = MM_TABLES;
const style = StyleSheet.create({
bottomSheet: {
flex: 1,
},
bottomSheet: {flex: 1},
});
const AtMention = ({
@@ -62,7 +49,6 @@ const AtMention = ({
onPostPress,
teammateNameDisplay,
textStyle,
touchableStyle,
users,
}: AtMentionProps) => {
const intl = useIntl();
@@ -228,48 +214,17 @@ const AtMention = ({
}
return (
<TouchableOpacity
<Text
onPress={onPress!}
onLongPress={onLongPress}
style={touchableStyle}
style={styleText}
>
<Text style={styleText}>
<Text style={mentionTextStyle}>
{'@' + mention}
</Text>
{suffixElement}
<Text style={mentionTextStyle}>
{'@' + mention}
</Text>
</TouchableOpacity>
{suffixElement}
</Text>
);
};
const withAtMention = withObservables(['mentionName'], ({database, mentionName}: {mentionName: string} & WithDatabaseArgs) => {
const config = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const license = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE);
const preferences = database.get<PreferenceModel>(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe();
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => of$(value)),
);
const teammateNameDisplay = combineLatest([config, license, preferences]).pipe(
map(
([{value: cfg}, {value: lcs}, prefs]) => getTeammateNameDisplaySetting(prefs, cfg, lcs),
),
);
let mn = mentionName.toLowerCase();
if ((/[._-]$/).test(mn)) {
mn = mn.substring(0, mn.length - 1);
}
return {
currentUserId,
teammateNameDisplay,
users: database.get(USER).query(
Q.where('username', Q.like(
`%${Q.sanitizeLikeString(mn)}%`,
)),
).observeWithColumns(['username']),
};
});
export default withDatabase(withAtMention(React.memo(AtMention)));
export default React.memo(AtMention);

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {Preferences} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import AtMention from './at_mention';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
const {SERVER: {PREFERENCE, SYSTEM, USER}} = MM_TABLES;
const {CONFIG, CURRENT_USER_ID, LICENSE} = SYSTEM_IDENTIFIERS;
const enhance = withObservables(['mentionName'], ({database, mentionName}: {mentionName: string} & WithDatabaseArgs) => {
const config = database.get<SystemModel>(SYSTEM).findAndObserve(CONFIG);
const license = database.get<SystemModel>(SYSTEM).findAndObserve(LICENSE);
const preferences = database.get<PreferenceModel>(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe();
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(CURRENT_USER_ID).pipe(
switchMap(({value}) => of$(value)),
);
const teammateNameDisplay = combineLatest([config, license, preferences]).pipe(
map(
([{value: cfg}, {value: lcs}, prefs]) => getTeammateNameDisplaySetting(prefs, cfg, lcs),
),
);
let mn = mentionName.toLowerCase();
if ((/[._-]$/).test(mn)) {
mn = mn.substring(0, mn.length - 1);
}
return {
currentUserId,
teammateNameDisplay,
users: database.get(USER).query(
Q.where('username', Q.like(
`%${Q.sanitizeLikeString(mn)}%`,
)),
).observeWithColumns(['username']),
};
});
export default withDatabase(enhance(AtMention));

View File

@@ -1,26 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {StyleProp, Text, TextStyle} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {map, switchMap} from 'rxjs/operators';
import {joinChannel, switchToChannelById} from '@actions/remote/channel';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {useServerUrl} from '@context/server';
import {t} from '@i18n';
import {dismissAllModals, popToRoot} from '@screens/navigation';
import {alertErrorWithFallback} from '@utils/draft';
import {preventDoubleTap} from '@utils/tap';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type SystemModel from '@typings/database/models/servers/system';
import type TeamModel from '@typings/database/models/servers/team';
export type ChannelMentions = Record<string, {id?: string; display_name: string; name?: string; team_name: string}>;
@@ -65,8 +57,6 @@ function getChannelFromChannelName(name: string, channels: ChannelModel[], chann
return null;
}
const {SERVER: {CHANNEL, SYSTEM, TEAM}} = MM_TABLES;
const ChannelMention = ({
channelMentions, channelName, channels, currentTeamId, currentUserId,
linkStyle, team, textStyle,
@@ -112,33 +102,16 @@ const ChannelMention = ({
}
return (
<TouchableOpacity onPress={handlePress}>
<Text style={textStyle}>
<Text style={linkStyle}>
{`~${channel.display_name}`}
</Text>
{suffix}
<Text style={textStyle}>
<Text
onPress={handlePress}
style={linkStyle}
>
{`~${channel.display_name}`}
</Text>
</TouchableOpacity>
{suffix}
</Text>
);
};
const withChannelsForTeam = withObservables([], ({database}: WithDatabaseArgs) => {
const currentTeamId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID);
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID);
const channels = currentTeamId.pipe(
switchMap(({value}) => database.get<ChannelModel>(CHANNEL).query(Q.where('team_id', value)).observeWithColumns(['display_name'])),
);
const team = currentTeamId.pipe(
switchMap(({value}) => database.get<TeamModel>(TEAM).findAndObserve(value)),
);
return {
channels,
currentTeamId: currentTeamId.pipe(map((ct) => ct.value)),
currentUserId: currentUserId.pipe(map((cu) => cu.value)),
team,
};
});
export default withDatabase(withChannelsForTeam(ChannelMention));
export default ChannelMention;

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {map, switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import ChannelMention from './channel_mention';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type SystemModel from '@typings/database/models/servers/system';
import type TeamModel from '@typings/database/models/servers/team';
export type ChannelMentions = Record<string, {id?: string; display_name: string; name?: string; team_name: string}>;
const {SERVER: {CHANNEL, SYSTEM, TEAM}} = MM_TABLES;
const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
const currentTeamId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID);
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID);
const channels = currentTeamId.pipe(
switchMap(({value}) => database.get<ChannelModel>(CHANNEL).query(Q.where('team_id', value)).observeWithColumns(['display_name'])),
);
const team = currentTeamId.pipe(
switchMap(({value}) => database.get<TeamModel>(TEAM).findAndObserve(value)),
);
return {
channels,
currentTeamId: currentTeamId.pipe(map((ct) => ct.value)),
currentUserId: currentUserId.pipe(map((cu) => cu.value)),
team,
};
});
export default withDatabase(enhance(ChannelMention));

View File

@@ -3,7 +3,6 @@
import React from 'react';
import {Text, TextStyle} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {popToRoot, showSearchModal, dismissAllModals} from '@screens/navigation';
@@ -22,11 +21,12 @@ const Hashtag = ({hashtag, linkStyle}: HashtagProps) => {
};
return (
<TouchableOpacity onPress={handlePress}>
<Text style={linkStyle}>
{`#${hashtag}`}
</Text>
</TouchableOpacity>
<Text
onPress={handlePress}
style={linkStyle}
>
{`#${hashtag}`}
</Text>
);
};

View File

@@ -56,6 +56,34 @@ type MarkdownProps = {
value: string | number;
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
// Android has trouble giving text transparency depending on how it's nested,
// so we calculate the resulting colour manually
const editedOpacity = Platform.select({
ios: 0.3,
android: 1.0,
});
const editedColor = Platform.select({
ios: theme.centerChannelColor,
android: blendColors(theme.centerChannelBg, theme.centerChannelColor, 0.3),
});
return {
block: {
alignItems: 'flex-start',
flexDirection: 'row',
flexWrap: 'wrap',
},
editedIndicatorText: {
color: editedColor,
opacity: editedOpacity,
},
atMentionOpacity: {
opacity: 1,
},
};
});
class Markdown extends PureComponent<MarkdownProps> {
static defaultProps = {
textStyles: {},
@@ -294,7 +322,9 @@ class Markdown extends PureComponent<MarkdownProps> {
return (
<View style={blockStyle}>
{children}
<Text>
{children}
</Text>
</View>
);
};
@@ -476,32 +506,4 @@ class Markdown extends PureComponent<MarkdownProps> {
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
// Android has trouble giving text transparency depending on how it's nested,
// so we calculate the resulting colour manually
const editedOpacity = Platform.select({
ios: 0.3,
android: 1.0,
});
const editedColor = Platform.select({
ios: theme.centerChannelColor,
android: blendColors(theme.centerChannelBg, theme.centerChannelColor, 0.3),
});
return {
block: {
alignItems: 'flex-start',
flexDirection: 'row',
flexWrap: 'wrap',
},
editedIndicatorText: {
color: editedColor,
opacity: editedOpacity,
},
atMentionOpacity: {
opacity: 1,
},
};
});
export default Markdown;

View File

@@ -5,8 +5,7 @@ import {useManagedConfig} from '@mattermost/react-native-emm';
import Clipboard from '@react-native-community/clipboard';
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, StyleSheet, Text, TextStyle, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Keyboard, StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native';
import FormattedText from '@components/formatted_text';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';

View File

@@ -5,8 +5,7 @@ import {useManagedConfig} from '@mattermost/react-native-emm';
import Clipboard from '@react-native-community/clipboard';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {Alert, Platform, StyleProp, Text, TextStyle, View} from 'react-native';
import {LongPressGestureHandler, TapGestureHandler} from 'react-native-gesture-handler';
import {Alert, Platform, StyleProp, Text, TextStyle, TouchableWithoutFeedback, View} from 'react-native';
import Animated from 'react-native-reanimated';
import {SvgUri} from 'react-native-svg';
import parseUrl from 'url-parse';
@@ -74,8 +73,7 @@ const MarkdownImage = ({
const style = getStyleSheet(theme);
const managedConfig = useManagedConfig();
const genericFileId = useRef(generateId('uid')).current;
const tapRef = useRef<TapGestureHandler>();
const metadata = imagesMetadata?.[source] || Object.values(imagesMetadata || {})?.[0];
const metadata = imagesMetadata?.[source] || Object.values(imagesMetadata || {})[0];
const [failed, setFailed] = useState(isGifTooLarge(metadata));
const originalSize = getMarkdownImageSize(isReplyPost, isTablet, sourceSize, metadata, layoutWidth);
const serverUrl = useServerUrl();
@@ -109,11 +107,12 @@ const MarkdownImage = ({
width: originalSize.width,
height: originalSize.height,
} as FileInfo;
}, [uri, originalSize, metadata, isReplyPost, isTablet]);
}, [originalSize, metadata]);
const handlePreviewImage = useCallback(() => {
const item: GalleryItemType = {
...fileToGalleryItem(fileInfo),
mime_type: lookupMimeType(fileInfo.name),
type: 'image',
};
openGalleryAtIndex(galleryIdentifier, 0, [item]);
@@ -233,28 +232,43 @@ const MarkdownImage = ({
);
} else {
image = (
<TouchableWithoutFeedback
disabled={disabled}
onLongPress={handleLinkLongPress}
onPress={onGestureEvent}
>
<Animated.View
style={[styles, {width, height}, style.container]}
testID='markdown_image'
>
<ProgressiveImage
forwardRef={ref}
id={fileInfo.id!}
defaultSource={{uri: fileInfo.uri!}}
onError={handleOnError}
resizeMode='contain'
style={{width, height}}
/>
</Animated.View>
</TouchableWithoutFeedback>
);
}
}
if (image && linkDestination && !disabled) {
image = (
<TouchableWithFeedback
onPress={handleLinkPress}
onLongPress={handleLinkLongPress}
style={[{width, height}, style.container]}
>
<ProgressiveImage
forwardRef={ref}
id={fileInfo.id!}
defaultSource={{uri: fileInfo.uri!}}
onError={handleOnError}
resizeMode='contain'
style={{width, height}}
/>
);
}
}
if (image && linkDestination && !disabled) {
return (
<TouchableWithFeedback
onPress={handleLinkPress}
onLongPress={handleLinkLongPress}
style={[{width, height}, style.container]}
testID='markdown_image_link'
>
{image}
</TouchableWithFeedback>
);
}
@@ -262,23 +276,7 @@ const MarkdownImage = ({
return (
<GalleryInit galleryIdentifier={galleryIdentifier}>
<Animated.View testID='markdown_image'>
<LongPressGestureHandler
enabled={!disabled}
onGestureEvent={handleLinkLongPress}
waitFor={tapRef}
>
<Animated.View style={[styles, {width, height}, style.container]}>
<TapGestureHandler
enabled={!disabled}
onGestureEvent={onGestureEvent}
ref={tapRef}
>
<Animated.View>
{image}
</Animated.View>
</TapGestureHandler>
</Animated.View>
</LongPressGestureHandler>
{image}
</Animated.View>
</GalleryInit>
);

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import MarkdownLink from './markdown_link';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
type ConfigValue = {
value: ClientConfig;
}
const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
const config = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const experimentalNormalizeMarkdownLinks = config.pipe(
switchMap(({value}: ConfigValue) => of$(value.ExperimentalNormalizeMarkdownLinks)),
);
const siteURL = config.pipe(
switchMap(({value}: ConfigValue) => of$(value.SiteURL)),
);
return {
experimentalNormalizeMarkdownLinks,
siteURL,
};
});
export default withDatabase(enhance(MarkdownLink));

View File

@@ -2,21 +2,15 @@
// See LICENSE.txt for license information.
import {useManagedConfig} from '@mattermost/react-native-emm';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import Clipboard from '@react-native-community/clipboard';
import React, {Children, ReactElement, useCallback} from 'react';
import {useIntl} from 'react-intl';
import {Alert, StyleSheet, Text, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import urlParse from 'url-parse';
import {showPermalink} from '@actions/local/permalink';
import {switchToChannelByName} from '@actions/remote/channel';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import DeepLinkTypes from '@constants/deep_linking';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
@@ -25,8 +19,6 @@ import {errorBadChannel} from '@utils/draft';
import {preventDoubleTap} from '@utils/tap';
import {matchDeepLink, normalizeProtocol, tryOpenURL} from '@utils/url';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
import type {DeepLinkChannel, DeepLinkPermalink, DeepLinkWithData} from '@typings/launch';
type MarkdownLinkProps = {
@@ -165,34 +157,13 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
const renderChildren = experimentalNormalizeMarkdownLinks ? parseChildren() : children;
return (
<TouchableOpacity
<Text
onPress={handlePress}
onLongPress={handleLongPress}
>
<Text>
{renderChildren}
</Text>
</TouchableOpacity>
{renderChildren}
</Text>
);
};
type ConfigValue = {
value: ClientConfig;
}
const withConfigValues = withObservables([], ({database}: WithDatabaseArgs) => {
const config = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const experimentalNormalizeMarkdownLinks = config.pipe(
switchMap(({value}: ConfigValue) => of$(value.ExperimentalNormalizeMarkdownLinks)),
);
const siteURL = config.pipe(
switchMap(({value}: ConfigValue) => of$(value.SiteURL)),
);
return {
experimentalNormalizeMarkdownLinks,
siteURL,
};
});
export default withDatabase(withConfigValues(MarkdownLink));
export default MarkdownLink;

View File

@@ -3,8 +3,7 @@
import React, {PureComponent, ReactNode} from 'react';
import {injectIntl, IntlShape} from 'react-intl';
import {Dimensions, EventSubscription, LayoutChangeEvent, Platform, ScaledSize, ScrollView, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Dimensions, EventSubscription, LayoutChangeEvent, Platform, ScaledSize, ScrollView, TouchableOpacity, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import CompassIcon from '@components/compass_icon';
@@ -34,6 +33,80 @@ type MarkdownTableProps = MarkdownTableInputProps & {
theme: Theme;
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
maxHeight: MAX_HEIGHT,
},
expandButton: {
height: 34,
width: 34,
},
iconContainer: {
maxWidth: '100%',
alignItems: 'flex-end',
paddingTop: 8,
paddingBottom: 4,
...Platform.select({
ios: {
paddingRight: 14,
},
}),
},
iconButton: {
backgroundColor: theme.centerChannelBg,
marginTop: -32,
marginRight: -6,
borderWidth: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 50,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
width: 34,
height: 34,
},
icon: {
fontSize: 14,
color: theme.linkColor,
...Platform.select({
ios: {
fontSize: 13,
},
}),
},
displayFlex: {
flex: 1,
},
table: {
width: '100%',
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderWidth: 1,
},
tablePadding: {
paddingRight: 10,
},
moreBelow: {
bottom: Platform.select({
ios: 34,
android: 33.75,
}),
height: 20,
position: 'absolute',
left: 0,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
},
moreRight: {
maxHeight: MAX_HEIGHT,
position: 'absolute',
top: 0,
width: 20,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
},
};
});
class MarkdownTable extends PureComponent<MarkdownTableProps, MarkdownTableState> {
private rowsSliced: boolean | undefined;
private colsSliced: boolean | undefined;
@@ -284,78 +357,4 @@ class MarkdownTable extends PureComponent<MarkdownTableProps, MarkdownTableState
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
maxHeight: MAX_HEIGHT,
},
expandButton: {
height: 34,
width: 34,
},
iconContainer: {
maxWidth: '100%',
alignItems: 'flex-end',
paddingTop: 8,
paddingBottom: 4,
...Platform.select({
ios: {
paddingRight: 14,
},
}),
},
iconButton: {
backgroundColor: theme.centerChannelBg,
marginTop: -32,
marginRight: -6,
borderWidth: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 50,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
width: 34,
height: 34,
},
icon: {
fontSize: 14,
color: theme.linkColor,
...Platform.select({
ios: {
fontSize: 13,
},
}),
},
displayFlex: {
flex: 1,
},
table: {
width: '100%',
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderWidth: 1,
},
tablePadding: {
paddingRight: 10,
},
moreBelow: {
bottom: Platform.select({
ios: 34,
android: 33.75,
}),
height: 20,
position: 'absolute',
left: 0,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
},
moreRight: {
maxHeight: MAX_HEIGHT,
position: 'absolute',
top: 0,
width: 20,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
},
};
});
export default injectIntl(MarkdownTable);

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React, {memo, useCallback, useRef, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {TapGestureHandler} from 'react-native-gesture-handler';
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native';
import Animated from 'react-native-reanimated';
import parseUrl from 'url-parse';
@@ -110,9 +109,9 @@ const MarkTableImage = ({disabled, imagesMetadata, location, postId, serverURL,
} else {
const {height, width} = calculateDimensions(metadata.height, metadata.width, 100, 100);
image = (
<TapGestureHandler
enabled={!disabled}
onGestureEvent={onGestureEvent}
<TouchableWithoutFeedback
disabled={disabled}
onPress={onGestureEvent}
>
<Animated.View
style={[styles, {width, height}]}
@@ -127,7 +126,7 @@ const MarkTableImage = ({disabled, imagesMetadata, location, postId, serverURL,
style={{width, height}}
/>
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
);
}

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {TapGestureHandler} from 'react-native-gesture-handler';
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native';
import Animated from 'react-native-reanimated';
import {updateDraftFile} from '@actions/local/draft';
@@ -131,11 +130,11 @@ export default function UploadItem({
style={style.preview}
>
<View style={style.previewContainer}>
<TapGestureHandler onGestureEvent={onGestureEvent}>
<TouchableWithoutFeedback onPress={onGestureEvent}>
<Animated.View style={[styles, style.filePreview]}>
{filePreviewComponent}
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
{file.failed &&
<UploadRetry
onPress={retryFileUpload}

View File

@@ -3,8 +3,7 @@
import React, {useCallback, useEffect} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Keyboard, StyleProp, TouchableHighlight, View, ViewStyle} from 'react-native';
import {fetchMissingProfilesByIds, fetchMissingProfilesByUsernames} from '@actions/remote/user';
import Markdown from '@components/markdown';
@@ -209,10 +208,11 @@ const CombinedUserActivity = ({
style={style}
testID={testID}
>
<TouchableOpacity
<TouchableHighlight
testID={itemTestID}
onPress={emptyFunction}
onLongPress={onLongPress}
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
>
<View style={styles.container}>
<SystemAvatar theme={theme}/>
@@ -226,7 +226,7 @@ const CombinedUserActivity = ({
</View>
</View>
</View>
</TouchableOpacity>
</TouchableHighlight>
</View>
);
};

View File

@@ -3,8 +3,7 @@
import React, {useState} from 'react';
import {useIntl} from 'react-intl';
import {Platform, Text} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Text} from 'react-native';
import FormattedMarkdownText from '@components/formatted_markdown_text';
import FormattedText from '@components/formatted_text';
@@ -33,9 +32,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
fontSize: 16,
lineHeight: 20,
},
touchableStyle: {
top: Platform.select({ios: 3, default: 5}),
},
};
});
@@ -83,18 +79,16 @@ const LastUsers = ({actor, postType, theme, usernames}: LastUsersProps) => {
textStyles={textStyles}
/>
<Text>{' '}</Text>
<TouchableOpacity
<Text
onPress={onPress}
style={style.touchableStyle}
style={style.linkText}
>
<Text style={style.linkText}>
<FormattedText
id={'last_users_message.others'}
defaultMessage={'{numOthers} others '}
values={{numOthers}}
/>
</Text>
</TouchableOpacity>
<FormattedText
id={'last_users_message.others'}
defaultMessage={'{numOthers} others '}
values={{numOthers}}
/>
</Text>
<FormattedMarkdownText
id={systemMessages[postType].id}
defaultMessage={systemMessages[postType].defaultMessage}

View File

@@ -1,21 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {ReactNode, useRef} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, Platform, StyleSheet, View} from 'react-native';
import {Keyboard, Platform, StyleSheet, TouchableOpacity, View} from 'react-native';
import FastImage from 'react-native-fast-image';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import CompassIcon from '@components/compass_icon';
import ProfilePicture from '@components/profile_picture';
import SystemAvatar from '@components/system_avatar';
import {View as ViewConstant} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import NetworkManager from '@init/network_manager';
@@ -23,9 +17,7 @@ import {showModal} from '@screens/navigation';
import {preventDoubleTap} from '@utils/tap';
import type {Client} from '@client/rest';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PostModel from '@typings/database/models/servers/post';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
import type {ImageSource} from 'react-native-vector-icons/Icon';
@@ -149,15 +141,4 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, isSystemPost, po
return component;
};
const withPost = withObservables(['post'], ({database, post}: {post: PostModel} & WithDatabaseArgs) => {
const enablePostIconOverride = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg) => of$(cfg.value.EnablePostIconOverride === 'true')),
);
return {
author: post.author.observe(),
enablePostIconOverride,
};
});
export default withDatabase(withPost(Avatar));
export default Avatar;

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import enhance from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import Avatar from './avatar';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PostModel from '@typings/database/models/servers/post';
import type SystemModel from '@typings/database/models/servers/system';
const withPost = enhance(['post'], ({database, post}: {post: PostModel} & WithDatabaseArgs) => {
const enablePostIconOverride = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg) => of$(cfg.value.EnablePostIconOverride === 'true')),
);
return {
author: post.author.observe(),
enablePostIconOverride,
};
});
export default withDatabase(withPost(Avatar));

View File

@@ -1,30 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {ReactNode} from 'react';
import {useIntl} from 'react-intl';
import {Text} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {removePost, sendAddToChannelEphemeralPost} from '@actions/local/post';
import {addMembersToChannel} from '@actions/remote/channel';
import FormattedText from '@components/formatted_text';
import AtMention from '@components/markdown/at_mention';
import {General} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {useServerUrl} from '@context/server';
import {t} from '@i18n';
import {getMarkdownTextStyles} from '@utils/markdown';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
type AddMembersProps = {
@@ -34,8 +25,6 @@ type AddMembersProps = {
theme: Theme;
}
const {SERVER: {SYSTEM, USER}} = MM_TABLES;
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
message: {
@@ -182,14 +171,16 @@ const AddMembers = ({channelType, currentUser, post, theme}: AddMembersProps) =>
defaultMessage={outOfChannelMessageText}
style={styles.message}
/>
<TouchableOpacity onPress={handleAddChannelMember}>
<Text
style={textStyles.link}
testID='add_channel_member_link'
onPress={handleAddChannelMember}
>
<FormattedText
id={linkId}
defaultMessage={linkText}
style={textStyles.link}
testID='add_channel_member_link'
/>
</TouchableOpacity>
</Text>
<FormattedText
id={'post_body.check_for_out_of_channel_mentions.message_last'}
defaultMessage={'? They will have access to all message history.'}
@@ -222,15 +213,4 @@ const AddMembers = ({channelType, currentUser, post, theme}: AddMembersProps) =>
);
};
const withChannelType = withObservables(['post'], ({database, post}: WithDatabaseArgs & {post: PostModel}) => ({
currentUser: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => database.get(USER).findAndObserve(value)),
),
channelType: post.channel.observe().pipe(
switchMap(
(channel: ChannelModel) => (channel ? of$(channel.type) : of$(null)),
),
),
}));
export default withDatabase(withChannelType(AddMembers));
export default AddMembers;

View File

@@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import AddMembers from './add_members';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type SystemModel from '@typings/database/models/servers/system';
const {SERVER: {SYSTEM, USER}} = MM_TABLES;
const enhance = withObservables(['post'], ({database, post}: WithDatabaseArgs & {post: PostModel}) => ({
currentUser: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => database.get(USER).findAndObserve(value)),
),
channelType: post.channel.observe().pipe(
switchMap(
(channel: ChannelModel) => (channel ? of$(channel.type) : of$(null)),
),
),
}));
export default withDatabase(enhance(AddMembers));

View File

@@ -1,19 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {Animated, StyleSheet, View} from 'react-native';
import {TapGestureHandler} from 'react-native-gesture-handler';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {Animated, StyleSheet, TouchableWithoutFeedback, View} from 'react-native';
import {getRedirectLocation} from '@actions/remote/general';
import FileIcon from '@components/post_list/post/body/files/file_icon';
import ProgressiveImage from '@components/progressive_image';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {GalleryInit} from '@context/gallery';
import {useServerUrl} from '@context/server';
import {useIsTablet} from '@hooks/device';
@@ -26,9 +19,6 @@ import {calculateDimensions, getViewPortWidth, isGifTooLarge} from '@utils/image
import {changeOpacity} from '@utils/theme';
import {extractFilenameFromUrl, isImageLink, isValidUrl} from '@utils/url';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
type ImagePreviewProps = {
expandedLink?: string;
isReplyPost: boolean;
@@ -124,7 +114,7 @@ const ImagePreview = ({expandedLink, isReplyPost, layoutWidth, link, location, m
return (
<GalleryInit galleryIdentifier={galleryIdentifier}>
<Animated.View style={[styles, style.imageContainer, {height: dimensions.height}]}>
<TapGestureHandler onGestureEvent={onGestureEvent}>
<TouchableWithoutFeedback onPress={onGestureEvent}>
<Animated.View testID={`ImagePreview-${fileId}`}>
<ProgressiveImage
forwardRef={ref}
@@ -135,25 +125,10 @@ const ImagePreview = ({expandedLink, isReplyPost, layoutWidth, link, location, m
style={[style.image, {width: dimensions.width, height: dimensions.height}]}
/>
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
</Animated.View>
</GalleryInit>
);
};
const withExpandedLink = withObservables(['metadata'], ({database, metadata}: WithDatabaseArgs & {metadata: PostMetadata}) => {
const link = metadata.embeds?.[0].url;
return {
expandedLink: database.get(MM_TABLES.SERVER.SYSTEM).query(
Q.where('id', SYSTEM_IDENTIFIERS.EXPANDED_LINKS),
).observe().pipe(
switchMap((values: SystemModel[]) => (
(link && values.length) ? of$((values[0].value as Record<string, string>)[link]) : of$(undefined)),
),
),
link: of$(link),
};
});
export default withDatabase(withExpandedLink(React.memo(ImagePreview)));
export default React.memo(ImagePreview);

View File

@@ -0,0 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import ImagePreview from './image_preview';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
const enhance = withObservables(['metadata'], ({database, metadata}: WithDatabaseArgs & {metadata: PostMetadata}) => {
const link = metadata.embeds?.[0].url;
return {
expandedLink: database.get(MM_TABLES.SERVER.SYSTEM).query(
Q.where('id', SYSTEM_IDENTIFIERS.EXPANDED_LINKS),
).observe().pipe(
switchMap((values: SystemModel[]) => (
(link && values.length) ? of$((values[0].value as Record<string, string>)[link]) : of$(undefined)),
),
),
link: of$(link),
};
});
export default withDatabase(enhance(ImagePreview));

View File

@@ -5,7 +5,6 @@ import React from 'react';
import {useIntl} from 'react-intl';
import {Alert, Text, View} from 'react-native';
import FastImage from 'react-native-fast-image';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {tryOpenURL} from '@utils/url';
@@ -69,14 +68,13 @@ const AttachmentAuthor = ({icon, link, name, theme}: Props) => {
/>
}
{Boolean(name) &&
<TouchableOpacity onPress={openLink}>
<Text
key='author_name'
style={[style.name, Boolean(link) && style.link]}
>
{name}
</Text>
</TouchableOpacity>
<Text
key='author_name'
onPress={openLink}
style={[style.name, Boolean(link) && style.link]}
>
{name}
</Text>
}
</View>
);

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React, {useCallback, useRef, useState} from 'react';
import {View} from 'react-native';
import {TapGestureHandler} from 'react-native-gesture-handler';
import {TouchableWithoutFeedback, View} from 'react-native';
import Animated from 'react-native-reanimated';
import FileIcon from '@components/post_list/post/body/files/file_icon';
@@ -98,7 +97,7 @@ const AttachmentImage = ({imageUrl, imageMetadata, layoutWidth, location, postId
return (
<GalleryInit galleryIdentifier={galleryIdentifier}>
<Animated.View style={[styles, style.container, {width}]}>
<TapGestureHandler onGestureEvent={onGestureEvent}>
<TouchableWithoutFeedback onPress={onGestureEvent}>
<Animated.View testID={`attachmentImage-${fileId}`}>
<ProgressiveImage
forwardRef={ref}
@@ -110,7 +109,7 @@ const AttachmentImage = ({imageUrl, imageMetadata, layoutWidth, location, postId
style={{height, width}}
/>
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
</Animated.View>
</GalleryInit>
);

View File

@@ -4,7 +4,6 @@
import React from 'react';
import {useIntl} from 'react-intl';
import {Alert, Text, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Markdown from '@components/markdown';
import {makeStyleSheetFromTheme} from '@utils/theme';
@@ -60,11 +59,12 @@ const AttachmentTitle = ({link, theme, value}: Props) => {
let title;
if (link) {
title = (
<TouchableOpacity onPress={openLink}>
<Text style={[style.title, Boolean(link) && style.link]}>
{value}
</Text>
</TouchableOpacity>
<Text
onPress={openLink}
style={[style.title, Boolean(link) && style.link]}
>
{value}
</Text>
);
} else {
title = (

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {Preferences} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {getPreferenceAsBool} from '@helpers/api/preference';
import Opengraph from './opengraph';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
const enhance = withObservables(
['removeLinkPreview'],
({database, removeLinkPreview}: WithDatabaseArgs & {removeLinkPreview: boolean}) => {
if (removeLinkPreview) {
return {showLinkPreviews: of$(false)};
}
const showLinkPreviews = database.get(MM_TABLES.SERVER.PREFERENCE).query(
Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS),
Q.where('name', Preferences.LINK_PREVIEW_DISPLAY),
).observe().pipe(
switchMap(
(preferences: PreferenceModel[]) => database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((config: SystemModel) => {
const cfg: ClientConfig = config.value;
const previewsEnabled = getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true);
return of$(previewsEnabled && cfg.EnableLinkPreviews === 'true');
}),
),
),
);
return {showLinkPreviews};
},
);
export default withDatabase(enhance(Opengraph));

View File

@@ -1,28 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React from 'react';
import {useIntl} from 'react-intl';
import {Alert, Text, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {Alert, Text, TouchableOpacity, View} from 'react-native';
import {Preferences} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {tryOpenURL} from '@utils/url';
import OpengraphImage from './opengraph_image';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
type OpengraphProps = {
isReplyPost: boolean;
layoutWidth?: number;
@@ -176,29 +163,4 @@ const Opengraph = ({isReplyPost, layoutWidth, location, metadata, postId, showLi
);
};
const enhanced = withObservables(
['removeLinkPreview'], ({database, removeLinkPreview}: WithDatabaseArgs & {removeLinkPreview: boolean}) => {
if (removeLinkPreview) {
return {showLinkPreviews: of$(false)};
}
const showLinkPreviews = database.get(MM_TABLES.SERVER.PREFERENCE).query(
Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS),
Q.where('name', Preferences.LINK_PREVIEW_DISPLAY),
).observe().pipe(
switchMap(
(preferences: PreferenceModel[]) => database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((config: SystemModel) => {
const cfg: ClientConfig = config.value;
const previewsEnabled = getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true);
return of$(previewsEnabled && cfg.EnableLinkPreviews === 'true');
}),
),
),
);
return {showLinkPreviews};
});
export default withDatabase(enhanced(React.memo(Opengraph)));
export default React.memo(Opengraph);

View File

@@ -2,9 +2,8 @@
// See LICENSE.txt for license information.
import React, {useMemo, useRef} from 'react';
import {useWindowDimensions} from 'react-native';
import {TouchableWithoutFeedback, useWindowDimensions} from 'react-native';
import FastImage, {Source} from 'react-native-fast-image';
import {TapGestureHandler} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import {Device as DeviceConstant, View as ViewConstants} from '@constants';
@@ -118,7 +117,7 @@ const OpengraphImage = ({isReplyPost, layoutWidth, location, metadata, openGraph
return (
<GalleryInit galleryIdentifier={galleryIdentifier}>
<Animated.View style={[styles, style.imageContainer, dimensionsStyle]}>
<TapGestureHandler onGestureEvent={onGestureEvent}>
<TouchableWithoutFeedback onPress={onGestureEvent}>
<Animated.View testID={`OpenGraphImage-${fileId}`}>
<FastImage
style={[style.image, dimensionsStyle]}
@@ -130,7 +129,7 @@ const OpengraphImage = ({isReplyPost, layoutWidth, location, metadata, openGraph
nativeID={`OpenGraphImage-${fileId}`}
/>
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
</Animated.View>
</GalleryInit>
);

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import YouTube from './youtube';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
const enhance = withObservables([], ({database}: WithDatabaseArgs) => ({
googleDeveloperKey: database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap(({value}: {value: ClientConfig}) => {
return of$(value.GoogleDeveloperKey);
}),
),
}));
export default withDatabase(enhance(YouTube));

View File

@@ -1,26 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {Alert, Image, Platform, StatusBar, StyleSheet, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Alert, Image, Platform, StatusBar, StyleSheet, TouchableOpacity, View} from 'react-native';
import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import ProgressiveImage from '@components/progressive_image';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {useIsTablet} from '@hooks/device';
import {emptyFunction} from '@utils/general';
import {calculateDimensions, getViewPortWidth} from '@utils/images';
import {getYouTubeVideoId, tryOpenURL} from '@utils/url';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
type YouTubeProps = {
googleDeveloperKey?: string;
isReplyPost: boolean;
@@ -178,12 +169,4 @@ const YouTube = ({googleDeveloperKey, isReplyPost, layoutWidth, metadata}: YouTu
);
};
const withGoogleKey = withObservables([], ({database}: WithDatabaseArgs) => ({
googleDeveloperKey: database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap(({value}: {value: ClientConfig}) => {
return of$(value.GoogleDeveloperKey);
}),
),
}));
export default withDatabase(withGoogleKey(React.memo(YouTube)));
export default React.memo(YouTube);

View File

@@ -3,8 +3,7 @@
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {StyleSheet, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {StyleSheet, TouchableOpacity, View} from 'react-native';
import {removePost} from '@actions/local/post';
import CompassIcon from '@components/compass_icon';

View File

@@ -5,9 +5,8 @@ import {ClientResponse, ProgressPromise} from '@mattermost/react-native-network-
import * as FileSystem from 'expo-file-system';
import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {Platform, StatusBar, StatusBarStyle, StyleSheet, View} from 'react-native';
import {Platform, StatusBar, StatusBarStyle, StyleSheet, TouchableOpacity, View} from 'react-native';
import FileViewer from 'react-native-file-viewer';
import {TouchableOpacity} from 'react-native-gesture-handler';
import tinyColor from 'tinycolor2';
import ProgressBar from '@components/progress_bar';

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
import {TapGestureHandler} from 'react-native-gesture-handler';
import {View, TouchableWithoutFeedback} from 'react-native';
import Animated from 'react-native-reanimated';
import TouchableWithFeedback from '@components/touchable_with_feedback';
@@ -71,10 +70,7 @@ const File = ({
if (isVideo(file) && publicLinkEnabled) {
return (
<TapGestureHandler
onGestureEvent={onGestureEvent}
shouldCancelWhenOutside={true}
>
<TouchableWithoutFeedback onPress={onGestureEvent}>
<Animated.View style={[styles]}>
<VideoFile
file={file}
@@ -93,16 +89,13 @@ const File = ({
/>
}
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
);
}
if (isImage(file)) {
return (
<TapGestureHandler
onGestureEvent={onGestureEvent}
shouldCancelWhenOutside={true}
>
<TouchableWithoutFeedback onPress={onGestureEvent}>
<Animated.View style={[styles]}>
<ImageFile
file={file}
@@ -119,7 +112,7 @@ const File = ({
/>
}
</Animated.View>
</TapGestureHandler>
</TouchableWithoutFeedback>
);
}

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Text, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Text, TouchableOpacity, View} from 'react-native';
import {getFormattedFileSize} from '@utils/file';
import {makeStyleSheetFromTheme} from '@utils/theme';

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import {View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {TouchableOpacity, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import CompassIcon from '@components/compass_icon';

View File

@@ -2,9 +2,8 @@
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {View} from 'react-native';
import {TouchableOpacity, View} from 'react-native';
import AnimatedNumbers from 'react-native-animated-numbers';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Emoji from '@components/emoji';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';

View File

@@ -3,8 +3,7 @@
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {TouchableOpacity, View} from 'react-native';
import {addReaction, removeReaction} from '@actions/remote/reactions';
import CompassIcon from '@components/compass_icon';

View File

@@ -3,8 +3,7 @@
import React, {useCallback, useRef} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, Text, useWindowDimensions, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Keyboard, Text, TouchableOpacity, useWindowDimensions, View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';

View File

@@ -2,8 +2,7 @@
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {Text, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Text, TouchableOpacity, View} from 'react-native';
import {fetchAndSwitchToThread} from '@actions/remote/thread';
import CompassIcon from '@components/compass_icon';

View File

@@ -3,8 +3,7 @@
import React, {ReactNode, useMemo, useRef} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, Platform, StyleProp, View, ViewStyle} from 'react-native';
import {TouchableHighlight} from 'react-native-gesture-handler';
import {Keyboard, Platform, StyleProp, View, ViewStyle, TouchableHighlight} from 'react-native';
import {showPermalink} from '@actions/local/permalink';
import {removePost} from '@actions/local/post';

View File

@@ -3,33 +3,23 @@
exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
<View
style={
Array [
Object {
"opacity": 1,
}
}
>
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
>
<Text>
<Text
style={
Array [
@@ -52,55 +42,45 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] =
@username
</Text>
</Text>
</View>
</RNGestureHandlerButton>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
updated the channel display name from: old displayname to: new displayname
</Text>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
updated the channel display name from: old displayname to: new displayname
</Text>
</Text>
</View>
</View>
`;
exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
<View
style={
Array [
Object {
"opacity": 1,
}
}
>
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
>
<Text>
<Text
style={
Array [
@@ -123,22 +103,22 @@ exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
@username
</Text>
</Text>
</View>
</RNGestureHandlerButton>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
updated the channel header from: old header to: new header
</Text>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
updated the channel header from: old header to: new header
</Text>
</Text>
</View>
</View>
`;
@@ -161,33 +141,23 @@ exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = `
exports[`renderSystemMessage uses renderer for Guest added and join to channel 1`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
<View
style={
Array [
Object {
"opacity": 1,
}
}
>
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
>
<Text>
<Text
style={
Array [
@@ -210,55 +180,45 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 1
@username
</Text>
</Text>
</View>
</RNGestureHandlerButton>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
joined the channel as a guest.
</Text>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
joined the channel as a guest.
</Text>
</Text>
</View>
</View>
`;
exports[`renderSystemMessage uses renderer for Guest added and join to channel 2`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
<View
style={
Array [
Object {
"opacity": 1,
}
}
>
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
>
<Text>
<Text
style={
Array [
@@ -281,40 +241,20 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 2
@other.user
</Text>
</Text>
</View>
</RNGestureHandlerButton>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
added to the channel as a guest by
</Text>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
Object {
"opacity": 1,
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
}
>
testID="markdown_text"
>
added to the channel as a guest by
</Text>
<Text
style={
Array [
@@ -337,70 +277,70 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 2
@username.
</Text>
</Text>
</View>
</RNGestureHandlerButton>
</Text>
</View>
</View>
`;
exports[`renderSystemMessage uses renderer for OLD archived channel without a username 1`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<Text
<View
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
testID="markdown_text"
>
archived the channel
</Text>
<Text>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
archived the channel
</Text>
</Text>
</View>
</View>
`;
exports[`renderSystemMessage uses renderer for archived channel 1`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
<View
style={
Array [
Object {
"opacity": 1,
}
}
>
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
>
<Text>
<Text
style={
Array [
@@ -423,55 +363,45 @@ exports[`renderSystemMessage uses renderer for archived channel 1`] = `
@username
</Text>
</Text>
</View>
</RNGestureHandlerButton>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
archived the channel
</Text>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
archived the channel
</Text>
</Text>
</View>
</View>
`;
exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
<View
style={
Array [
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
Object {
"marginBottom": 5,
}
}
>
<RNGestureHandlerButton
collapsable={false}
exclusive={true}
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
>
<View
accessible={true}
collapsable={false}
style={
<View
style={
Array [
Object {
"opacity": 1,
}
}
>
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
},
]
}
>
<Text>
<Text
style={
Array [
@@ -494,21 +424,21 @@ exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
@username
</Text>
</Text>
</View>
</RNGestureHandlerButton>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
unarchived the channel
</Text>
<Text
style={
Object {
"color": "rgba(63,67,80,0.6)",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
}
}
testID="markdown_text"
>
unarchived the channel
</Text>
</Text>
</View>
</View>
`;

View File

@@ -3,7 +3,7 @@
import React from 'react';
import {IntlShape, useIntl} from 'react-intl';
import {StyleProp, Text, TextStyle, ViewStyle} from 'react-native';
import {StyleProp, Text, TextStyle, View, ViewStyle} from 'react-native';
import Markdown from '@components/markdown';
import {Post} from '@constants';
@@ -25,6 +25,7 @@ type SystemMessageProps = {
type RenderersProps = SystemMessageProps & {
intl: IntlShape;
styles: {
containerStyle: StyleProp<ViewStyle>;
messageStyle: StyleProp<ViewStyle>;
textStyles: {
[key: string]: TextStyle;
@@ -45,6 +46,9 @@ type RenderMessageProps = RenderersProps & {
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
marginBottom: 5,
},
systemMessage: {
color: changeOpacity(theme.centerChannelColor, 0.6),
...typography('Body', 200, 'Regular'),
@@ -61,7 +65,7 @@ const renderUsername = (value = '') => {
};
const renderMessage = ({styles, intl, localeHolder, theme, values, skipMarkdown = false}: RenderMessageProps) => {
const {messageStyle, textStyles} = styles;
const {containerStyle, messageStyle, textStyles} = styles;
if (skipMarkdown) {
return (
@@ -72,13 +76,15 @@ const renderMessage = ({styles, intl, localeHolder, theme, values, skipMarkdown
}
return (
<Markdown
baseTextStyle={messageStyle}
disableGallery={true}
textStyles={textStyles}
value={intl.formatMessage(localeHolder, values)}
theme={theme}
/>
<View style={containerStyle}>
<Markdown
baseTextStyle={messageStyle}
disableGallery={true}
textStyles={textStyles}
value={intl.formatMessage(localeHolder, values)}
theme={theme}
/>
</View>
);
};
@@ -259,7 +265,7 @@ export const SystemMessage = ({post, author}: SystemMessageProps) => {
const theme = useTheme();
const style = getStyleSheet(theme);
const textStyles = getMarkdownTextStyles(theme);
const styles = {messageStyle: style.systemMessage, textStyles};
const styles = {messageStyle: style.systemMessage, textStyles, containerStyle: style.container};
const renderer = systemMessageRenderers[post.type];
if (!renderer) {
@@ -272,7 +278,6 @@ export const SystemMessage = ({post, author}: SystemMessageProps) => {
theme={theme}
/>
);
return null;
}
return renderer({post, author, styles, intl, theme});

View File

@@ -10,7 +10,6 @@ import {
} from 'react-native-reanimated';
import {useGallery} from '@context/gallery';
import {measureItem} from '@utils/gallery';
import type {GestureHandlerGestureEvent} from 'react-native-gesture-handler';
@@ -236,20 +235,13 @@ export function useGalleryItem(
gallery.registerItem(index, ref);
}, []);
const onGestureEvent = useAnimatedGestureHandler({
onFinish: (_evt, _ctx, isCanceledOrFailed) => {
if (isCanceledOrFailed) {
return;
}
const onGestureEvent = () => {
'worklet';
activeIndex.value = index;
activeIndex.value = index;
// measure the images
// width/height and position to animate from it to the full screen one
measureItem(ref, gallery.sharedValues);
runOnJS(onPress)(identifier, index);
},
});
runOnJS(onPress)(identifier, index);
};
return {
ref,

View File

@@ -6,10 +6,9 @@ import {NativeModules, useWindowDimensions, Platform} from 'react-native';
import {Navigation} from 'react-native-navigation';
import {Screens} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import {useGalleryControls} from '@hooks/gallery';
import {dismissModal} from '@screens/navigation';
import {dismissOverlay} from '@screens/navigation';
import {freezeOtherScreens} from '@utils/gallery';
import Footer from './footer';
@@ -26,7 +25,6 @@ type Props = {
const GalleryScreen = ({galleryIdentifier, hideActions, initialIndex, items}: Props) => {
const dim = useWindowDimensions();
const isTablet = useIsTablet();
const theme = useTheme();
const [localIndex, setLocalIndex] = useState(initialIndex);
const {setControlsHidden, headerStyles, footerStyles} = useGalleryControls();
const dimensions = useMemo(() => ({width: dim.width, height: dim.height}), [dim.width]);
@@ -55,16 +53,7 @@ const GalleryScreen = ({galleryIdentifier, hideActions, initialIndex, items}: Pr
}
freezeOtherScreens(false);
requestAnimationFrame(async () => {
dismissModal({
componentId: Screens.GALLERY,
layout: {
orientation: isTablet ? undefined : ['portrait'],
},
statusBar: {
visible: true,
backgroundColor: theme.sidebarBg,
},
});
dismissOverlay(Screens.GALLERY);
});
}, [isTablet]);

View File

@@ -609,6 +609,7 @@ export function showOverlay(name: string, passProps = {}, options = {}) {
Navigation.showOverlay({
component: {
id: name,
name,
passProps,
options: merge(defaultOptions, options),

View File

@@ -12,6 +12,7 @@ import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {MAX_ALLOWED_REACTIONS} from '@constants/emoji';
import {isMinimumServerVersion} from '@utils/helpers';
import {isSystemMessage} from '@utils/post';
import {getPostIdsForCombinedUserActivityPost} from '@utils/post_list';
import {hasPermissionForChannel, hasPermissionForPost} from '@utils/role';
import {isSystemAdmin} from '@utils/user';
@@ -62,7 +63,7 @@ const withPost = withObservables([], ({post, database}: {post: Post | PostModel}
let id: string | undefined;
let combinedPost: Observable<Post | undefined> = of$(undefined);
if (post.type === Post.POST_TYPES.COMBINED_USER_ACTIVITY && post.props?.system_post_ids) {
const systemPostIds = post.props.system_post_ids as string[];
const systemPostIds = getPostIdsForCombinedUserActivityPost(post.id);
id = systemPostIds?.pop();
combinedPost = of$(post as Post);
}

View File

@@ -7,7 +7,7 @@ import {Navigation, Options, OptionsLayout} from 'react-native-navigation';
import {measure} from 'react-native-reanimated';
import {Events, Screens} from '@constants';
import {showModalOverCurrentContext} from '@screens/navigation';
import {showOverlay} from '@screens/navigation';
import {isImage, isVideo} from '@utils/file';
import {generateId} from '@utils/general';
@@ -154,7 +154,7 @@ export function openGalleryAtIndex(galleryIdentifier: string, initialIndex: numb
Navigation.setDefaultOptions({layout});
NativeModules.MattermostManaged.unlockOrientation();
}
showModalOverCurrentContext(Screens.GALLERY, props, options);
showOverlay(Screens.GALLERY, props, options);
setTimeout(() => {
freezeOtherScreens(true);

View File

@@ -34,7 +34,7 @@ export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
lineHeight: 25,
},
heading1Text: {
paddingBottom: 8,
paddingVertical: 8,
},
heading2: {
fontFamily: 'OpenSans-Bold',
@@ -42,7 +42,7 @@ export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
lineHeight: 25,
},
heading2Text: {
paddingBottom: 8,
paddingVertical: 6,
},
heading3: {
fontFamily: 'OpenSans-Bold',
@@ -50,7 +50,7 @@ export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
lineHeight: 25,
},
heading3Text: {
paddingBottom: 8,
paddingVertical: 6,
},
heading4: {
fontFamily: 'OpenSans-Bold',
@@ -58,7 +58,7 @@ export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
lineHeight: 25,
},
heading4Text: {
paddingBottom: 8,
paddingVertical: 5,
},
heading5: {
fontFamily: 'OpenSans-Bold',
@@ -66,7 +66,7 @@ export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
lineHeight: 25,
},
heading5Text: {
paddingBottom: 8,
paddingVertical: 5,
},
heading6: {
fontFamily: 'OpenSans-Bold',
@@ -74,7 +74,7 @@ export const getMarkdownTextStyles = makeStyleSheetFromTheme((theme: Theme) => {
lineHeight: 25,
},
heading6Text: {
paddingBottom: 8,
paddingVertical: 4,
},
code: {
alignSelf: 'center',