forked from Ivasoft/mattermost-mobile
[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:
@@ -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);
|
||||
52
app/components/markdown/at_mention/index.ts
Normal file
52
app/components/markdown/at_mention/index.ts
Normal 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));
|
||||
@@ -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;
|
||||
40
app/components/markdown/channel_mention/index.ts
Normal file
40
app/components/markdown/channel_mention/index.ts
Normal 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));
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
35
app/components/markdown/markdown_link/index.ts
Normal file
35
app/components/markdown/markdown_link/index.ts
Normal 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));
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user