Merge branch 'develop' into FIX-Links-do-not-work-if-protocol-is-not-set-in-URL-prefix
This commit is contained in:
commit
5a2a0e7c0f
|
@ -3,7 +3,13 @@ import { Provider } from 'react-redux';
|
|||
|
||||
import { themes } from '../app/lib/constants';
|
||||
import MessageContext from '../app/containers/message/Context';
|
||||
import { selectServerRequest } from '../app/actions/server';
|
||||
import { mockedStore as store } from '../app/reducers/mockedStore';
|
||||
import { setUser } from '../app/actions/login';
|
||||
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
store.dispatch(selectServerRequest(baseUrl));
|
||||
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
|
||||
|
||||
export const decorators = [
|
||||
Story => (
|
||||
|
@ -15,7 +21,7 @@ export const decorators = [
|
|||
username: 'diego.mello',
|
||||
token: 'abc'
|
||||
},
|
||||
baseUrl: 'https://open.rocket.chat',
|
||||
baseUrl,
|
||||
onPress: () => {},
|
||||
onLongPress: () => {},
|
||||
reactionInit: () => {},
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Chip Chip Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Rocket.Cat\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||
exports[`Storyshots Chip Chip Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Rocket.Cat\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||
|
||||
exports[`Storyshots Chip Chip With Short Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Short\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||
exports[`Storyshots Chip Chip With Short Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Short\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||
|
||||
exports[`Storyshots Chip Chip Without Avatar 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||
|
||||
exports[`Storyshots Chip Chip Without Avatar And Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar and Icon\\"]}]}]}]}]}"`;
|
||||
|
||||
exports[`Storyshots Chip Chip Without Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Icon\\"]}]}]}]}]}"`;
|
||||
exports[`Storyshots Chip Chip Without Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Icon\\"]}]}]}]}]}"`;
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "4.32.0"
|
||||
versionName "4.33.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
if (!isFoss) {
|
||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||
|
|
|
@ -18,10 +18,10 @@ const HANDLE_HEIGHT = isIOS ? 40 : 56;
|
|||
const MIN_SNAP_HEIGHT = 16;
|
||||
const CANCEL_HEIGHT = 64;
|
||||
|
||||
const ANIMATION_DURATION = 250;
|
||||
export const ACTION_SHEET_ANIMATION_DURATION = 250;
|
||||
|
||||
const ANIMATION_CONFIG = {
|
||||
duration: ANIMATION_DURATION,
|
||||
duration: ACTION_SHEET_ANIMATION_DURATION,
|
||||
// https://easings.net/#easeInOutCubic
|
||||
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
|
||||
};
|
||||
|
@ -140,7 +140,7 @@ const ActionSheet = React.memo(
|
|||
style={{ ...styles.container, ...bottomSheet }}
|
||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||
onChange={index => index === -1 && onClose()}
|
||||
// We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker
|
||||
// We need this to allow horizontal swipe gesture inside the bottom sheet like in reaction picker
|
||||
enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
|
||||
{...androidTablet}
|
||||
>
|
||||
|
|
|
@ -52,7 +52,11 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I
|
|||
/>
|
||||
);
|
||||
}
|
||||
return <BottomSheetView style={styles.contentContainer}>{children}</BottomSheetView>;
|
||||
return (
|
||||
<BottomSheetView testID='action-sheet' style={styles.contentContainer}>
|
||||
{children}
|
||||
</BottomSheetView>
|
||||
);
|
||||
});
|
||||
|
||||
export default BottomSheetContent;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './Provider';
|
||||
export * from './ActionSheet';
|
||||
|
|
|
@ -43,9 +43,7 @@ const Avatar = React.memo(
|
|||
|
||||
let image;
|
||||
if (emoji) {
|
||||
image = (
|
||||
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
|
||||
);
|
||||
image = <Emoji getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />;
|
||||
} else {
|
||||
let uri = avatar;
|
||||
if (!isStatic) {
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import React from 'react';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { StyleProp } from 'react-native';
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image';
|
||||
|
||||
import { ICustomEmoji } from '../../definitions/IEmoji';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { ICustomEmoji } from '../../definitions';
|
||||
|
||||
interface ICustomEmojiProps {
|
||||
emoji: ICustomEmoji;
|
||||
style: StyleProp<ImageStyle>;
|
||||
}
|
||||
|
||||
const CustomEmoji = React.memo(
|
||||
({ baseUrl, emoji, style }: ICustomEmoji) => (
|
||||
<FastImage
|
||||
style={style}
|
||||
source={{
|
||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
||||
priority: FastImage.priority.high
|
||||
}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
),
|
||||
(prevProps, nextProps) => {
|
||||
const prevEmoji = prevProps.emoji.content || prevProps.emoji.name;
|
||||
const nextEmoji = nextProps.emoji.content || nextProps.emoji.name;
|
||||
return prevEmoji === nextEmoji;
|
||||
}
|
||||
({ emoji, style }: ICustomEmojiProps) => {
|
||||
const baseUrl = useAppSelector(state => state.share.server.server || state.server.server);
|
||||
return (
|
||||
<FastImage
|
||||
style={style}
|
||||
source={{
|
||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.name)}.${emoji.extension}`,
|
||||
priority: FastImage.priority.high
|
||||
}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
);
|
||||
},
|
||||
() => true
|
||||
);
|
||||
|
||||
export default CustomEmoji;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import styles from './styles';
|
||||
import CustomEmoji from './CustomEmoji';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
|
||||
interface IEmojiProps {
|
||||
emoji: IEmoji;
|
||||
}
|
||||
|
||||
export const Emoji = ({ emoji }: IEmojiProps): React.ReactElement => {
|
||||
if (typeof emoji === 'string') {
|
||||
return <Text style={styles.categoryEmoji}>{shortnameToUnicode(`:${emoji}:`)}</Text>;
|
||||
}
|
||||
return <CustomEmoji style={styles.customCategoryEmoji} emoji={emoji} />;
|
||||
};
|
|
@ -1,75 +1,45 @@
|
|||
import React from 'react';
|
||||
import { FlatList, Text, TouchableOpacity } from 'react-native';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import styles from './styles';
|
||||
import CustomEmoji from './CustomEmoji';
|
||||
import { EMOJI_BUTTON_SIZE } from './styles';
|
||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
import { PressableEmoji } from './PressableEmoji';
|
||||
|
||||
const EMOJI_SIZE = 50;
|
||||
interface IEmojiCategoryProps {
|
||||
emojis: IEmoji[];
|
||||
onEmojiSelected: (emoji: IEmoji) => void;
|
||||
tabLabel?: string; // needed for react-native-scrollable-tab-view only
|
||||
}
|
||||
|
||||
const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
|
||||
if (emoji && emoji.isCustom) {
|
||||
return (
|
||||
<CustomEmoji
|
||||
style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]}
|
||||
emoji={emoji}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
);
|
||||
const EmojiCategory = ({ onEmojiSelected, emojis }: IEmojiCategoryProps): React.ReactElement | null => {
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
const numColumns = Math.trunc(width / EMOJI_BUTTON_SIZE);
|
||||
const marginHorizontal = (width % EMOJI_BUTTON_SIZE) / 2;
|
||||
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
|
||||
{shortnameToUnicode(`:${emoji}:`)}
|
||||
</Text>
|
||||
<FlatList
|
||||
// needed to update the numColumns when the width changes
|
||||
key={`emoji-category-${width}`}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||
data={emojis}
|
||||
renderItem={renderItem}
|
||||
numColumns={numColumns}
|
||||
initialNumToRender={45}
|
||||
removeClippedSubviews
|
||||
contentContainerStyle={{ marginHorizontal }}
|
||||
{...scrollPersistTaps}
|
||||
keyboardDismissMode={'none'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
class EmojiCategory extends React.Component<IEmojiCategory> {
|
||||
renderItem(emoji: IEmoji) {
|
||||
const { baseUrl, onEmojiSelected } = this.props;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
// @ts-ignore
|
||||
key={emoji && emoji.isCustom ? emoji.content : emoji}
|
||||
onPress={() => onEmojiSelected(emoji)}
|
||||
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}
|
||||
>
|
||||
{renderEmoji(emoji, EMOJI_SIZE, baseUrl)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { emojis, width } = this.props;
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const numColumns = Math.trunc(width / EMOJI_SIZE);
|
||||
const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={{ marginHorizontal }}
|
||||
// rerender FlatList in case of width changes
|
||||
key={`emoji-category-${width}`}
|
||||
// @ts-ignore
|
||||
keyExtractor={item => (item && item.isCustom && item.content) || item}
|
||||
data={emojis}
|
||||
extraData={this.props}
|
||||
renderItem={({ item }) => this.renderItem(item)}
|
||||
numColumns={numColumns}
|
||||
initialNumToRender={45}
|
||||
removeClippedSubviews
|
||||
{...scrollPersistTaps}
|
||||
keyboardDismissMode={'none'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmojiCategory;
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useState } from 'react';
|
||||
import { StyleSheet, TextInputProps } from 'react-native';
|
||||
|
||||
import { FormTextInput } from '../TextInput/FormTextInput';
|
||||
import { useTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
input: {
|
||||
height: 32,
|
||||
borderWidth: 0,
|
||||
paddingVertical: 0,
|
||||
borderRadius: 4
|
||||
},
|
||||
textInputContainer: {
|
||||
marginBottom: 0
|
||||
}
|
||||
});
|
||||
|
||||
interface IEmojiSearchBarProps {
|
||||
onBlur?: TextInputProps['onBlur'];
|
||||
onChangeText: TextInputProps['onChangeText'];
|
||||
bottomSheet?: boolean;
|
||||
}
|
||||
|
||||
export const EmojiSearch = ({ onBlur, onChangeText, bottomSheet }: IEmojiSearchBarProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const handleTextChange = (text: string) => {
|
||||
setSearchText(text);
|
||||
if (onChangeText) {
|
||||
onChangeText(text);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormTextInput
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
autoComplete='off'
|
||||
returnKeyType='search'
|
||||
textContentType='none'
|
||||
blurOnSubmit
|
||||
placeholder={I18n.t('Search_emoji')}
|
||||
placeholderTextColor={colors.auxiliaryText}
|
||||
underlineColorAndroid='transparent'
|
||||
onChangeText={handleTextChange}
|
||||
inputStyle={[styles.input, { backgroundColor: colors.textInputSecondaryBackground }]}
|
||||
containerStyle={styles.textInputContainer}
|
||||
value={searchText}
|
||||
onClearInput={() => handleTextChange('')}
|
||||
onBlur={onBlur}
|
||||
iconRight={'search'}
|
||||
testID='emoji-searchbar-input'
|
||||
bottomSheet={bottomSheet && isIOS}
|
||||
autoFocus={!bottomSheet} // focus on input when not in reaction picker
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { View, Pressable } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import styles from './styles';
|
||||
import { IFooterProps } from './interfaces';
|
||||
|
||||
const BUTTON_HIT_SLOP = { top: 15, right: 15, bottom: 15, left: 15 };
|
||||
|
||||
const Footer = ({ onSearchPressed, onBackspacePressed }: IFooterProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View style={[styles.footerContainer, { borderTopColor: colors.borderColor }]}>
|
||||
<Pressable
|
||||
onPress={onSearchPressed}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
|
||||
testID='emoji-picker-search'
|
||||
>
|
||||
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='search' />
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={onBackspacePressed}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
|
||||
testID='emoji-picker-backspace'
|
||||
>
|
||||
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='backspace' />
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Pressable } from 'react-native';
|
||||
|
||||
import styles, { EMOJI_BUTTON_SIZE } from './styles';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
import { useTheme } from '../../theme';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { Emoji } from './Emoji';
|
||||
|
||||
export const PressableEmoji = ({ emoji, onPress }: { emoji: IEmoji; onPress: (emoji: IEmoji) => void }): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<Pressable
|
||||
key={typeof emoji === 'string' ? emoji : emoji.name}
|
||||
onPress={() => onPress(emoji)}
|
||||
testID={`emoji-${typeof emoji === 'string' ? emoji : emoji.name}`}
|
||||
android_ripple={{ color: colors.bannerBackground, borderless: true, radius: EMOJI_BUTTON_SIZE / 2 }}
|
||||
style={({ pressed }: { pressed: boolean }) => [
|
||||
styles.emojiButton,
|
||||
{
|
||||
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Emoji emoji={emoji} />
|
||||
</Pressable>
|
||||
);
|
||||
};
|
|
@ -1,56 +1,42 @@
|
|||
import React from 'react';
|
||||
import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native';
|
||||
import { Pressable, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { useTheme } from '../../theme';
|
||||
import { ITabBarProps } from './interfaces';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
|
||||
interface ITabBarProps {
|
||||
goToPage?: (page: number) => void;
|
||||
activeTab?: number;
|
||||
tabs?: string[];
|
||||
tabEmojiStyle: StyleProp<TextStyle>;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
const TabBar = ({ activeTab, tabs, goToPage }: ITabBarProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
export default class TabBar extends React.Component<ITabBarProps> {
|
||||
shouldComponentUpdate(nextProps: ITabBarProps) {
|
||||
const { activeTab, theme } = this.props;
|
||||
if (nextProps.activeTab !== activeTab) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<View style={styles.tabsContainer}>
|
||||
{tabs?.map((tab, i) => (
|
||||
<Pressable
|
||||
key={tab}
|
||||
onPress={() => goToPage?.(i)}
|
||||
testID={`emoji-picker-tab-${tab}`}
|
||||
android_ripple={{ color: colors.bannerBackground }}
|
||||
style={({ pressed }: { pressed: boolean }) => [
|
||||
styles.tab,
|
||||
{
|
||||
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CustomIcon name={tab} size={24} color={activeTab === i ? colors.tintColor : colors.auxiliaryTintColor} />
|
||||
<View
|
||||
style={
|
||||
activeTab === i
|
||||
? [styles.activeTabLine, { backgroundColor: colors.tintColor }]
|
||||
: [styles.tabLine, { backgroundColor: colors.borderColor }]
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabs, goToPage, tabEmojiStyle, activeTab, theme } = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.tabsContainer}>
|
||||
{tabs?.map((tab, i) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
key={tab}
|
||||
onPress={() => {
|
||||
if (goToPage) {
|
||||
goToPage(i);
|
||||
}
|
||||
}}
|
||||
style={styles.tab}
|
||||
testID={`reaction-picker-${tab}`}
|
||||
>
|
||||
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
||||
{activeTab === i ? (
|
||||
<View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} />
|
||||
) : (
|
||||
<View style={styles.tabLine} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default TabBar;
|
||||
|
|
|
@ -1,155 +1,44 @@
|
|||
import React, { Component } from 'react';
|
||||
import { StyleProp, TextStyle, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import { dequal } from 'dequal';
|
||||
import { connect } from 'react-redux';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { ImageStyle } from 'react-native-fast-image';
|
||||
|
||||
import TabBar from './TabBar';
|
||||
import EmojiCategory from './EmojiCategory';
|
||||
import Footer from './Footer';
|
||||
import styles from './styles';
|
||||
import categories from './categories';
|
||||
import database from '../../lib/database';
|
||||
import { emojisByCategory } from './emojis';
|
||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import log from '../../lib/methods/helpers/log';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
import { categories, emojisByCategory } from '../../lib/constants';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IEmoji, ICustomEmojis } from '../../definitions';
|
||||
import { useAppSelector, useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||
import { addFrequentlyUsed } from '../../lib/methods';
|
||||
import { IEmojiPickerProps, EventTypes } from './interfaces';
|
||||
|
||||
interface IEmojiPickerProps {
|
||||
isMessageContainsOnlyEmoji?: boolean;
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
baseUrl: string;
|
||||
customEmojis: ICustomEmojis;
|
||||
style?: StyleProp<ImageStyle>;
|
||||
theme: TSupportedThemes;
|
||||
onEmojiSelected: (emoji: string, shortname?: string) => void;
|
||||
tabEmojiStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
const EmojiPicker = ({
|
||||
onItemClicked,
|
||||
isEmojiKeyboard = false,
|
||||
searching = false,
|
||||
searchedEmojis = []
|
||||
}: IEmojiPickerProps): React.ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||
|
||||
interface IEmojiPickerState {
|
||||
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
|
||||
customEmojis: any;
|
||||
show: boolean;
|
||||
width: number | null;
|
||||
}
|
||||
const allCustomEmojis: ICustomEmojis = useAppSelector(
|
||||
state => state.customEmojis,
|
||||
() => true
|
||||
);
|
||||
const customEmojis = Object.keys(allCustomEmojis)
|
||||
.filter(item => item === allCustomEmojis[item].name)
|
||||
.map(item => ({
|
||||
name: allCustomEmojis[item].name,
|
||||
extension: allCustomEmojis[item].extension
|
||||
}));
|
||||
|
||||
class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||
constructor(props: IEmojiPickerProps) {
|
||||
super(props);
|
||||
const customEmojis = Object.keys(props.customEmojis)
|
||||
.filter(item => item === props.customEmojis[item].name)
|
||||
.map(item => ({
|
||||
content: props.customEmojis[item].name,
|
||||
extension: props.customEmojis[item].extension,
|
||||
isCustom: true
|
||||
}));
|
||||
this.state = {
|
||||
frequentlyUsed: [],
|
||||
customEmojis,
|
||||
show: false,
|
||||
width: null
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await this.updateFrequentlyUsed();
|
||||
this.setState({ show: true });
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: IEmojiPickerProps, nextState: IEmojiPickerState) {
|
||||
const { frequentlyUsed, show, width } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.show !== show) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.width !== width) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextState.frequentlyUsed, frequentlyUsed)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onEmojiSelected = (emoji: IEmoji) => {
|
||||
try {
|
||||
const { onEmojiSelected } = this.props;
|
||||
if (emoji.isCustom) {
|
||||
this._addFrequentlyUsed({
|
||||
content: emoji.content,
|
||||
extension: emoji.extension,
|
||||
isCustom: true
|
||||
});
|
||||
onEmojiSelected(`:${emoji.content}:`);
|
||||
} else {
|
||||
const content = emoji;
|
||||
this._addFrequentlyUsed({ content, isCustom: false });
|
||||
const shortname = `:${emoji}:`;
|
||||
onEmojiSelected(shortnameToUnicode(shortname), shortname);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
const handleEmojiSelect = (emoji: IEmoji) => {
|
||||
onItemClicked(EventTypes.EMOJI_PRESSED, emoji);
|
||||
addFrequentlyUsed(emoji);
|
||||
};
|
||||
|
||||
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
|
||||
const db = database.active;
|
||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
|
||||
try {
|
||||
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
await db.write(async () => {
|
||||
if (freqEmojiRecord) {
|
||||
await freqEmojiRecord.update(f => {
|
||||
if (f.count) {
|
||||
f.count += 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await freqEmojiCollection.create(f => {
|
||||
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
|
||||
Object.assign(f, emoji);
|
||||
f.count = 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
updateFrequentlyUsed = async () => {
|
||||
const db = database.active;
|
||||
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
|
||||
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
|
||||
const frequentlyUsed = frequentlyUsedOrdered.map(item => {
|
||||
if (item.isCustom) {
|
||||
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
|
||||
}
|
||||
return shortnameToUnicode(`${item.content}`);
|
||||
});
|
||||
this.setState({ frequentlyUsed });
|
||||
};
|
||||
|
||||
onLayout = ({
|
||||
nativeEvent: {
|
||||
layout: { width }
|
||||
}
|
||||
}: any) => this.setState({ width });
|
||||
|
||||
renderCategory(category: keyof typeof emojisByCategory, i: number, label: string) {
|
||||
const { frequentlyUsed, customEmojis, width } = this.state;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string) => {
|
||||
let emojis = [];
|
||||
if (i === 0) {
|
||||
emojis = frequentlyUsed;
|
||||
|
@ -158,49 +47,40 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
|||
} else {
|
||||
emojis = emojisByCategory[category];
|
||||
}
|
||||
return (
|
||||
<EmojiCategory
|
||||
emojis={emojis}
|
||||
onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)}
|
||||
style={styles.categoryContainer}
|
||||
width={width}
|
||||
baseUrl={baseUrl}
|
||||
tabLabel={label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { show, frequentlyUsed } = this.state;
|
||||
const { tabEmojiStyle, theme } = this.props;
|
||||
|
||||
if (!show) {
|
||||
if (!emojis.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View onLayout={this.onLayout} style={{ flex: 1 }}>
|
||||
return <EmojiCategory emojis={emojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} tabLabel={label} />;
|
||||
};
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.emojiPickerContainer}>
|
||||
{searching ? (
|
||||
<EmojiCategory emojis={searchedEmojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} />
|
||||
) : (
|
||||
<ScrollableTabView
|
||||
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
||||
renderTabBar={() => <TabBar />}
|
||||
contentProps={{
|
||||
keyboardShouldPersistTaps: 'always',
|
||||
keyboardDismissMode: 'none'
|
||||
}}
|
||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||
style={{ backgroundColor: colors.messageboxBackground }}
|
||||
>
|
||||
{categories.tabs.map((tab: any, i) =>
|
||||
i === 0 && frequentlyUsed.length === 0
|
||||
? null // when no frequentlyUsed don't show the tab
|
||||
: this.renderCategory(tab.category, i, tab.tabLabel)
|
||||
)}
|
||||
{categories.tabs.map((tab: any, i) => renderCategory(tab.category, i, tab.tabLabel))}
|
||||
</ScrollableTabView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
{isEmojiKeyboard && (
|
||||
<Footer
|
||||
onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
|
||||
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
customEmojis: state.customEmojis,
|
||||
baseUrl: state.share.server.server || state.server.server
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(EmojiPicker);
|
||||
export default EmojiPicker;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { TIconsName } from '../CustomIcon';
|
||||
import { IEmoji } from '../../definitions';
|
||||
|
||||
export enum EventTypes {
|
||||
EMOJI_PRESSED = 'emojiPressed',
|
||||
BACKSPACE_PRESSED = 'backspacePressed',
|
||||
SEARCH_PRESSED = 'searchPressed'
|
||||
}
|
||||
|
||||
export interface IEmojiPickerProps {
|
||||
onItemClicked: (event: EventTypes, emoji?: IEmoji) => void;
|
||||
isEmojiKeyboard?: boolean;
|
||||
searching?: boolean;
|
||||
searchedEmojis?: IEmoji[];
|
||||
}
|
||||
|
||||
export interface IFooterProps {
|
||||
onBackspacePressed: () => void;
|
||||
onSearchPressed: () => void;
|
||||
}
|
||||
|
||||
export interface ITabBarProps {
|
||||
goToPage?: (page: number) => void;
|
||||
activeTab?: number;
|
||||
tabs?: TIconsName[];
|
||||
}
|
|
@ -2,20 +2,23 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const EMOJI_BUTTON_SIZE = 44;
|
||||
export const EMOJI_SIZE = EMOJI_BUTTON_SIZE - 16;
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
tabsContainer: {
|
||||
height: 45,
|
||||
flexDirection: 'row',
|
||||
paddingTop: 5
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
tab: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 10
|
||||
paddingVertical: 10,
|
||||
width: EMOJI_BUTTON_SIZE
|
||||
},
|
||||
tabEmoji: {
|
||||
fontSize: 20,
|
||||
|
@ -33,7 +36,6 @@ export default StyleSheet.create({
|
|||
left: 0,
|
||||
right: 0,
|
||||
height: 2,
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
bottom: 0
|
||||
},
|
||||
categoryContainer: {
|
||||
|
@ -49,10 +51,34 @@ export default StyleSheet.create({
|
|||
},
|
||||
categoryEmoji: {
|
||||
...sharedStyles.textAlignCenter,
|
||||
textAlignVertical: 'center',
|
||||
fontSize: EMOJI_SIZE,
|
||||
backgroundColor: 'transparent',
|
||||
color: '#ffffff'
|
||||
},
|
||||
customCategoryEmoji: {
|
||||
margin: 8
|
||||
}
|
||||
height: EMOJI_SIZE,
|
||||
width: EMOJI_SIZE
|
||||
},
|
||||
emojiButton: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
width: EMOJI_BUTTON_SIZE
|
||||
},
|
||||
footerContainer: {
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: 1
|
||||
},
|
||||
footerButtonsContainer: {
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
width: EMOJI_BUTTON_SIZE,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
emojiPickerContainer: { flex: 1 }
|
||||
});
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { TSupportedThemes, useTheme } from '../../theme';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import { addFrequentlyUsed } from '../../lib/methods';
|
||||
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||
import database from '../../lib/database';
|
||||
import { useDimensions } from '../../dimensions';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
import { IEmoji, TAnyMessageModel } from '../../definitions';
|
||||
import Touch from '../Touch';
|
||||
|
||||
type TItem = TFrequentlyUsedEmojiModel | string;
|
||||
|
||||
export interface IHeader {
|
||||
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void;
|
||||
server: string;
|
||||
handleReaction: (emoji: IEmoji, message: TAnyMessageModel) => void;
|
||||
message: TAnyMessageModel;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
type TOnReaction = ({ emoji }: { emoji: TItem }) => void;
|
||||
type TOnReaction = ({ emoji }: { emoji: IEmoji }) => void;
|
||||
|
||||
interface THeaderItem {
|
||||
item: TItem;
|
||||
item: IEmoji;
|
||||
onReaction: TOnReaction;
|
||||
server: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
|
@ -64,30 +61,19 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const keyExtractor = (item: TItem) => {
|
||||
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
||||
return (emojiModel.id ? emojiModel.content : item) as string;
|
||||
};
|
||||
|
||||
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
|
||||
|
||||
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
|
||||
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
||||
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
|
||||
return (
|
||||
<Touch
|
||||
testID={`message-actions-emoji-${emoji}`}
|
||||
onPress={() => onReaction({ emoji: `:${emoji}:` })}
|
||||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
>
|
||||
{emojiModel?.isCustom ? (
|
||||
<CustomEmoji style={styles.customEmoji} emoji={emojiModel} baseUrl={server} />
|
||||
) : (
|
||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
|
||||
)}
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
const HeaderItem = ({ item, onReaction, theme }: THeaderItem) => (
|
||||
<Touch
|
||||
testID={`message-actions-emoji-${item}`}
|
||||
onPress={() => onReaction({ emoji: item })}
|
||||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
>
|
||||
{typeof item === 'string' ? (
|
||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${item}:`)}</Text>
|
||||
) : (
|
||||
<CustomEmoji style={styles.customEmoji} emoji={item} />
|
||||
)}
|
||||
</Touch>
|
||||
);
|
||||
|
||||
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
||||
<Touch
|
||||
|
@ -99,49 +85,35 @@ const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
|||
</Touch>
|
||||
);
|
||||
|
||||
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => {
|
||||
const [items, setItems] = useState<TItem[]>([]);
|
||||
const Header = React.memo(({ handleReaction, message, isMasterDetail }: IHeader) => {
|
||||
const { width, height } = useDimensions();
|
||||
const { theme } = useTheme();
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(true);
|
||||
const isLandscape = width > height;
|
||||
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
||||
const quantity = Math.trunc(size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1);
|
||||
|
||||
// TODO: create custom hook to re-render based on screen size
|
||||
const setEmojis = async () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
|
||||
|
||||
const isLandscape = width > height;
|
||||
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
||||
const quantity = size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1;
|
||||
|
||||
freqEmojis = freqEmojis.concat(DEFAULT_EMOJIS).slice(0, quantity);
|
||||
setItems(freqEmojis);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
const onReaction: TOnReaction = ({ emoji }) => {
|
||||
handleReaction(emoji, message);
|
||||
addFrequentlyUsed(emoji);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setEmojis();
|
||||
}, []);
|
||||
|
||||
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
|
||||
|
||||
const renderItem = ({ item }: { item: TItem }) => (
|
||||
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
|
||||
);
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <HeaderItem item={item} onReaction={onReaction} theme={theme} />;
|
||||
|
||||
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
|
||||
<FlatList
|
||||
data={items}
|
||||
data={frequentlyUsed.slice(0, quantity)}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={renderFooter}
|
||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||
keyExtractor={keyExtractor}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
scrollEnabled={false}
|
||||
horizontal
|
||||
|
|
|
@ -12,10 +12,10 @@ import { getMessageTranslation } from '../message/utils';
|
|||
import { LISTENER } from '../Toast';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
||||
import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATION } from '../ActionSheet';
|
||||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||
import events from '../../lib/methods/helpers/log/events';
|
||||
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { getPermalinkMessage } from '../../lib/methods';
|
||||
import { hasPermission } from '../../lib/methods/helpers';
|
||||
import { Services } from '../../lib/services';
|
||||
|
@ -26,7 +26,7 @@ export interface IMessageActionsProps {
|
|||
user: Pick<ILoggedUser, 'id'>;
|
||||
editInit: (message: TAnyMessageModel) => void;
|
||||
reactionInit: (message: TAnyMessageModel) => void;
|
||||
onReactionPress: (shortname: string, messageId: string) => void;
|
||||
onReactionPress: (shortname: IEmoji, messageId: string) => void;
|
||||
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
||||
isMasterDetail: boolean;
|
||||
isReadOnly: boolean;
|
||||
|
@ -37,7 +37,6 @@ export interface IMessageActionsProps {
|
|||
Message_AllowPinning?: boolean;
|
||||
Message_AllowStarring?: boolean;
|
||||
Message_Read_Receipt_Store_Users?: boolean;
|
||||
server: string;
|
||||
editMessagePermission?: string[];
|
||||
deleteMessagePermission?: string[];
|
||||
forceDeleteMessagePermission?: string[];
|
||||
|
@ -60,7 +59,6 @@ const MessageActions = React.memo(
|
|||
onReactionPress,
|
||||
replyInit,
|
||||
isReadOnly,
|
||||
server,
|
||||
Message_AllowDeleting,
|
||||
Message_AllowDeleting_BlockDeleteInMinutes,
|
||||
Message_AllowEditing,
|
||||
|
@ -261,12 +259,10 @@ const MessageActions = React.memo(
|
|||
const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
||||
if (shortname) {
|
||||
// TODO: evaluate unification with IEmoji
|
||||
onReactionPress(shortname as any, message.id);
|
||||
onReactionPress(shortname, message.id);
|
||||
} else {
|
||||
reactionInit(message);
|
||||
setTimeout(() => reactionInit(message), ACTION_SHEET_ANIMATION_DURATION);
|
||||
}
|
||||
// close actionSheet when click at header
|
||||
hideActionSheet();
|
||||
};
|
||||
|
||||
|
@ -460,7 +456,7 @@ const MessageActions = React.memo(
|
|||
headerHeight: HEADER_HEIGHT,
|
||||
customHeader:
|
||||
!isReadOnly || room.reactWhenReadOnly ? (
|
||||
<Header server={server} handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
|
||||
<Header handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
|
||||
) : null
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,22 +6,31 @@ import { Provider } from 'react-redux';
|
|||
import store from '../../lib/store';
|
||||
import EmojiPicker from '../EmojiPicker';
|
||||
import styles from './styles';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { ThemeContext, TSupportedThemes } from '../../theme';
|
||||
import { EventTypes } from '../EmojiPicker/interfaces';
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { colors } from '../../lib/constants';
|
||||
|
||||
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
|
||||
const onEmojiSelected = (emoji: string) => {
|
||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||
const onItemClicked = (eventType: EventTypes, emoji?: IEmoji) => {
|
||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji });
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<View
|
||||
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]}
|
||||
testID='messagebox-keyboard-emoji'
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
theme,
|
||||
colors: colors[theme]
|
||||
}}
|
||||
>
|
||||
<EmojiPicker onEmojiSelected={onEmojiSelected} theme={theme} />
|
||||
</View>
|
||||
<View
|
||||
style={[styles.emojiKeyboardContainer, { borderTopColor: colors[theme].borderColor }]}
|
||||
testID='messagebox-keyboard-emoji'
|
||||
>
|
||||
<EmojiPicker onItemClicked={onItemClicked} isEmojiKeyboard={true} />
|
||||
</View>
|
||||
</ThemeContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View, Text, Pressable, FlatList, StyleSheet } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||
import { addFrequentlyUsed, searchEmojis } from '../../lib/methods';
|
||||
import { useDebounce } from '../../lib/methods/helpers';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { PressableEmoji } from '../EmojiPicker/PressableEmoji';
|
||||
import { EmojiSearch } from '../EmojiPicker/EmojiSearch';
|
||||
import { EMOJI_BUTTON_SIZE } from '../EmojiPicker/styles';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
|
||||
const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listContainer: {
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
margin: 8,
|
||||
flexGrow: 1
|
||||
},
|
||||
container: {
|
||||
borderTopWidth: 1
|
||||
},
|
||||
searchContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 12,
|
||||
marginBottom: 12
|
||||
},
|
||||
backButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 10
|
||||
},
|
||||
emptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
emptyText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 16
|
||||
},
|
||||
inputContainer: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
||||
interface IEmojiSearchBarProps {
|
||||
openEmoji: () => void;
|
||||
closeEmoji: () => void;
|
||||
onEmojiSelected: (emoji: IEmoji) => void;
|
||||
}
|
||||
|
||||
const EmojiSearchBar = ({ openEmoji, closeEmoji, onEmojiSelected }: IEmojiSearchBarProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const { frequentlyUsed } = useFrequentlyUsedEmoji(true);
|
||||
const [emojis, setEmojis] = useState<IEmoji[]>([]);
|
||||
|
||||
const handleTextChange = useDebounce(async (text: string) => {
|
||||
logEvent(events.MB_SB_EMOJI_SEARCH);
|
||||
setSearchText(text);
|
||||
const result = await searchEmojis(text);
|
||||
setEmojis(result);
|
||||
}, 300);
|
||||
|
||||
const handleEmojiSelected = (emoji: IEmoji) => {
|
||||
logEvent(events.MB_SB_EMOJI_SELECTED);
|
||||
onEmojiSelected(emoji);
|
||||
addFrequentlyUsed(emoji);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={handleEmojiSelected} />;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { borderTopColor: colors.borderColor, backgroundColor: colors.messageboxBackground }]}>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={searchText ? emojis : frequentlyUsed}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
ListEmptyComponent={() => (
|
||||
<View style={styles.emptyContainer} testID='no-results-found'>
|
||||
<Text style={[styles.emptyText, { color: colors.auxiliaryText }]}>{I18n.t('No_results_found')}</Text>
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
<View style={styles.searchContainer}>
|
||||
<Pressable
|
||||
style={({ pressed }: { pressed: boolean }) => [styles.backButton, { opacity: pressed ? 0.7 : 1 }]}
|
||||
onPress={openEmoji}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
testID='openback-emoji-keyboard'
|
||||
>
|
||||
<CustomIcon name='chevron-left' size={24} color={colors.auxiliaryTintColor} />
|
||||
</Pressable>
|
||||
<View style={styles.inputContainer}>
|
||||
<EmojiSearch onBlur={closeEmoji} onChangeText={handleTextChange} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmojiSearchBar;
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import { IEmoji } from '../../../definitions/IEmoji';
|
||||
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||
import MessageboxContext from '../Context';
|
||||
import styles from '../styles';
|
||||
|
||||
interface IMessageBoxMentionEmoji {
|
||||
|
@ -12,13 +11,10 @@ interface IMessageBoxMentionEmoji {
|
|||
}
|
||||
|
||||
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
|
||||
const context = useContext(MessageboxContext);
|
||||
const { baseUrl } = context;
|
||||
|
||||
if (item.name) {
|
||||
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} baseUrl={baseUrl} />;
|
||||
if (typeof item === 'string') {
|
||||
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
||||
}
|
||||
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
||||
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} />;
|
||||
};
|
||||
|
||||
export default MentionEmoji;
|
||||
|
|
|
@ -4,5 +4,6 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
|||
export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
|
||||
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
|
||||
export const MENTIONS_COUNT_TO_DISPLAY = 4;
|
||||
export const MAX_EMOJIS_TO_DISPLAY = 20;
|
||||
|
||||
export const TIMEOUT_CLOSE_EMOJI = 300;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
|
||||
import { Alert, Keyboard, NativeModules, Text, View, BackHandler } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
||||
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
|
||||
|
@ -13,12 +13,11 @@ import { TextInput, IThemedTextInput } from '../TextInput';
|
|||
import { userTyping as userTypingAction } from '../../actions/room';
|
||||
import styles from './styles';
|
||||
import database from '../../lib/database';
|
||||
import { emojis } from '../EmojiPicker/emojis';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import RecordAudio from './RecordAudio';
|
||||
import I18n from '../../i18n';
|
||||
import ReplyPreview from './ReplyPreview';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { themes, emojis } from '../../lib/constants';
|
||||
import LeftButtons from './LeftButtons';
|
||||
import RightButtons from './RightButtons';
|
||||
import { canUploadFile } from '../../lib/methods/helpers/media';
|
||||
|
@ -51,7 +50,8 @@ import {
|
|||
TGetCustomEmoji,
|
||||
TSubscriptionModel,
|
||||
TThreadModel,
|
||||
IMessage
|
||||
IMessage,
|
||||
IEmoji
|
||||
} from '../../definitions';
|
||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
|
||||
|
@ -59,6 +59,9 @@ import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersi
|
|||
import { Services } from '../../lib/services';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { EventTypes } from '../EmojiPicker/interfaces';
|
||||
import EmojiSearchbar from './EmojiSearchbar';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
|
||||
require('./EmojiKeyboard');
|
||||
|
||||
|
@ -129,6 +132,7 @@ interface IMessageBoxState {
|
|||
tshow: boolean;
|
||||
mentionLoading: boolean;
|
||||
permissionToUpload: boolean;
|
||||
showEmojiSearchbar: boolean;
|
||||
}
|
||||
|
||||
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||
|
@ -183,7 +187,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
command: {},
|
||||
tshow: this.sendThreadToChannel,
|
||||
mentionLoading: false,
|
||||
permissionToUpload: true
|
||||
permissionToUpload: true,
|
||||
showEmojiSearchbar: false
|
||||
};
|
||||
this.text = '';
|
||||
this.selection = { start: 0, end: 0 };
|
||||
|
@ -209,6 +214,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
...videoPickerConfig,
|
||||
...libPickerLabels
|
||||
};
|
||||
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
get sendThreadToChannel() {
|
||||
|
@ -326,7 +333,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
tshow,
|
||||
mentionLoading,
|
||||
trackingType,
|
||||
permissionToUpload
|
||||
permissionToUpload,
|
||||
showEmojiSearchbar
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
|
@ -346,6 +354,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showEmojiSearchbar !== showEmojiSearchbar) {
|
||||
return true;
|
||||
}
|
||||
if (!isFocused()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -438,6 +449,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (isTablet) {
|
||||
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
setOptions = async () => {
|
||||
|
@ -519,7 +531,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}, 100);
|
||||
|
||||
onKeyboardResigned = () => {
|
||||
this.closeEmoji();
|
||||
const { showEmojiSearchbar } = this.state;
|
||||
if (!showEmojiSearchbar) {
|
||||
this.closeEmoji();
|
||||
}
|
||||
};
|
||||
|
||||
onPressMention = (item: any) => {
|
||||
|
@ -577,18 +592,50 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
};
|
||||
|
||||
onEmojiSelected = (keyboardId: string, params: { emoji: string }) => {
|
||||
onKeyboardItemSelected = (keyboardId: string, params: { eventType: EventTypes; emoji: IEmoji }) => {
|
||||
const { eventType, emoji } = params;
|
||||
const { text } = this;
|
||||
const { emoji } = params;
|
||||
let newText = '';
|
||||
|
||||
// if messagebox has an active cursor
|
||||
const { start, end } = this.selection;
|
||||
const cursor = Math.max(start, end);
|
||||
newText = `${text.substr(0, cursor)}${emoji}${text.substr(cursor)}`;
|
||||
const newCursor = cursor + emoji.length;
|
||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||
this.setShowSend(true);
|
||||
let newCursor;
|
||||
|
||||
switch (eventType) {
|
||||
case EventTypes.BACKSPACE_PRESSED:
|
||||
logEvent(events.MB_BACKSPACE);
|
||||
const emojiRegex = /\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]/;
|
||||
let charsToRemove = 1;
|
||||
const lastEmoji = text.substr(cursor > 0 ? cursor - 2 : text.length - 2, cursor > 0 ? cursor : text.length);
|
||||
// Check if last character is an emoji
|
||||
if (emojiRegex.test(lastEmoji)) charsToRemove = 2;
|
||||
newText =
|
||||
text.substr(0, (cursor > 0 ? cursor : text.length) - charsToRemove) + text.substr(cursor > 0 ? cursor : text.length);
|
||||
newCursor = cursor - charsToRemove;
|
||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||
this.setShowSend(newText !== '');
|
||||
break;
|
||||
case EventTypes.EMOJI_PRESSED:
|
||||
logEvent(events.MB_EMOJI_SELECTED);
|
||||
let emojiText = '';
|
||||
if (typeof emoji === 'string') {
|
||||
const shortname = `:${emoji}:`;
|
||||
emojiText = shortnameToUnicode(shortname);
|
||||
} else {
|
||||
emojiText = `:${emoji.name}:`;
|
||||
}
|
||||
newText = `${text.substr(0, cursor)}${emojiText}${text.substr(cursor)}`;
|
||||
newCursor = cursor + emojiText.length;
|
||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||
this.setShowSend(true);
|
||||
break;
|
||||
case EventTypes.SEARCH_PRESSED:
|
||||
logEvent(events.MB_EMOJI_SEARCH_PRESSED);
|
||||
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: true });
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
getPermalink = async (message: any) => {
|
||||
|
@ -621,16 +668,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
this.setState({ mentions: res, mentionLoading: false });
|
||||
}, 300);
|
||||
|
||||
getEmojis = debounce(async (keyword: any) => {
|
||||
const db = database.active;
|
||||
const customEmojisCollection = db.get('custom_emojis');
|
||||
getCustomEmojis = async (keyword: any, count: number) => {
|
||||
const likeString = sanitizeLikeString(keyword);
|
||||
const whereClause = [];
|
||||
if (likeString) {
|
||||
whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
|
||||
}
|
||||
let customEmojis = await customEmojisCollection.query(...whereClause).fetch();
|
||||
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||
const db = database.active;
|
||||
const customEmojisCollection = db.get('custom_emojis');
|
||||
const customEmojis = await (await customEmojisCollection.query(...whereClause).fetch()).slice(0, count);
|
||||
return customEmojis;
|
||||
};
|
||||
|
||||
getEmojis = debounce(async (keyword: any) => {
|
||||
const customEmojis = await this.getCustomEmojis(keyword, MENTIONS_COUNT_TO_DISPLAY);
|
||||
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||
this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
|
||||
|
@ -881,7 +932,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
openEmoji = () => {
|
||||
logEvent(events.ROOM_OPEN_EMOJI);
|
||||
this.setState({ showEmojiKeyboard: true });
|
||||
this.setState({ showEmojiKeyboard: true, showEmojiSearchbar: false });
|
||||
this.stopTrackingMention();
|
||||
};
|
||||
|
||||
recordingCallback = (recording: any) => {
|
||||
|
@ -903,7 +955,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
};
|
||||
|
||||
closeEmoji = () => {
|
||||
this.setState({ showEmojiKeyboard: false });
|
||||
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: false });
|
||||
};
|
||||
|
||||
closeEmojiKeyboardAndFocus = () => {
|
||||
logEvent(events.ROOM_CLOSE_EMOJI);
|
||||
this.closeEmoji();
|
||||
this.focus();
|
||||
};
|
||||
|
||||
closeEmojiAndAction = (action?: Function, params?: any) => {
|
||||
|
@ -926,7 +984,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
this.clearInput();
|
||||
this.debouncedOnChangeText.stop();
|
||||
this.closeEmoji();
|
||||
this.closeEmojiKeyboardAndFocus();
|
||||
this.stopTrackingMention();
|
||||
this.handleTyping(false);
|
||||
if (message.trim() === '' && !showSend) {
|
||||
|
@ -1083,10 +1141,34 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
);
|
||||
};
|
||||
|
||||
renderEmojiSearchbar = () => {
|
||||
const { showEmojiSearchbar } = this.state;
|
||||
|
||||
return showEmojiSearchbar ? (
|
||||
<EmojiSearchbar
|
||||
openEmoji={this.openEmoji}
|
||||
closeEmoji={this.closeEmoji}
|
||||
onEmojiSelected={(emoji: IEmoji) => {
|
||||
this.onKeyboardItemSelected('EmojiKeyboard', { eventType: EventTypes.EMOJI_PRESSED, emoji });
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
handleBackPress = () => {
|
||||
const { showEmojiSearchbar } = this.state;
|
||||
if (showEmojiSearchbar) {
|
||||
this.setState({ showEmojiSearchbar: false });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
const {
|
||||
recording,
|
||||
showEmojiKeyboard,
|
||||
showEmojiSearchbar,
|
||||
showSend,
|
||||
mentions,
|
||||
trackingType,
|
||||
|
@ -1149,11 +1231,11 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
const textInputAndButtons = !recording ? (
|
||||
<>
|
||||
<LeftButtons
|
||||
showEmojiKeyboard={showEmojiKeyboard}
|
||||
showEmojiKeyboard={showEmojiKeyboard || showEmojiSearchbar}
|
||||
editing={editing}
|
||||
editCancel={this.editCancel}
|
||||
openEmoji={this.openEmoji}
|
||||
closeEmoji={this.closeEmoji}
|
||||
closeEmoji={this.closeEmojiKeyboardAndFocus}
|
||||
/>
|
||||
<TextInput
|
||||
ref={component => (this.component = component)}
|
||||
|
@ -1197,6 +1279,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
{recordAudio}
|
||||
</View>
|
||||
{this.renderSendToChannel()}
|
||||
{this.renderEmojiSearchbar()}
|
||||
</View>
|
||||
{children}
|
||||
</>
|
||||
|
@ -1224,7 +1307,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||
kbInitialProps={{ theme }}
|
||||
onKeyboardResigned={this.onKeyboardResigned}
|
||||
onItemSelected={this.onEmojiSelected}
|
||||
onItemSelected={this.onKeyboardItemSelected}
|
||||
trackInteractive
|
||||
requiresSameParentToManageScrollView
|
||||
addBottomView
|
||||
|
|
|
@ -105,7 +105,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
emojiKeyboardContainer: {
|
||||
flex: 1,
|
||||
borderTopWidth: StyleSheet.hairlineWidth
|
||||
borderTopWidth: 1
|
||||
},
|
||||
slash: {
|
||||
height: 30,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Text, StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../../lib/constants';
|
||||
|
@ -12,16 +12,17 @@ interface IPasscodeButton {
|
|||
icon?: TIconsName;
|
||||
disabled?: boolean;
|
||||
onPress?: Function;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const Button = React.memo(({ text, disabled, onPress, icon }: IPasscodeButton) => {
|
||||
const Button = React.memo(({ style, text, disabled, onPress, icon }: IPasscodeButton) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const press = () => onPress && onPress(text);
|
||||
|
||||
return (
|
||||
<Touch
|
||||
style={[styles.buttonView, { backgroundColor: 'transparent' }]}
|
||||
style={[styles.buttonView, { backgroundColor: 'transparent' }, style]}
|
||||
underlayColor={themes[theme].passcodeButtonActive}
|
||||
rippleColor={themes[theme].passcodeButtonActive}
|
||||
enabled={!disabled}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Col, Grid, Row } from 'react-native-easy-grid';
|
||||
import range from 'lodash/range';
|
||||
import { View } from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
|
||||
import styles from './styles';
|
||||
import Button from './Button';
|
||||
|
@ -14,6 +15,8 @@ import { useTheme } from '../../../theme';
|
|||
import LockIcon from './LockIcon';
|
||||
import Title from './Title';
|
||||
import Subtitle from './Subtitle';
|
||||
import { useDimensions } from '../../../dimensions';
|
||||
import { isTablet } from '../../../lib/methods/helpers';
|
||||
|
||||
interface IPasscodeBase {
|
||||
type: string;
|
||||
|
@ -34,7 +37,25 @@ export interface IBase {
|
|||
|
||||
const Base = forwardRef<IBase, IPasscodeBase>(
|
||||
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
|
||||
useLayoutEffect(() => {
|
||||
if (!isTablet) {
|
||||
Orientation.lockToPortrait();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (!isTablet) {
|
||||
Orientation.unlockAllOrientations();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { theme } = useTheme();
|
||||
const { height } = useDimensions();
|
||||
|
||||
// 206 is the height of the header calculating the margins, icon size height, title font size and subtitle height.
|
||||
// 56 is a fixed number to decrease the height of button numbers.
|
||||
const dinamicHeight = (height - 206 - 56) / 4;
|
||||
const heightButtonRow = { height: dinamicHeight > 102 ? 102 : dinamicHeight };
|
||||
|
||||
const rootRef = useRef<Animatable.View & View>(null);
|
||||
const dotsRef = useRef<Animatable.View & View>(null);
|
||||
|
@ -103,40 +124,40 @@ const Base = forwardRef<IBase, IPasscodeBase>(
|
|||
<Dots passcode={passcode} length={PASSCODE_LENGTH} />
|
||||
</Animatable.View>
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{range(1, 4).map(i => (
|
||||
<Col key={i} style={styles.colButton}>
|
||||
<Button text={i.toString()} onPress={onPressNumber} />
|
||||
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{range(4, 7).map(i => (
|
||||
<Col key={i} style={styles.colButton}>
|
||||
<Button text={i.toString()} onPress={onPressNumber} />
|
||||
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{range(7, 10).map(i => (
|
||||
<Col key={i} style={styles.colButton}>
|
||||
<Button text={i.toString()} onPress={onPressNumber} />
|
||||
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{showBiometry ? (
|
||||
<Col style={styles.colButton}>
|
||||
<Button icon='fingerprint' onPress={onBiometryPress} />
|
||||
<Col style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} icon='fingerprint' onPress={onBiometryPress} />
|
||||
</Col>
|
||||
) : (
|
||||
<Col style={styles.colButton} />
|
||||
<Col style={[styles.colButton, heightButtonRow]} />
|
||||
)}
|
||||
<Col style={styles.colButton}>
|
||||
<Button text='0' onPress={onPressNumber} />
|
||||
<Col style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text='0' onPress={onPressNumber} />
|
||||
</Col>
|
||||
<Col style={styles.colButton}>
|
||||
<Button icon='backspace' onPress={onPressDelete} />
|
||||
<Col style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} icon='backspace' onPress={onPressDelete} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
|
|
@ -18,16 +18,12 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
buttonRow: {
|
||||
height: 102
|
||||
},
|
||||
colButton: {
|
||||
flex: 0,
|
||||
marginLeft: 12,
|
||||
marginRight: 12,
|
||||
alignItems: 'center',
|
||||
width: 78,
|
||||
height: 78
|
||||
width: 78
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 28,
|
||||
|
@ -37,7 +33,6 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 78,
|
||||
height: 78,
|
||||
borderRadius: 4
|
||||
},
|
||||
textTitle: {
|
||||
|
|
|
@ -23,7 +23,6 @@ interface IAllTabProps {
|
|||
const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => {
|
||||
const { colors } = useTheme();
|
||||
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
||||
const server = useAppSelector(state => state.server.server);
|
||||
const username = useAppSelector(state => state.login.user.username);
|
||||
const count = item.usernames.length;
|
||||
|
||||
|
@ -50,7 +49,6 @@ const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemPro
|
|||
content={item.emoji}
|
||||
standardEmojiStyle={styles.allTabStandardEmojiStyle}
|
||||
customEmojiStyle={styles.allTabCustomEmojiStyle}
|
||||
baseUrl={server}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<View style={styles.textContainer}>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { TGetCustomEmoji, IEmoji } from '../../definitions';
|
||||
import { TGetCustomEmoji, ICustomEmoji } from '../../definitions';
|
||||
import ReactionsList from '.';
|
||||
import { mockedStore as store } from '../../reducers/mockedStore';
|
||||
import { updateSettings } from '../../actions/settings';
|
||||
|
@ -11,7 +11,7 @@ const getCustomEmoji: TGetCustomEmoji = content => {
|
|||
marioparty: { name: content, extension: 'gif' },
|
||||
react_rocket: { name: content, extension: 'png' },
|
||||
nyan_rocket: { name: content, extension: 'png' }
|
||||
}[content] as IEmoji;
|
||||
}[content] as ICustomEmoji;
|
||||
return customEmoji;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
|||
import I18n from '../../i18n';
|
||||
import styles, { MIN_TAB_WIDTH } from './styles';
|
||||
import { useDimensions, useOrientation } from '../../dimensions';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
interface ITabBarItem {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
|
@ -25,7 +24,6 @@ interface IReactionsTabBar {
|
|||
|
||||
const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => {
|
||||
const { colors } = useTheme();
|
||||
const server = useAppSelector(state => state.server.server);
|
||||
return (
|
||||
<Pressable
|
||||
key={tab.emoji}
|
||||
|
@ -46,7 +44,6 @@ const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => {
|
|||
content={tab.emoji}
|
||||
standardEmojiStyle={styles.standardEmojiStyle}
|
||||
customEmojiStyle={styles.customEmojiStyle}
|
||||
baseUrl={server}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { StyleSheet, Text, unstable_batchedUpdates, View } from 'react-native';
|
||||
import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||
|
@ -48,11 +48,15 @@ export const DatePicker = ({ element, language, action, context, loading, value,
|
|||
// timestamp as number exists in Event
|
||||
// @ts-ignore
|
||||
const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => {
|
||||
const newDate = date || new Date(timestamp);
|
||||
onChangeDate(newDate);
|
||||
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
||||
if (isAndroid) {
|
||||
onShow(false);
|
||||
if (date || timestamp) {
|
||||
const newDate = date || new Date(timestamp);
|
||||
unstable_batchedUpdates(() => {
|
||||
onChangeDate(newDate);
|
||||
if (isAndroid) {
|
||||
onShow(false);
|
||||
}
|
||||
});
|
||||
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -85,7 +89,13 @@ export const DatePicker = ({ element, language, action, context, loading, value,
|
|||
}
|
||||
|
||||
const content = show ? (
|
||||
<DateTimePicker mode='date' display='default' value={currentDate} onChange={onChange} textColor={themes[theme].titleText} />
|
||||
<DateTimePicker
|
||||
mode='date'
|
||||
display={isAndroid ? 'default' : 'inline'}
|
||||
value={currentDate}
|
||||
onChange={onChange}
|
||||
textColor={themes[theme].titleText}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -10,33 +10,24 @@ interface IEmoji {
|
|||
literal: string;
|
||||
isMessageContainsOnlyEmoji: boolean;
|
||||
getCustomEmoji?: Function;
|
||||
baseUrl: string;
|
||||
customEmojis?: any;
|
||||
style?: object;
|
||||
onEmojiSelected?: Function;
|
||||
tabEmojiStyle?: object;
|
||||
}
|
||||
|
||||
const Emoji = React.memo(
|
||||
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {} }: IEmoji) => {
|
||||
const { colors } = useTheme();
|
||||
const emojiUnicode = shortnameToUnicode(literal);
|
||||
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
|
||||
if (emoji && customEmojis) {
|
||||
return (
|
||||
<CustomEmoji
|
||||
baseUrl={baseUrl}
|
||||
style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]}
|
||||
emoji={emoji}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
|
||||
{emojiUnicode}
|
||||
</Text>
|
||||
);
|
||||
const Emoji = React.memo(({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, customEmojis = true, style = {} }: IEmoji) => {
|
||||
const { colors } = useTheme();
|
||||
const emojiUnicode = shortnameToUnicode(literal);
|
||||
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
|
||||
if (emoji && customEmojis) {
|
||||
return <CustomEmoji style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]} emoji={emoji} />;
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
|
||||
{emojiUnicode}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
export default Emoji;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { NavigationContainer } from '@react-navigation/native';
|
|||
|
||||
import Markdown, { MarkdownPreview } from '.';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TGetCustomEmoji, IEmoji } from '../../definitions/IEmoji';
|
||||
import { TGetCustomEmoji, ICustomEmoji } from '../../definitions/IEmoji';
|
||||
|
||||
const theme = 'light';
|
||||
|
||||
|
@ -16,7 +16,6 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
const longText =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
|
||||
const lineBreakText = `a
|
||||
|
@ -34,7 +33,7 @@ const getCustomEmoji: TGetCustomEmoji = content => {
|
|||
marioparty: { name: content, extension: 'gif' },
|
||||
react_rocket: { name: content, extension: 'png' },
|
||||
nyan_rocket: { name: content, extension: 'png' }
|
||||
}[content] as IEmoji;
|
||||
}[content] as ICustomEmoji;
|
||||
return customEmoji;
|
||||
};
|
||||
|
||||
|
@ -108,13 +107,8 @@ export const Emoji = () => (
|
|||
<View style={styles.container}>
|
||||
<Markdown msg='Unicode: 😃😇👍' theme={theme} />
|
||||
<Markdown msg='Shortnames: :joy::+1:' theme={theme} />
|
||||
<Markdown
|
||||
msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:'
|
||||
theme={theme}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
|
||||
<Markdown msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} />
|
||||
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ interface IMarkdownProps {
|
|||
md?: MarkdownAST;
|
||||
mentions?: IUserMention[];
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
baseUrl?: string;
|
||||
username?: string;
|
||||
tmid?: string;
|
||||
numberOfLines?: number;
|
||||
|
@ -235,13 +234,12 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
renderEmoji = ({ literal }: TLiteral) => {
|
||||
const { getCustomEmoji, baseUrl = '', customEmojis, style } = this.props;
|
||||
const { getCustomEmoji, customEmojis, style } = this.props;
|
||||
return (
|
||||
<MarkdownEmoji
|
||||
literal={literal}
|
||||
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
baseUrl={baseUrl}
|
||||
customEmojis={customEmojis}
|
||||
style={style}
|
||||
/>
|
||||
|
@ -312,18 +310,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
msg,
|
||||
md,
|
||||
mentions,
|
||||
channels,
|
||||
navToRoomInfo,
|
||||
useRealName,
|
||||
username = '',
|
||||
getCustomEmoji,
|
||||
baseUrl = '',
|
||||
onLinkPress
|
||||
} = this.props;
|
||||
const { msg, md, mentions, channels, navToRoomInfo, useRealName, username = '', getCustomEmoji, onLinkPress } = this.props;
|
||||
|
||||
if (!msg) {
|
||||
return null;
|
||||
|
@ -333,7 +320,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
return (
|
||||
<NewMarkdown
|
||||
username={username}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
useRealName={useRealName}
|
||||
tokens={md}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Text } from 'react-native';
|
|||
import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { useTheme } from '../../../theme';
|
||||
import styles from '../styles';
|
||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||
|
@ -15,21 +14,21 @@ interface IEmojiProps {
|
|||
}
|
||||
|
||||
const Emoji = ({ block, isBigEmoji }: IEmojiProps) => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
|
||||
const { colors } = useTheme();
|
||||
const { getCustomEmoji } = useContext(MarkdownContext);
|
||||
|
||||
if ('unicode' in block) {
|
||||
return <Text style={[{ color: themes[theme].bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{block.unicode}</Text>;
|
||||
return <Text style={[{ color: colors.bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{block.unicode}</Text>;
|
||||
}
|
||||
const emojiToken = block?.shortCode ? `:${block.shortCode}:` : `:${block.value?.value}:`;
|
||||
const emojiUnicode = shortnameToUnicode(emojiToken);
|
||||
const emoji = getCustomEmoji?.(block.value?.value);
|
||||
|
||||
if (emoji) {
|
||||
return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
|
||||
return <CustomEmoji style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
|
||||
}
|
||||
return (
|
||||
<Text style={[{ color: themes[theme].bodyText }, isBigEmoji && emojiToken !== emojiUnicode ? styles.textBig : styles.text]}>
|
||||
<Text style={[{ color: colors.bodyText }, isBigEmoji && emojiToken !== emojiUnicode ? styles.textBig : styles.text]}>
|
||||
{emojiUnicode}
|
||||
</Text>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@ interface IMarkdownContext {
|
|||
channels?: IUserChannel[];
|
||||
useRealName?: boolean;
|
||||
username?: string;
|
||||
baseUrl?: string;
|
||||
navToRoomInfo?: Function;
|
||||
getCustomEmoji?: Function;
|
||||
onLinkPress?: Function;
|
||||
|
@ -18,7 +17,6 @@ const defaultState = {
|
|||
channels: [],
|
||||
useRealName: false,
|
||||
username: '',
|
||||
baseUrl: '',
|
||||
navToRoomInfo: () => {}
|
||||
};
|
||||
|
||||
|
|
|
@ -34,11 +34,8 @@ const getCustomEmoji = (content: string) => {
|
|||
}[content];
|
||||
return customEmoji;
|
||||
};
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
|
||||
const NewMarkdown = ({ ...props }) => (
|
||||
<NewMarkdownComponent baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} username='rocket.cat' {...props} />
|
||||
);
|
||||
const NewMarkdown = ({ ...props }) => <NewMarkdownComponent getCustomEmoji={getCustomEmoji} username='rocket.cat' {...props} />;
|
||||
|
||||
const simpleTextMsg = [
|
||||
{
|
||||
|
@ -340,7 +337,7 @@ const emojiTokens = [
|
|||
export const Emoji = () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={bigEmojiTokens} />
|
||||
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
|
||||
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ interface IBodyProps {
|
|||
navToRoomInfo?: Function;
|
||||
useRealName?: boolean;
|
||||
username: string;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const Body = ({
|
||||
|
@ -35,7 +34,6 @@ const Body = ({
|
|||
username,
|
||||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
}: IBodyProps): React.ReactElement | null => {
|
||||
if (isEmpty(tokens)) {
|
||||
|
@ -51,7 +49,6 @@ const Body = ({
|
|||
username,
|
||||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -283,7 +283,6 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
<Markdown
|
||||
msg={description}
|
||||
style={[isReply && style]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
|
|
@ -30,7 +30,7 @@ export const Item = () => (
|
|||
user: { username: 'Marcos' }
|
||||
}}
|
||||
>
|
||||
<CollapsibleQuote key={0} index={0} attachment={testAttachment} getCustomEmoji={() => {}} timeFormat='LT' />
|
||||
<CollapsibleQuote key={0} index={0} attachment={testAttachment} getCustomEmoji={() => null} timeFormat='LT' />
|
||||
</MessageContext.Provider>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -82,7 +82,7 @@ interface IMessageReply {
|
|||
const Fields = React.memo(
|
||||
({ attachment, getCustomEmoji }: IMessageFields) => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { user } = useContext(MessageContext);
|
||||
|
||||
if (!attachment.fields) {
|
||||
return null;
|
||||
|
@ -97,7 +97,6 @@ const Fields = React.memo(
|
|||
</Text>
|
||||
<Markdown
|
||||
msg={field?.value || ''}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { CustomIcon } from '../../../CustomIcon';
|
|||
import styles from '../../styles';
|
||||
import { useTheme } from '../../../../theme';
|
||||
|
||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => {
|
||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread?: boolean }) => {
|
||||
const { theme } = useTheme();
|
||||
if (isReadReceiptEnabled && !unread && unread !== null) {
|
||||
return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={styles.rightIcons} />;
|
||||
|
|
|
@ -17,8 +17,8 @@ interface IRightIcons {
|
|||
type: MessageType;
|
||||
msg?: string;
|
||||
isEdited: boolean;
|
||||
isReadReceiptEnabled: boolean;
|
||||
unread: boolean;
|
||||
isReadReceiptEnabled?: boolean;
|
||||
unread?: boolean;
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ const RightIcons = ({ type, msg, isEdited, hasError, isReadReceiptEnabled, unrea
|
|||
<Encrypted type={type} />
|
||||
<Edited testID={`${msg}-edited`} isEdited={isEdited} />
|
||||
<MessageError hasError={hasError} />
|
||||
<ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread || false} />
|
||||
<ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
@ -6,16 +6,17 @@ import I18n from '../../i18n';
|
|||
import styles from './styles';
|
||||
import Markdown, { MarkdownPreview } from '../markdown';
|
||||
import User from './User';
|
||||
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
|
||||
import { messageHaveAuthorName, getInfoMessage } from './utils';
|
||||
import MessageContext from './Context';
|
||||
import { IMessageContent } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { MessageTypesValues } from '../../definitions';
|
||||
|
||||
const Content = React.memo(
|
||||
(props: IMessageContent) => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
|
||||
const { user, onLinkPress } = useContext(MessageContext);
|
||||
|
||||
if (props.isInfo) {
|
||||
// @ts-ignore
|
||||
|
@ -26,8 +27,7 @@ const Content = React.memo(
|
|||
{infoMessage}
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(props.type)) {
|
||||
if (messageHaveAuthorName(props.type as MessageTypesValues)) {
|
||||
return (
|
||||
<Text>
|
||||
<User {...props} /> {renderMessageContent}
|
||||
|
@ -54,7 +54,6 @@ const Content = React.memo(
|
|||
<Markdown
|
||||
msg={props.msg}
|
||||
md={props.md}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={props.getCustomEmoji}
|
||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||
username={user.username}
|
||||
|
|
|
@ -6,11 +6,11 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
|||
import { IMessageEmoji } from './interfaces';
|
||||
|
||||
const Emoji = React.memo(
|
||||
({ content, baseUrl, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => {
|
||||
({ content, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => {
|
||||
const parsedContent = content.replace(/^:|:$/g, '');
|
||||
const emoji = getCustomEmoji(parsedContent);
|
||||
if (emoji) {
|
||||
return <CustomEmoji key={content} baseUrl={baseUrl} style={customEmojiStyle} emoji={emoji} />;
|
||||
return <CustomEmoji key={content} style={customEmojiStyle} emoji={emoji} />;
|
||||
}
|
||||
return <Text style={standardEmojiStyle}>{shortnameToUnicode(content)}</Text>;
|
||||
},
|
||||
|
|
|
@ -81,7 +81,6 @@ const ImageContainer = React.memo(
|
|||
<Markdown
|
||||
msg={file.description}
|
||||
style={[isReply && style]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
|
|
@ -820,8 +820,8 @@ export const SystemMessages = () => (
|
|||
<Message msg='public' type='room_changed_privacy' isInfo />
|
||||
<Message type='room_e2e_disabled' isInfo />
|
||||
<Message type='room_e2e_enabled' isInfo />
|
||||
<Message type='removed-user-from-team' isInfo />
|
||||
<Message type='added-user-to-team' isInfo />
|
||||
<Message msg='rocket.cat' type='removed-user-from-team' isInfo />
|
||||
<Message msg='rocket.cat' type='added-user-to-team' isInfo />
|
||||
<Message type='user-added-room-to-team' isInfo msg='channel-name' />
|
||||
<Message type='user-converted-to-team' isInfo msg='channel-name' />
|
||||
<Message type='user-converted-to-channel' isInfo msg='channel-name' />
|
||||
|
|
|
@ -97,8 +97,8 @@ const Message = React.memo((props: IMessage) => {
|
|||
msg={props.msg}
|
||||
isEdited={props.isEdited}
|
||||
hasError={props.hasError}
|
||||
isReadReceiptEnabled={props.isReadReceiptEnabled || false}
|
||||
unread={props.unread || false}
|
||||
isReadReceiptEnabled={props.isReadReceiptEnabled}
|
||||
unread={props.unread}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
|
|
@ -47,7 +47,7 @@ const AddReaction = React.memo(({ theme }: { theme: TSupportedThemes }) => {
|
|||
});
|
||||
|
||||
const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReaction) => {
|
||||
const { onReactionPress, onReactionLongPress, baseUrl, user } = useContext(MessageContext);
|
||||
const { onReactionPress, onReactionLongPress, user } = useContext(MessageContext);
|
||||
const reacted = reaction.usernames.findIndex((item: string) => item === user.username) !== -1;
|
||||
return (
|
||||
<Touchable
|
||||
|
@ -67,7 +67,6 @@ const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReacti
|
|||
content={reaction.emoji}
|
||||
standardEmojiStyle={styles.reactionEmoji}
|
||||
customEmojiStyle={styles.reactionCustomEmoji}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<Text style={[styles.reactionCount, { color: themes[theme].tintColor }]}>{reaction.usernames.length}</Text>
|
||||
|
|
|
@ -121,7 +121,7 @@ const Description = React.memo(
|
|||
getCustomEmoji: TGetCustomEmoji;
|
||||
theme: TSupportedThemes;
|
||||
}) => {
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { user } = useContext(MessageContext);
|
||||
const text = attachment.text || attachment.title;
|
||||
|
||||
if (!text) {
|
||||
|
@ -132,7 +132,6 @@ const Description = React.memo(
|
|||
<Markdown
|
||||
msg={text}
|
||||
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14 }]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
@ -177,7 +176,7 @@ const Fields = React.memo(
|
|||
theme: TSupportedThemes;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
}) => {
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { user } = useContext(MessageContext);
|
||||
|
||||
if (!attachment.fields) {
|
||||
return null;
|
||||
|
@ -188,13 +187,7 @@ const Fields = React.memo(
|
|||
{attachment.fields.map(field => (
|
||||
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
||||
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
|
||||
<Markdown
|
||||
msg={field?.value || ''}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
<Markdown msg={field?.value || ''} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
@ -278,13 +271,7 @@ const Reply = React.memo(
|
|||
) : null}
|
||||
</View>
|
||||
</Touchable>
|
||||
<Markdown
|
||||
msg={attachment.description}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
<Markdown msg={attachment.description} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -7,8 +7,8 @@ import { useTheme } from '../../theme';
|
|||
import sharedStyles from '../../views/Styles';
|
||||
import messageStyles from './styles';
|
||||
import MessageContext from './Context';
|
||||
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
|
||||
import { MessageType, SubscriptionType } from '../../definitions';
|
||||
import { messageHaveAuthorName } from './utils';
|
||||
import { MessageType, MessageTypesValues, SubscriptionType } from '../../definitions';
|
||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||
import RightIcons from './Components/RightIcons';
|
||||
|
||||
|
@ -88,8 +88,7 @@ const User = React.memo(
|
|||
{aliasUsername}
|
||||
</>
|
||||
);
|
||||
|
||||
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(type)) {
|
||||
if (messageHaveAuthorName(type as MessageTypesValues)) {
|
||||
return (
|
||||
<Text
|
||||
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
|
||||
|
@ -114,8 +113,8 @@ const User = React.memo(
|
|||
type={type}
|
||||
isEdited={isEdited}
|
||||
hasError={hasError}
|
||||
isReadReceiptEnabled={props.isReadReceiptEnabled || false}
|
||||
unread={props.unread || false}
|
||||
isReadReceiptEnabled={props.isReadReceiptEnabled}
|
||||
unread={props.unread}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -79,7 +79,6 @@ const Video = React.memo(
|
|||
<>
|
||||
<Markdown
|
||||
msg={file.description}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
style={[isReply && style]}
|
||||
|
|
|
@ -67,7 +67,6 @@ export interface IMessageContent {
|
|||
|
||||
export interface IMessageEmoji {
|
||||
content: string;
|
||||
baseUrl: string;
|
||||
standardEmojiStyle: { fontSize: number };
|
||||
customEmojiStyle: StyleProp<ImageStyle>;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable complexity */
|
||||
import { TMessageModel } from '../../definitions/IMessage';
|
||||
import { MessageTypesValues, TMessageModel } from '../../definitions/IMessage';
|
||||
import I18n from '../../i18n';
|
||||
import { DISCUSSION } from './constants';
|
||||
|
||||
|
@ -26,30 +26,18 @@ export const BUTTON_HIT_SLOP = {
|
|||
left: 4
|
||||
};
|
||||
|
||||
export const SYSTEM_MESSAGES = [
|
||||
const messagesWithAuthorName: MessageTypesValues[] = [
|
||||
'r',
|
||||
'au',
|
||||
'ru',
|
||||
'ul',
|
||||
'ult',
|
||||
'au',
|
||||
'rm',
|
||||
'uj',
|
||||
'ujt',
|
||||
'ut',
|
||||
'rm',
|
||||
'user-muted',
|
||||
'user-unmuted',
|
||||
'ul',
|
||||
'ult',
|
||||
'message_pinned',
|
||||
'subscription-role-added',
|
||||
'subscription-role-removed',
|
||||
'room_changed_description',
|
||||
'room_changed_announcement',
|
||||
'room_changed_topic',
|
||||
'room_changed_privacy',
|
||||
'room_changed_avatar',
|
||||
'message_snippeted',
|
||||
'thread-created',
|
||||
'room_e2e_enabled',
|
||||
'room_e2e_disabled',
|
||||
'removed-user-from-team',
|
||||
'added-user-to-team',
|
||||
'user-added-room-to-team',
|
||||
|
@ -57,81 +45,39 @@ export const SYSTEM_MESSAGES = [
|
|||
'user-converted-to-channel',
|
||||
'user-deleted-room-from-team',
|
||||
'user-removed-room-from-team',
|
||||
'room-disallowed-reacting',
|
||||
'room-allowed-reacting',
|
||||
'room-set-read-only',
|
||||
'room-removed-read-only',
|
||||
'omnichannel_placed_chat_on_hold',
|
||||
'omnichannel_on_hold_chat_resumed'
|
||||
];
|
||||
|
||||
export const IGNORED_LIVECHAT_SYSTEM_MESSAGES = [
|
||||
'omnichannel_on_hold_chat_resumed',
|
||||
'livechat_navigation_history',
|
||||
'livechat_transcript_history',
|
||||
'livechat_transfer_history',
|
||||
'command',
|
||||
'livechat-close',
|
||||
'livechat-started',
|
||||
'livechat-close',
|
||||
'livechat_video_call',
|
||||
'livechat_webrtc_video_call'
|
||||
'livechat_webrtc_video_call',
|
||||
'livechat_transfer_history',
|
||||
'room-archived',
|
||||
'room-unarchived',
|
||||
'user-muted',
|
||||
'room_changed_description',
|
||||
'room_changed_announcement',
|
||||
'room_changed_topic',
|
||||
'room_changed_privacy',
|
||||
'room_changed_avatar',
|
||||
'room_e2e_disabled',
|
||||
'room_e2e_enabled',
|
||||
'room-disallowed-reacting',
|
||||
'room-set-read-only',
|
||||
'room-removed-read-only',
|
||||
'user-unmuted',
|
||||
'room-unarchived',
|
||||
'subscription-role-added',
|
||||
'subscription-role-removed'
|
||||
];
|
||||
|
||||
export const SYSTEM_MESSAGE_TYPES = {
|
||||
MESSAGE_REMOVED: 'rm',
|
||||
MESSAGE_PINNED: 'message_pinned',
|
||||
MESSAGE_SNIPPETED: 'message_snippeted',
|
||||
USER_JOINED_CHANNEL: 'uj',
|
||||
USER_JOINED_TEAM: 'ujt',
|
||||
USER_JOINED_DISCUSSION: 'ut',
|
||||
USER_LEFT_CHANNEL: 'ul',
|
||||
USER_LEFT_TEAM: 'ult',
|
||||
REMOVED_USER_FROM_TEAM: 'removed-user-from-team',
|
||||
ADDED_USER_TO_TEAM: 'added-user-to-team',
|
||||
ADDED_ROOM_TO_TEAM: 'user-added-room-to-team',
|
||||
CONVERTED_TO_TEAM: 'user-converted-to-team',
|
||||
CONVERTED_TO_CHANNEL: 'user-converted-to-channel',
|
||||
DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
|
||||
REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team',
|
||||
OMNICHANNEL_PLACED_CHAT_ON_HOLD: 'omnichannel_placed_chat_on_hold',
|
||||
OMNICHANNEL_ON_HOLD_CHAT_RESUMED: 'omnichannel_on_hold_chat_resumed',
|
||||
LIVECHAT_NAVIGATION_HISTORY: 'livechat_navigation_history',
|
||||
LIVECHAT_TRANSCRIPT_HISTORY: 'livechat_transcript_history',
|
||||
COMMAND: 'command',
|
||||
LIVECHAT_STARTED: 'livechat-started',
|
||||
LIVECHAT_CLOSE: 'livechat-close',
|
||||
LIVECHAT_VIDEO_CALL: 'livechat_video_call',
|
||||
LIVECHAT_WEBRTC_VIDEO_CALL: 'livechat_webrtc_video_call',
|
||||
LIVECHAT_TRANSFER_HISTORY: 'livechat_transfer_history'
|
||||
};
|
||||
|
||||
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
|
||||
SYSTEM_MESSAGE_TYPES.MESSAGE_REMOVED,
|
||||
SYSTEM_MESSAGE_TYPES.MESSAGE_PINNED,
|
||||
SYSTEM_MESSAGE_TYPES.MESSAGE_SNIPPETED,
|
||||
SYSTEM_MESSAGE_TYPES.USER_JOINED_CHANNEL,
|
||||
SYSTEM_MESSAGE_TYPES.USER_JOINED_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION,
|
||||
SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL,
|
||||
SYSTEM_MESSAGE_TYPES.USER_LEFT_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.REMOVED_USER_FROM_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.ADDED_USER_TO_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.ADDED_ROOM_TO_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_CHANNEL,
|
||||
SYSTEM_MESSAGE_TYPES.DELETED_ROOM_FROM_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.REMOVED_ROOM_FROM_TEAM,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_NAVIGATION_HISTORY,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_TRANSCRIPT_HISTORY,
|
||||
SYSTEM_MESSAGE_TYPES.COMMAND,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_STARTED,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_CLOSE,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_VIDEO_CALL,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_WEBRTC_VIDEO_CALL,
|
||||
SYSTEM_MESSAGE_TYPES.LIVECHAT_TRANSFER_HISTORY
|
||||
];
|
||||
export const messageHaveAuthorName = (type: MessageTypesValues): boolean => messagesWithAuthorName.includes(type);
|
||||
|
||||
type TInfoMessage = {
|
||||
type: string;
|
||||
type: MessageTypesValues;
|
||||
role: string;
|
||||
msg: string;
|
||||
author: { username: string };
|
||||
|
@ -141,137 +87,109 @@ type TInfoMessage = {
|
|||
export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessage): string => {
|
||||
const { username } = author;
|
||||
|
||||
if (type === 'rm') {
|
||||
return I18n.t('Message_removed');
|
||||
switch (type) {
|
||||
// with author name
|
||||
case 'rm':
|
||||
return I18n.t('Message_removed');
|
||||
case 'uj':
|
||||
return I18n.t('User_joined_the_channel');
|
||||
case 'ujt':
|
||||
return I18n.t('User_joined_the_team');
|
||||
case 'ut':
|
||||
return I18n.t('User_joined_the_conversation');
|
||||
case 'r':
|
||||
return I18n.t('Room_name_changed_to', { name: msg });
|
||||
case 'ru':
|
||||
return I18n.t('User_has_been_removed', { userRemoved: msg });
|
||||
case 'au':
|
||||
return I18n.t('User_added_to', { userAdded: msg });
|
||||
case 'user-muted':
|
||||
return I18n.t('User_has_been_muted', { userMuted: msg });
|
||||
case 'room_changed_description':
|
||||
return I18n.t('changed_room_description', { description: msg });
|
||||
case 'room_changed_announcement':
|
||||
return I18n.t('changed_room_announcement', { announcement: msg });
|
||||
case 'room_changed_topic':
|
||||
return I18n.t('room_changed_topic_to', { topic: msg });
|
||||
case 'room_changed_privacy':
|
||||
return I18n.t('room_changed_type', { type: msg });
|
||||
case 'room_changed_avatar':
|
||||
return I18n.t('room_avatar_changed');
|
||||
case 'message_snippeted':
|
||||
return I18n.t('Created_snippet');
|
||||
case 'room_e2e_disabled':
|
||||
return I18n.t('Disabled_E2E_Encryption_for_this_room');
|
||||
case 'room_e2e_enabled':
|
||||
return I18n.t('Enabled_E2E_Encryption_for_this_room');
|
||||
case 'removed-user-from-team':
|
||||
return I18n.t('Removed__username__from_the_team', { userRemoved: msg });
|
||||
case 'added-user-to-team':
|
||||
return I18n.t('Added__username__to_this_team', { user_added: msg });
|
||||
case 'user-added-room-to-team':
|
||||
return I18n.t('added__roomName__to_this_team', { roomName: msg });
|
||||
case 'user-converted-to-team':
|
||||
return I18n.t('Converted__roomName__to_a_team', { roomName: msg });
|
||||
case 'user-converted-to-channel':
|
||||
return I18n.t('Converted__roomName__to_a_channel', { roomName: msg });
|
||||
case 'user-deleted-room-from-team':
|
||||
return I18n.t('Deleted__roomName__', { roomName: msg });
|
||||
case 'user-removed-room-from-team':
|
||||
return I18n.t('Removed__roomName__from_the_team', { roomName: msg });
|
||||
case 'room-disallowed-reacting':
|
||||
return I18n.t('room_disallowed_reactions');
|
||||
case 'room-allowed-reacting':
|
||||
return I18n.t('room_allowed_reactions');
|
||||
case 'room-set-read-only':
|
||||
return I18n.t('room_set_read_only_permission');
|
||||
case 'room-removed-read-only':
|
||||
return I18n.t('room_removed_read_only_permission');
|
||||
case 'user-unmuted':
|
||||
return I18n.t('User_has_been_unmuted', { userUnmuted: msg });
|
||||
case 'room-archived':
|
||||
return I18n.t('room_archived');
|
||||
case 'room-unarchived':
|
||||
return I18n.t('room_unarchived');
|
||||
case 'subscription-role-added':
|
||||
return I18n.t('Defined_user_as_role', { user: msg, role });
|
||||
case 'subscription-role-removed':
|
||||
return I18n.t('Removed_user_as_role', { user: msg, role });
|
||||
case 'message_pinned':
|
||||
return I18n.t('Message_pinned');
|
||||
|
||||
// without author name
|
||||
case 'ul':
|
||||
return I18n.t('User_left_this_channel');
|
||||
case 'ult':
|
||||
return I18n.t('Has_left_the_team');
|
||||
case 'jitsi_call_started':
|
||||
return I18n.t('Started_call', { userBy: username });
|
||||
case 'omnichannel_placed_chat_on_hold':
|
||||
return I18n.t('Omnichannel_placed_chat_on_hold', { comment });
|
||||
case 'omnichannel_on_hold_chat_resumed':
|
||||
return I18n.t('Omnichannel_on_hold_chat_resumed', { comment });
|
||||
case 'command':
|
||||
return I18n.t('Livechat_transfer_return_to_the_queue');
|
||||
case 'livechat-started':
|
||||
return I18n.t('Chat_started');
|
||||
case 'livechat-close':
|
||||
return I18n.t('Conversation_closed');
|
||||
case 'livechat_transfer_history':
|
||||
return I18n.t('New_chat_transfer', { agent: username });
|
||||
|
||||
// default value
|
||||
default:
|
||||
return I18n.t('Unsupported_system_message');
|
||||
}
|
||||
if (type === 'uj') {
|
||||
return I18n.t('Has_joined_the_channel');
|
||||
}
|
||||
if (type === 'ujt') {
|
||||
return I18n.t('Has_joined_the_team');
|
||||
}
|
||||
if (type === 'ut') {
|
||||
return I18n.t('Has_joined_the_conversation');
|
||||
}
|
||||
if (type === 'r') {
|
||||
return I18n.t('Room_name_changed', { name: msg, userBy: username });
|
||||
}
|
||||
if (type === 'message_pinned') {
|
||||
return I18n.t('Message_pinned');
|
||||
}
|
||||
if (type === 'jitsi_call_started') {
|
||||
return I18n.t('Started_call', { userBy: username });
|
||||
}
|
||||
if (type === 'ul') {
|
||||
return I18n.t('Has_left_the_channel');
|
||||
}
|
||||
if (type === 'ult') {
|
||||
return I18n.t('Has_left_the_team');
|
||||
}
|
||||
if (type === 'ru') {
|
||||
return I18n.t('User_removed_by', { userRemoved: msg, userBy: username });
|
||||
}
|
||||
if (type === 'au') {
|
||||
return I18n.t('User_added_by', { userAdded: msg, userBy: username });
|
||||
}
|
||||
if (type === 'user-muted') {
|
||||
return I18n.t('User_muted_by', { userMuted: msg, userBy: username });
|
||||
}
|
||||
if (type === 'user-unmuted') {
|
||||
return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: username });
|
||||
}
|
||||
if (type === 'subscription-role-added') {
|
||||
return `${msg} was set ${role} by ${username}`;
|
||||
}
|
||||
if (type === 'subscription-role-removed') {
|
||||
return `${msg} is no longer ${role} by ${username}`;
|
||||
}
|
||||
if (type === 'room_changed_description') {
|
||||
return I18n.t('Room_changed_description', { description: msg, userBy: username });
|
||||
}
|
||||
if (type === 'room_changed_announcement') {
|
||||
return I18n.t('Room_changed_announcement', { announcement: msg, userBy: username });
|
||||
}
|
||||
if (type === 'room_changed_topic') {
|
||||
return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
|
||||
}
|
||||
if (type === 'room_changed_privacy') {
|
||||
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
||||
}
|
||||
if (type === 'room_changed_avatar') {
|
||||
return I18n.t('Room_changed_avatar', { userBy: username });
|
||||
}
|
||||
if (type === 'message_snippeted') {
|
||||
return I18n.t('Created_snippet');
|
||||
}
|
||||
if (type === 'room_e2e_disabled') {
|
||||
return I18n.t('This_room_encryption_has_been_disabled_by__username_', { username });
|
||||
}
|
||||
if (type === 'room_e2e_enabled') {
|
||||
return I18n.t('This_room_encryption_has_been_enabled_by__username_', { username });
|
||||
}
|
||||
if (type === 'removed-user-from-team') {
|
||||
return I18n.t('Removed__username__from_team', { user_removed: msg });
|
||||
}
|
||||
if (type === 'added-user-to-team') {
|
||||
return I18n.t('Added__username__to_team', { user_added: msg });
|
||||
}
|
||||
if (type === 'user-added-room-to-team') {
|
||||
return I18n.t('added__roomName__to_team', { roomName: msg });
|
||||
}
|
||||
if (type === 'user-converted-to-team') {
|
||||
return I18n.t('Converted__roomName__to_team', { roomName: msg });
|
||||
}
|
||||
if (type === 'user-converted-to-channel') {
|
||||
return I18n.t('Converted__roomName__to_channel', { roomName: msg });
|
||||
}
|
||||
if (type === 'user-deleted-room-from-team') {
|
||||
return I18n.t('Deleted__roomName__', { roomName: msg });
|
||||
}
|
||||
if (type === 'user-removed-room-from-team') {
|
||||
return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
|
||||
}
|
||||
if (type === 'room-disallowed-reacting') {
|
||||
return I18n.t('Room_disallowed_reacting', { userBy: username });
|
||||
}
|
||||
if (type === 'room-allowed-reacting') {
|
||||
return I18n.t('Room_allowed_reacting', { userBy: username });
|
||||
}
|
||||
if (type === 'room-set-read-only') {
|
||||
return I18n.t('Room_set_read_only', { userBy: username });
|
||||
}
|
||||
if (type === 'room-removed-read-only') {
|
||||
return I18n.t('Room_removed_read_only', { userBy: username });
|
||||
}
|
||||
if (type === 'omnichannel_placed_chat_on_hold') {
|
||||
return I18n.t('Omnichannel_placed_chat_on_hold', { comment });
|
||||
}
|
||||
if (type === 'omnichannel_on_hold_chat_resumed') {
|
||||
return I18n.t('Omnichannel_on_hold_chat_resumed', { comment });
|
||||
}
|
||||
if (type === 'command') {
|
||||
return I18n.t('Livechat_transfer_return_to_the_queue');
|
||||
}
|
||||
if (type === 'livechat-started') {
|
||||
return I18n.t('Chat_started');
|
||||
}
|
||||
if (type === 'livechat-close') {
|
||||
return I18n.t('Conversation_closed');
|
||||
}
|
||||
if (type === 'livechat_transfer_history') {
|
||||
return I18n.t('New_chat_transfer', { agent: username });
|
||||
}
|
||||
return I18n.t('Unsupported_system_message');
|
||||
};
|
||||
|
||||
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
|
||||
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string): string | null => {
|
||||
if (!autoTranslateLanguage) {
|
||||
return null;
|
||||
}
|
||||
const { translations } = message;
|
||||
if (translations) {
|
||||
const translation = translations.find((trans: any) => trans.language === autoTranslateLanguage);
|
||||
return translation && translation.value;
|
||||
return translation?.value || null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -1,43 +1,35 @@
|
|||
import Model from '@nozbe/watermelondb/Model';
|
||||
import { StyleProp } from 'react-native';
|
||||
import { ImageStyle } from 'react-native-fast-image';
|
||||
|
||||
export interface IEmoji {
|
||||
export interface IFrequentlyUsedEmoji {
|
||||
content: string;
|
||||
name: string;
|
||||
extension: string;
|
||||
extension?: string;
|
||||
isCustom: boolean;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface ICustomEmojis {
|
||||
[key: string]: Pick<IEmoji, 'name' | 'extension'>;
|
||||
}
|
||||
type TBasicEmoji = string;
|
||||
|
||||
export interface ICustomEmoji {
|
||||
baseUrl?: string;
|
||||
emoji: IEmoji;
|
||||
style: StyleProp<ImageStyle>;
|
||||
name: string;
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export type IEmoji = ICustomEmoji | TBasicEmoji;
|
||||
|
||||
export interface ICustomEmojis {
|
||||
[key: string]: ICustomEmoji;
|
||||
}
|
||||
|
||||
export type TGetCustomEmoji = (name: string) => ICustomEmoji | null;
|
||||
|
||||
export type TFrequentlyUsedEmojiModel = IFrequentlyUsedEmoji & Model;
|
||||
|
||||
export interface ICustomEmojiModel {
|
||||
_id: string;
|
||||
name?: string;
|
||||
name: string;
|
||||
aliases?: string[];
|
||||
extension: string;
|
||||
_updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface IEmojiCategory {
|
||||
baseUrl: string;
|
||||
emojis: IEmoji[];
|
||||
onEmojiSelected: (emoji: IEmoji) => void;
|
||||
width: number | null;
|
||||
style: StyleProp<ImageStyle>;
|
||||
tabLabel: string;
|
||||
}
|
||||
|
||||
export type TGetCustomEmoji = (name: string) => any;
|
||||
|
||||
export type TFrequentlyUsedEmojiModel = IEmoji & Model;
|
||||
export type TCustomEmojiModel = ICustomEmojiModel & Model;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { TThreadMessageModel } from './IThreadMessage';
|
|||
import { TThreadModel } from './IThread';
|
||||
import { IUrl, IUrlFromServer } from './IUrl';
|
||||
|
||||
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj' | MessageTypeLoad;
|
||||
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj' | MessageTypeLoad | MessageTypesValues;
|
||||
|
||||
export interface IUserMessage {
|
||||
_id: string;
|
||||
|
@ -153,3 +153,81 @@ export interface IReadReceipts {
|
|||
ts: string;
|
||||
user?: IUserMessage;
|
||||
}
|
||||
|
||||
// from Rocket.Chat codebase
|
||||
type VoipMessageTypesValues =
|
||||
| 'voip-call-started'
|
||||
| 'voip-call-declined'
|
||||
| 'voip-call-on-hold'
|
||||
| 'voip-call-unhold'
|
||||
| 'voip-call-ended'
|
||||
| 'voip-call-duration'
|
||||
| 'voip-call-wrapup'
|
||||
| 'voip-call-ended-unexpectedly';
|
||||
|
||||
type TeamMessageTypes =
|
||||
| 'removed-user-from-team'
|
||||
| 'added-user-to-team'
|
||||
| 'ult'
|
||||
| 'user-converted-to-team'
|
||||
| 'user-converted-to-channel'
|
||||
| 'user-removed-room-from-team'
|
||||
| 'user-deleted-room-from-team'
|
||||
| 'user-added-room-to-team'
|
||||
| 'ujt';
|
||||
|
||||
type LivechatMessageTypes =
|
||||
| 'livechat_navigation_history'
|
||||
| 'livechat_transfer_history'
|
||||
| 'livechat_transcript_history'
|
||||
| 'livechat_video_call'
|
||||
| 'livechat_webrtc_video_call'
|
||||
| 'livechat-started';
|
||||
|
||||
type OmnichannelTypesValues =
|
||||
| 'livechat_transfer_history_fallback'
|
||||
| 'livechat-close'
|
||||
| 'omnichannel_placed_chat_on_hold'
|
||||
| 'omnichannel_on_hold_chat_resumed';
|
||||
|
||||
type OtrMessageTypeValues = 'otr' | 'otr-ack';
|
||||
type OtrSystemMessages = 'user_joined_otr' | 'user_requested_otr_key_refresh' | 'user_key_refreshed_successfully';
|
||||
|
||||
export type MessageTypesValues =
|
||||
| 'e2e'
|
||||
| 'uj'
|
||||
| 'ul'
|
||||
| 'ru'
|
||||
| 'au'
|
||||
| 'mute_unmute'
|
||||
| 'r'
|
||||
| 'ut'
|
||||
| 'wm'
|
||||
| 'rm'
|
||||
| 'subscription-role-added'
|
||||
| 'subscription-role-removed'
|
||||
| 'room-archived'
|
||||
| 'room-unarchived'
|
||||
| 'room_changed_privacy'
|
||||
| 'room_changed_description'
|
||||
| 'room_changed_announcement'
|
||||
| 'room_changed_avatar'
|
||||
| 'room_changed_topic'
|
||||
| 'room_e2e_enabled'
|
||||
| 'room_e2e_disabled'
|
||||
| 'user-muted'
|
||||
| 'user-unmuted'
|
||||
| 'room-removed-read-only'
|
||||
| 'room-set-read-only'
|
||||
| 'room-allowed-reacting'
|
||||
| 'room-disallowed-reacting'
|
||||
| 'command'
|
||||
| LivechatMessageTypes
|
||||
| TeamMessageTypes
|
||||
| VoipMessageTypesValues
|
||||
| OmnichannelTypesValues
|
||||
| OtrMessageTypeValues
|
||||
| OtrSystemMessages
|
||||
| 'message_pinned'
|
||||
| 'message_snippeted'
|
||||
| 'jitsi_call_started';
|
||||
|
|
|
@ -24,7 +24,7 @@ export type TeamsEndpoints = {
|
|||
'teams.create': {
|
||||
POST: (params: {
|
||||
name: string;
|
||||
users: string[];
|
||||
members: string[];
|
||||
type: TEAM_TYPE;
|
||||
room: { readOnly: boolean; extraData: { broadcast: boolean; encrypted: boolean } };
|
||||
}) => { team: ITeam };
|
||||
|
|
|
@ -26,6 +26,9 @@ export const DimensionsContext = React.createContext<IDimensionsContextProps>(
|
|||
Dimensions.get('window') as IDimensionsContextProps
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated use RN's useWindowDimensions hook instead
|
||||
*/
|
||||
export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
|
||||
const DimensionsComponent = (props: T) => (
|
||||
<DimensionsContext.Consumer>{contexts => <Component {...props} {...contexts} />}</DimensionsContext.Consumer>
|
||||
|
@ -35,8 +38,14 @@ export function withDimensions<T extends object>(Component: React.ComponentType<
|
|||
return DimensionsComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use RN's useWindowDimensions hook instead
|
||||
*/
|
||||
export const useDimensions = () => React.useContext(DimensionsContext);
|
||||
|
||||
/**
|
||||
* @deprecated use RN's useWindowDimensions hook instead
|
||||
*/
|
||||
export const useOrientation = () => {
|
||||
const { width, height } = React.useContext(DimensionsContext);
|
||||
const isPortrait = height > width;
|
||||
|
|
|
@ -244,9 +244,6 @@
|
|||
"Forward_to_user": "إعادة توجيه لمستخدم",
|
||||
"Full_table": "انقر لرؤية الجدول كاملاً",
|
||||
"Generate_New_Link": "إنشاء رابط جديد",
|
||||
"Has_joined_the_channel": "انضم إلى القناة",
|
||||
"Has_joined_the_conversation": "انضم إلى المحادثة",
|
||||
"Has_left_the_channel": "غادر القناة",
|
||||
"Hide_System_Messages": "إخفاء رسائل النظام",
|
||||
"Hide_type_messages": "إخفاء رسائل \"{{type}}\"",
|
||||
"How_It_Works": "طريقة العمل",
|
||||
|
@ -430,15 +427,11 @@
|
|||
"Roles": "أدوار",
|
||||
"Room_actions": "إجراءات الغرفة",
|
||||
"Room_changed_announcement": "تم تغيير إعلان الغرفة إلى: {{announcement}} من قبل {{userBy}}",
|
||||
"Room_changed_avatar": " {{userBy}}تم تغيير الصورة الرمزية للغرفة",
|
||||
"Room_changed_description": "تم تغيير وصف الغرفة إلى: {{description}} من قبل {{userBy}}",
|
||||
"Room_changed_privacy": "تم تغيير نوع الغرفة إلى: {{type}} من قبل {{userBy}}",
|
||||
"Room_changed_topic": "تم تغيير موضوع الغرفة إلى: {{topic}} من قبل {{userBy}}",
|
||||
"Room_Files": "ملفات الغرفة",
|
||||
"Room_Info_Edit": "تعديل معلومات الغرفة",
|
||||
"Room_Info": "معلومات الغرفة",
|
||||
"Room_Members": "أعضاء الغرفة",
|
||||
"Room_name_changed": "تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}",
|
||||
"SAVE": "حفظ",
|
||||
"Save_Changes": "حفظ التغيرات",
|
||||
"Save": "حفظ",
|
||||
|
@ -540,12 +533,9 @@
|
|||
"Upload_file_question_mark": "رفع الملف؟",
|
||||
"User": "مستخدم",
|
||||
"Users": "مستخدمين",
|
||||
"User_added_by": "مستخدم {{userAdded}} أضيف من قبل {{userBy}}",
|
||||
"User_Info": "معلومات المستخدم",
|
||||
"User_has_been_key": "تمت {{key}} المستخدم!",
|
||||
"User_is_no_longer_role_by_": "تم إزالة الدور {{role}} عن المستخدم {{user}} من قبل {{userBy}}",
|
||||
"User_muted_by": "المستخدم {{userMuted}} كتم من قبل {{userBy}}",
|
||||
"User_removed_by": "المستخدم {{userRemoved}} حذف من قبل {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} أرسل مرفقًا",
|
||||
"User_unmuted_by": "ألغى {{userBy}} الكتم عن {{userUnmuted}}",
|
||||
"User_was_set_role_by_": "أضيف دور {{role}} للمستخدم {{user}} من قبل {{userBy}}",
|
||||
|
|
|
@ -247,10 +247,6 @@
|
|||
"Forward_to_user": "Weiterleiten an Benutzer",
|
||||
"Full_table": "Klicken, um die ganze Tabelle anzuzeigen",
|
||||
"Generate_New_Link": "Neuen Link erstellen",
|
||||
"Has_joined_the_channel": "Ist dem Kanal beigetreten",
|
||||
"Has_joined_the_team": "ist dem Team beigetreten",
|
||||
"Has_joined_the_conversation": "hat sich dem Gespräch angeschlossen",
|
||||
"Has_left_the_channel": "Hat den CHannel verlassen",
|
||||
"Has_left_the_team": "Hat das Team verlassen",
|
||||
"Hide_System_Messages": "Systemnachrichten ausblenden",
|
||||
"Hide_type_messages": "\"{{type}}\"-Nachrichten ausblenden",
|
||||
|
@ -436,15 +432,11 @@
|
|||
"Roles": "Rollen",
|
||||
"Room_actions": "Room-Aktionen",
|
||||
"Room_changed_announcement": "Room-Ansage geändert in: {{announcement}} von {{userBy}}",
|
||||
"Room_changed_avatar": "Room-Avatar durch Nutzer {{userBy}} gändert",
|
||||
"Room_changed_description": "Room-beschreibung geändert in: {{description}} von {{userBy}}",
|
||||
"Room_changed_privacy": "Room-Typ geändert in: {{type}} von {{userBy}}",
|
||||
"Room_changed_topic": "Room-Thema geändert in: {{topic}} von {{userBy}}",
|
||||
"Room_Files": "Room-Dateien",
|
||||
"Room_Info_Edit": "Room-Info bearbeiten",
|
||||
"Room_Info": "Room-Info",
|
||||
"Room_Members": "Room-Mitglieder",
|
||||
"Room_name_changed": "Room-Name geändert in {{name}} von {{userBy}}",
|
||||
"SAVE": "SPEICHERN",
|
||||
"Save_Changes": "Änderungen speichern",
|
||||
"Save": "speichern",
|
||||
|
@ -546,12 +538,9 @@
|
|||
"Upload_file_question_mark": "Datei hochladen?",
|
||||
"User": "Benutzer",
|
||||
"Users": "Benutzer",
|
||||
"User_added_by": "Benutzer {{userAdded}} hinzugefügt von {{userBy}}",
|
||||
"User_Info": "Benutzerinfo",
|
||||
"User_has_been_key": "Benutzer wurde {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} ist nicht länger {{role}} von {{userBy}}",
|
||||
"User_muted_by": "Benutzer {{userMuted}} von {{userBy}} stummgeschaltet",
|
||||
"User_removed_by": "Benutzer {{userRemoved}} wurde von {{userBy}} entfernt",
|
||||
"User_sent_an_attachment": "{{user}}: eine Datei gesendet",
|
||||
"User_unmuted_by": "Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}",
|
||||
"User_was_set_role_by_": "{{user}} wurde von {{userBy}} {{role}} festgelegt.",
|
||||
|
@ -695,8 +684,6 @@
|
|||
"Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.",
|
||||
"Enter_workspace_URL": "Arbeitsbereich-URL",
|
||||
"Workspace_URL_Example": "z.B. https://rocketchat.deine-firma.de",
|
||||
"This_room_encryption_has_been_enabled_by__username_": "Die Verschlüsselung dieses Rooms wurde von {{username}} aktiviert",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "Die Verschlüsselung dieses Rooms wurde von {{username}} deaktiviert",
|
||||
"Teams": "Teams",
|
||||
"No_team_channels_found": "Keine Kanäle gefunden",
|
||||
"Team_not_found": "Team nicht gefunden",
|
||||
|
@ -793,8 +780,6 @@
|
|||
"Message_HideType_user_converted_to_team": "Nachrichten \"Benutzer hat Channel in Team konvertiert\" ausblenden",
|
||||
"Message_HideType_user_deleted_room_from_team": "Nachrichten \"Benutzer hat Room aus Team gelöscht\" ausblenden",
|
||||
"Message_HideType_user_removed_room_from_team": "Nachrichten \"Benutzer hat Room aus Team entfernt\" ausblenden",
|
||||
"Removed__roomName__from_this_team": "Sie entfernen {{roomName}} aus diesem Team",
|
||||
"Removed__username__from_team": "@{{user_removed}} aus diesem Team entfernt",
|
||||
"User_joined_team": "ist dem Team beigetreten",
|
||||
"User_left_team": "hat das Team verlassen"
|
||||
}
|
|
@ -257,10 +257,10 @@
|
|||
"Forward_to_user": "Forward to user",
|
||||
"Full_table": "Click to see full table",
|
||||
"Generate_New_Link": "Generate New Link",
|
||||
"Has_joined_the_channel": "has joined the channel",
|
||||
"Has_joined_the_team": "has joined the team",
|
||||
"Has_joined_the_conversation": "has joined the conversation",
|
||||
"Has_left_the_channel": "has left the channel",
|
||||
"User_joined_the_channel": "joined the channel",
|
||||
"User_joined_the_conversation": "joined the conversation",
|
||||
"User_joined_the_team": "joined this team",
|
||||
"User_left_this_channel": "left the channel",
|
||||
"Has_left_the_team": "has left the team",
|
||||
"Hide_System_Messages": "Hide System Messages",
|
||||
"Hide_type_messages": "Hide \"{{type}}\" messages",
|
||||
|
@ -326,7 +326,7 @@
|
|||
"Message_accessibility": "Message from {{user}} at {{time}}: {{message}}",
|
||||
"Message_actions": "Message actions",
|
||||
"Message_pinned": "Message pinned",
|
||||
"Message_removed": "Message removed",
|
||||
"Message_removed": "message removed",
|
||||
"Message_starred": "Message starred",
|
||||
"Message_unstarred": "Message unstarred",
|
||||
"message": "message",
|
||||
|
@ -450,19 +450,21 @@
|
|||
"Roles": "Roles",
|
||||
"Room_actions": "Room actions",
|
||||
"Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}",
|
||||
"Room_changed_avatar": "Room avatar changed by {{userBy}}",
|
||||
"room_avatar_changed": "changed room avatar",
|
||||
"Room_changed_description": "Room description changed to: {{description}} by {{userBy}}",
|
||||
"Room_changed_privacy": "Room type changed to: {{type}} by {{userBy}}",
|
||||
"Room_changed_topic": "Room topic changed to: {{topic}} by {{userBy}}",
|
||||
"changed_room_description": "changed room description to: {{description}}",
|
||||
"changed_room_announcement": "changed room announcement to: {{announcement}}",
|
||||
"room_changed_type": "changed room to {{type}}",
|
||||
"room_changed_topic_to": "changed room topic to: {{topic}}",
|
||||
"Room_Files": "Room Files",
|
||||
"Room_Info_Edit": "Room Info Edit",
|
||||
"Room_Info": "Room Info",
|
||||
"Room_Members": "Room Members",
|
||||
"Room_name_changed": "Room name changed to: {{name}} by {{userBy}}",
|
||||
"Room_disallowed_reacting": "Room disallowed reacting by {{userBy}}",
|
||||
"Room_allowed_reacting": "Room allowed reacting by {{userBy}}",
|
||||
"Room_set_read_only": "Room set read only by {{userBy}}",
|
||||
"Room_removed_read_only": "Room removed read only by {{userBy}}",
|
||||
"Room_name_changed_to": "changed room name to: {{name}}",
|
||||
"room_disallowed_reactions": "disallowed reactions",
|
||||
"room_allowed_reactions": "allowed reactions",
|
||||
"room_removed_read_only_permission": "removed read only permission",
|
||||
"room_set_read_only_permission": "set room to read only",
|
||||
"SAVE": "SAVE",
|
||||
"Save_Changes": "Save Changes",
|
||||
"Save": "Save",
|
||||
|
@ -478,6 +480,7 @@
|
|||
"Search_Messages": "Search Messages",
|
||||
"Search": "Search",
|
||||
"Search_by": "Search by",
|
||||
"Search_emoji": "Search emoji",
|
||||
"Search_global_users": "Search for global users",
|
||||
"Search_global_users_description": "If you turn-on, you can search for any user from others companies or servers.",
|
||||
"Seconds": "{{second}} seconds",
|
||||
|
@ -565,15 +568,16 @@
|
|||
"Upload_file_question_mark": "Upload file?",
|
||||
"User": "User",
|
||||
"Users": "Users",
|
||||
"User_added_by": "User {{userAdded}} added by {{userBy}}",
|
||||
"User_added_to": "added {{userAdded}}",
|
||||
"User_Info": "User Info",
|
||||
"User_has_been_key": "User has been {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} is no longer {{role}} by {{userBy}}",
|
||||
"User_muted_by": "User {{userMuted}} muted by {{userBy}}",
|
||||
"User_removed_by": "User {{userRemoved}} removed by {{userBy}}",
|
||||
"User_has_been_muted": "muted {{userMuted}}",
|
||||
"User_has_been_removed": "removed {{userRemoved}}",
|
||||
"User_sent_an_attachment": "{{user}} sent an attachment",
|
||||
"User_unmuted_by": "User {{userUnmuted}} unmuted by {{userBy}}",
|
||||
"User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}",
|
||||
"User_has_been_unmuted": "unmuted {{userUnmuted}}",
|
||||
"Defined_user_as_role": "defined {{user}} as {{role}}",
|
||||
"Removed_user_as_role": "removed {{user}} as {{role}}",
|
||||
"Username_is_empty": "Username is empty",
|
||||
"Username": "Username",
|
||||
"Username_or_email": "Username or email",
|
||||
|
@ -715,8 +719,8 @@
|
|||
"Message_Ignored": "Message ignored. Tap to display it.",
|
||||
"Enter_workspace_URL": "Enter workspace URL",
|
||||
"Workspace_URL_Example": "Ex. your-company.rocket.chat",
|
||||
"This_room_encryption_has_been_enabled_by__username_": "This room's encryption has been enabled by {{username}}",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}",
|
||||
"Enabled_E2E_Encryption_for_this_room": "enabled E2E Encryption for this room",
|
||||
"Disabled_E2E_Encryption_for_this_room": "disabled E2E Encryption for this room",
|
||||
"Teams": "Teams",
|
||||
"No_team_channels_found": "No channels found",
|
||||
"Team_not_found": "Team not found",
|
||||
|
@ -800,11 +804,11 @@
|
|||
"Unsupported_format": "Unsupported format",
|
||||
"Downloaded_file": "Downloaded file",
|
||||
"Error_Download_file": "Error while downloading file",
|
||||
"added__roomName__to_team": "added #{{roomName}} to this Team",
|
||||
"Added__username__to_team": "added @{{user_added}} to this Team",
|
||||
"Converted__roomName__to_team": "converted #{{roomName}} to a Team",
|
||||
"Converted__roomName__to_channel": "converted #{{roomName}} to a Channel",
|
||||
"added__roomName__to_this_team": "added #{{roomName}} to this team",
|
||||
"Added__username__to_this_team": "added @{{user_added}} to this team",
|
||||
"Converting_team_to_channel": "Converting Team to Channel",
|
||||
"Converted__roomName__to_a_team": "converted #{{roomName}} to a team",
|
||||
"Converted__roomName__to_a_channel": "converted #{{roomName}} to channel",
|
||||
"Deleted__roomName__": "deleted #{{roomName}}",
|
||||
"Message_HideType_added_user_to_team": "Hide \"User Added to Team\" messages",
|
||||
"Message_HideType_removed_user_from_team": "Hide \"User Removed from Team\" messages",
|
||||
|
@ -815,10 +819,10 @@
|
|||
"Message_HideType_user_converted_to_team": "Hide \"User converted channel to a Team\" messages",
|
||||
"Message_HideType_user_deleted_room_from_team": "Hide \"User deleted room from Team\" messages",
|
||||
"Message_HideType_user_removed_room_from_team": "Hide \"User removed room from Team\" messages",
|
||||
"Removed__roomName__from_this_team": "removed #{{roomName}} from this Team",
|
||||
"Removed__username__from_team": "removed @{{user_removed}} from this Team",
|
||||
"Removed__roomName__from_the_team": "removed #{{roomName}} from this team",
|
||||
"Removed__username__from_the_team": "removed @{{userRemoved}} from this team",
|
||||
"User_joined_team": "joined this Team",
|
||||
"User_left_team": "left this Team",
|
||||
"User_left_team": "left this team",
|
||||
"Place_chat_on_hold": "Place chat on-hold",
|
||||
"Would_like_to_place_on_hold": "Would you like to place this chat On-Hold?",
|
||||
"Open_Livechats": "Omnichannel chats in progress",
|
||||
|
@ -857,5 +861,7 @@
|
|||
"Team": "Team",
|
||||
"Select_Members": "Select Members",
|
||||
"Also_send_thread_message_to_channel_behavior": "Also send thread message to channel",
|
||||
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior"
|
||||
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior",
|
||||
"room_archived": "archived room",
|
||||
"room_unarchived": "unarchived room"
|
||||
}
|
|
@ -185,9 +185,6 @@
|
|||
"Forgot_password": "¿Ha olvidado su contraseña?",
|
||||
"Forgot_Password": "Olvidé la contraseña",
|
||||
"Full_table": "Click para ver la tabla completa",
|
||||
"Has_joined_the_channel": "se ha unido al canal",
|
||||
"Has_joined_the_conversation": "se ha unido a la conversación",
|
||||
"Has_left_the_channel": "ha dejado el canal",
|
||||
"In_App_And_Desktop": "En la aplicación y en el escritorio",
|
||||
"In_App_and_Desktop_Alert_info": "Muestra un banner en la parte superior de la pantalla cuando la aplicación esté abierta y muestra una notificación en el escritorio",
|
||||
"Invisible": "Invisible",
|
||||
|
@ -305,13 +302,10 @@
|
|||
"Room_actions": "Acciones de sala",
|
||||
"Room_changed_announcement": "El anuncio de la sala cambió a: {{announcement}} por {{userBy}}",
|
||||
"Room_changed_description": "La descripción de la sala cambió a: {{description}} por {{userBy}}",
|
||||
"Room_changed_privacy": "El tipo de la sala cambió a: {{type}} por {{userBy}}",
|
||||
"Room_changed_topic": "El asunto de la sala cambió a: {{topic}} por {{userBy}}",
|
||||
"Room_Files": "Archivos",
|
||||
"Room_Info_Edit": "Editar información de la sala",
|
||||
"Room_Info": "Información de la sala",
|
||||
"Room_Members": "Miembros de la sala",
|
||||
"Room_name_changed": "El nombre de la sala cambió a: {{name}} por {{userBy}}",
|
||||
"SAVE": "GUARDAR",
|
||||
"Save_Changes": "Guardar cambios",
|
||||
"Save": "Guardar",
|
||||
|
@ -389,11 +383,8 @@
|
|||
"Uploading": "Subiendo",
|
||||
"Upload_file_question_mark": "¿Subir fichero?",
|
||||
"Users": "Usuarios",
|
||||
"User_added_by": "Usuario {{userAdded}} añadido por {{userBy}}",
|
||||
"User_has_been_key": "El usuario ha sido {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} ha dejado de ser {{role}} por {{userBy}}",
|
||||
"User_muted_by": "Usuario {{userMuted}} muteado por {{userBy}}",
|
||||
"User_removed_by": "Usuario {{userRemoved}} eliminado por {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} envío un adjunto",
|
||||
"User_unmuted_by": "Usuario {{userUnmuted}} desmuteado por {{userBy}}",
|
||||
"User_was_set_role_by_": "{{user}} ha comenzado a ser {{role}} por {{userBy}}",
|
||||
|
|
|
@ -249,10 +249,6 @@
|
|||
"Forward_to_user": "Transmettre à l'utilisateur",
|
||||
"Full_table": "Cliquez pour voir le tableau complet",
|
||||
"Generate_New_Link": "Générer un nouveau lien",
|
||||
"Has_joined_the_channel": "a rejoint le canal",
|
||||
"Has_joined_the_team": "a rejoint l'équipe",
|
||||
"Has_joined_the_conversation": "a rejoint la conversation",
|
||||
"Has_left_the_channel": "a quitté le canal",
|
||||
"Has_left_the_team": "a quitté l'équipe",
|
||||
"Hide_System_Messages": "Masquer les messages système",
|
||||
"Hide_type_messages": "Masquer les messages \"{{type}}\"",
|
||||
|
@ -440,19 +436,11 @@
|
|||
"Roles": "Rôles",
|
||||
"Room_actions": "Actions du salon",
|
||||
"Room_changed_announcement": "Annonce du salon changé en : {{announcement}} par {{userBy}}",
|
||||
"Room_changed_avatar": "Avatar du salon modifié par {{userBy}}",
|
||||
"Room_changed_description": "Description du salon changé en : {{description}} par {{userBy}}",
|
||||
"Room_changed_privacy": "Type de salon changé en : {{type}} par {{userBy}}",
|
||||
"Room_changed_topic": "Le sujet de salon est changé en : {{topic}} par {{userBy}}",
|
||||
"Room_Files": "Fichiers du salon",
|
||||
"Room_Info_Edit": "Modifier les informations du salon",
|
||||
"Room_Info": "Info sur le salon",
|
||||
"Room_Members": "Membres du salon",
|
||||
"Room_name_changed": "Nom de salon changé en : {{name}} par {{userBy}}",
|
||||
"Room_disallowed_reacting": "Réactions non autorisées par {{userBy}}",
|
||||
"Room_allowed_reacting": "Réactions autorisées dans le salon par {{userBy}}",
|
||||
"Room_set_read_only": "Salon mis en lecture seule par {{userBy}}",
|
||||
"Room_removed_read_only": "Salon n'est plus en lecture seule par {{userBy}}",
|
||||
"SAVE": "SAUVEGARDER",
|
||||
"Save_Changes": "Sauvegarder les modifications",
|
||||
"Save": "Sauvegarder",
|
||||
|
@ -555,12 +543,9 @@
|
|||
"Upload_file_question_mark": "Téléverser un fichier ?",
|
||||
"User": "Utilisateur",
|
||||
"Users": "Utilisateurs",
|
||||
"User_added_by": "Utilisateur {{userAdded}} ajouté par {{userBy}}",
|
||||
"User_Info": "Info d'utilisateur",
|
||||
"User_has_been_key": "L'utilisateur a été {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} n'est plus {{role}} par {{userBy}}",
|
||||
"User_muted_by": "L'utilisateur {{userMuted}} a été rendu muet par {{userBy}}",
|
||||
"User_removed_by": "Utilisateur {{userRemoved}} supprimé par {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} a envoyé une pièce jointe",
|
||||
"User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole à {{userUnmuted}}",
|
||||
"User_was_set_role_by_": "{{user}} a été défini {{role}} par {{userBy}}",
|
||||
|
@ -705,8 +690,6 @@
|
|||
"Message_Ignored": "Message ignoré. Touchez pour l'afficher.",
|
||||
"Enter_workspace_URL": "Entrez l'URL de l'espace de travail",
|
||||
"Workspace_URL_Example": "Ex. votre-société.rocket.chat",
|
||||
"This_room_encryption_has_been_enabled_by__username_": "Le cryptage de ce salon a été activé par {{username}}",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "Le cryptage de ce salon a été désactivé par {{username}}",
|
||||
"Teams": "Equipes",
|
||||
"No_team_channels_found": "Aucun canal trouvé",
|
||||
"Team_not_found": "Equipe non trouvée",
|
||||
|
@ -803,8 +786,6 @@
|
|||
"Message_HideType_user_converted_to_team": "Masquer les messages \"L'utilisateur a converti le canal en équipe\"",
|
||||
"Message_HideType_user_deleted_room_from_team": "Masquer les messages \"L'utilisateur a supprimé le salon de l'équipe\"",
|
||||
"Message_HideType_user_removed_room_from_team": "Masquer les messages \"L'utilisateur a enlevé le salon de l'équipe\"",
|
||||
"Removed__roomName__from_this_team": "#{{roomName}} supprimé de cette équipe",
|
||||
"Removed__username__from_team": "@{{user_removed}} supprimé de cette équipe",
|
||||
"User_joined_team": "a rejoint cette équipe",
|
||||
"User_left_team": "a quitté cette équipe",
|
||||
"Place_chat_on_hold": "Mettre le chat en attente",
|
||||
|
|
|
@ -241,9 +241,6 @@
|
|||
"Forward_to_user": "Inoltra ad udente",
|
||||
"Full_table": "Clicca per la tabella completa",
|
||||
"Generate_New_Link": "Genera nuovo link",
|
||||
"Has_joined_the_channel": "si è unito al canale",
|
||||
"Has_joined_the_conversation": "si è unito alla conversazione",
|
||||
"Has_left_the_channel": "ha lasciato il canale",
|
||||
"Hide_System_Messages": "Nascondi messaggi di sistema",
|
||||
"Hide_type_messages": "Nascondi messaggi di \"{{type}}\"",
|
||||
"How_It_Works": "Come funziona",
|
||||
|
@ -424,15 +421,11 @@
|
|||
"Roles": "Ruoli",
|
||||
"Room_actions": "Azioni stanza",
|
||||
"Room_changed_announcement": "Annuncio stanza cambiato in: {{announcement}} da {{userBy}}",
|
||||
"Room_changed_avatar": "Immagine stanza cambiata da {{userBy}}",
|
||||
"Room_changed_description": "Descrizione stanza cambiata in: {{description}} da {{userBy}}",
|
||||
"Room_changed_privacy": "Tipo stanza cambiato in: {{type}} da {{userBy}}",
|
||||
"Room_changed_topic": "Argomento stanza cambiato in: {{topic}} da {{userBy}}",
|
||||
"Room_Files": "File stanza",
|
||||
"Room_Info_Edit": "Modifica informazioni stanza",
|
||||
"Room_Info": "Informazioni stanza",
|
||||
"Room_Members": "Membri stanza",
|
||||
"Room_name_changed": "Nome stanza cambiato in: {{name}} da {{userBy}}",
|
||||
"SAVE": "SALVA",
|
||||
"Save_Changes": "Salva cambiamenti",
|
||||
"Save": "Salva",
|
||||
|
@ -534,12 +527,9 @@
|
|||
"Upload_file_question_mark": "Carica file?",
|
||||
"User": "Utente",
|
||||
"Users": "Utenti",
|
||||
"User_added_by": "Utente {{userAdded}} aggiunto da {{userBy}}",
|
||||
"User_Info": "Informazioni utente",
|
||||
"User_has_been_key": "Utente {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} non è più {{role}} da {{userBy}}",
|
||||
"User_muted_by": "Utente {{userMuted}} silenziato da {{userBy}}",
|
||||
"User_removed_by": "Utente {{userRemoved}} rimosso da {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} ha inviato un allegato",
|
||||
"User_unmuted_by": "Utente {{userUnmuted}} de-silenziato da {{userBy}}",
|
||||
"User_was_set_role_by_": "{{user}} è stato impostato come {{role}} da {{userBy}}",
|
||||
|
|
|
@ -247,10 +247,6 @@
|
|||
"Forward_to_user": "ユーザーに転送する",
|
||||
"Full_table": "クリックしてテーブル全体を見る",
|
||||
"Generate_New_Link": "新しいリンクを生成",
|
||||
"Has_joined_the_channel": "はチャンネルに参加しました",
|
||||
"Has_joined_the_team": "チームに参加しました",
|
||||
"Has_joined_the_conversation": "は会話に参加しました",
|
||||
"Has_left_the_channel": "はチャンネルを退出しました",
|
||||
"Has_left_the_team": "チームを退出しました",
|
||||
"Hide_System_Messages": "システムメッセージを非表示にする",
|
||||
"Hide_type_messages": "\"{{type}}\"メッセージを非表示にする",
|
||||
|
@ -410,13 +406,10 @@
|
|||
"Room_actions": "ルームアクション",
|
||||
"Room_changed_announcement": "{{userBy}}がアナウンスを変更しました: {{announcement}}",
|
||||
"Room_changed_description": "{{userBy}}が概要を変更しました: {{description}}",
|
||||
"Room_changed_privacy": "{{userBy}}がルームタイプを変更しました。: {{type}}",
|
||||
"Room_changed_topic": "{{userBy}}がトピックを変更しました: {{topic}}",
|
||||
"Room_Files": "ルームのファイル",
|
||||
"Room_Info_Edit": "ルーム情報を編集",
|
||||
"Room_Info": "ルーム情報",
|
||||
"Room_Members": "ルームのメンバー",
|
||||
"Room_name_changed": "ルーム名が{{userBy}}により変更されました: {{name}}",
|
||||
"SAVE": "保存",
|
||||
"Save_Changes": "変更を保存",
|
||||
"Save": "保存",
|
||||
|
@ -498,12 +491,9 @@
|
|||
"Upload_file_question_mark": "ファイルをアップロードしますか?",
|
||||
"User": "ユーザー",
|
||||
"Users": "ユーザー",
|
||||
"User_added_by": "{{userBy}} が {{userAdded}} を追加しました",
|
||||
"User_Info": "ユーザー情報",
|
||||
"User_has_been_key": "ユーザーは{{key}}",
|
||||
"User_is_no_longer_role_by_": "{{userBy}} は {{user}} のロール {{role}} を削除しました。",
|
||||
"User_muted_by": "{{userBy}} は {{userMuted}} をミュートしました。",
|
||||
"User_removed_by": "{{userBy}} は {{userRemoved}} を退出させました。",
|
||||
"User_sent_an_attachment": "{{user}}は添付ファイルを送信しました",
|
||||
"User_unmuted_by": "{{userUnmuted}} は {{userBy}} にミュート解除されました。",
|
||||
"User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}",
|
||||
|
|
|
@ -249,10 +249,6 @@
|
|||
"Forward_to_user": "Doorsturen naar gebruiker",
|
||||
"Full_table": "Klik om de volledige tabel te zien",
|
||||
"Generate_New_Link": "Nieuwe link genereren",
|
||||
"Has_joined_the_channel": "is bij het kanaal gekomen",
|
||||
"Has_joined_the_team": "is lid geworden van het team",
|
||||
"Has_joined_the_conversation": "heeft zich bij het gesprek aangesloten",
|
||||
"Has_left_the_channel": "heeft het kanaal verlaten",
|
||||
"Has_left_the_team": "heeft het team verlaten",
|
||||
"Hide_System_Messages": "Verberg systeemberichten",
|
||||
"Hide_type_messages": "Verberg \"{{type}}\" berichten",
|
||||
|
@ -440,19 +436,12 @@
|
|||
"Roles": "Rollen",
|
||||
"Room_actions": "Kameracties",
|
||||
"Room_changed_announcement": "Kameraankondiging gewijzigd in: {{announcement}} door {{userBy}}",
|
||||
"Room_changed_avatar": "Kameravatar gewijzigd door {{userBy}}",
|
||||
"Room_changed_description": "Kamerbeschrijving gewijzigd in: {{description}} door {{userBy}}",
|
||||
"Room_changed_privacy": "Kamertype gewijzigd in: {{type}} door {{userBy}}",
|
||||
"Room_changed_topic": "Oonderwerp van kamer gewijzigd in: {{topic}} door {{userBy}}",
|
||||
"Room_Files": "Kamerbestanden",
|
||||
"Room_Info_Edit": "Kamer info bewerken",
|
||||
"Room_Info": "Kamer info",
|
||||
"Room_Members": "Kamerleden",
|
||||
"Room_name_changed": "Kamernaam gewijzigd in: {{name}} door {{userBy}}",
|
||||
"Room_disallowed_reacting": "Niet toegestaan om in de kamer te reageren door {{userBy}}",
|
||||
"Room_allowed_reacting": "Reageren toegestaan in de kamer door {{userBy}}",
|
||||
"Room_set_read_only": "Kamer als alleen lezen ingesteld door {{userBy}}",
|
||||
"Room_removed_read_only": "Kamer is niet meer als alleen lezen ingesteld door {{userBy}}",
|
||||
"SAVE": "OPSLAAN",
|
||||
"Save_Changes": "Wijzigingen opslaan",
|
||||
"Save": "Opslaan",
|
||||
|
@ -555,12 +544,9 @@
|
|||
"Upload_file_question_mark": "Bestand uploaden?",
|
||||
"User": "Gebruiker",
|
||||
"Users": "Gebruikers",
|
||||
"User_added_by": "Gebruiker {{userAdded}} toegevoegd door {{userBy}}",
|
||||
"User_Info": "Gebruikers info",
|
||||
"User_has_been_key": "Gebruiker is {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} is niet langer {{role}} door {{userBy}}",
|
||||
"User_muted_by": "Gebruiker {{userMuted}} gedempt door {{userBy}}",
|
||||
"User_removed_by": "Gebruiker {{userRemoved}} verwijderd door {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} stuurde een bijlage",
|
||||
"User_unmuted_by": "Dempen voor {{userUnmuted}} opgeheven door {{userBy}}",
|
||||
"User_was_set_role_by_": "{{user}} is als {{role}} ingesteld door {{userBy}}",
|
||||
|
@ -705,8 +691,6 @@
|
|||
"Message_Ignored": "Bericht genegeerd. Tik om het weer te geven.",
|
||||
"Enter_workspace_URL": "Voer de werkruimte-URL in",
|
||||
"Workspace_URL_Example": "Vb. uw-bedrijf.rocket.chat",
|
||||
"This_room_encryption_has_been_enabled_by__username_": "De versleuteling van deze kamer is ingeschakeld door {{username}}",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "De versleuteling van deze kamer is uitgeschakeld door {{username}}",
|
||||
"Teams": "Teams",
|
||||
"No_team_channels_found": "Geen kanalen gevonden",
|
||||
"Team_not_found": "Team niet gevonden",
|
||||
|
@ -803,8 +787,6 @@
|
|||
"Message_HideType_user_converted_to_team": "Verberg \"Gebruiker heeft kanaal in team geconverteerd\" berichten",
|
||||
"Message_HideType_user_deleted_room_from_team": "Verberg \"Gebruiker heeft kamer uit team verwijderd\" berichten",
|
||||
"Message_HideType_user_removed_room_from_team": "Verberg \"Gebruiker heeft kamer uit team verwijderd\" berichten",
|
||||
"Removed__roomName__from_this_team": "#{{roomName}} uit dit team verwijderd",
|
||||
"Removed__username__from_team": "@{{user_removed}} uit dit team verwijderd",
|
||||
"User_joined_team": "is lid geworden van dit team",
|
||||
"User_left_team": "heeft dit team verlaten",
|
||||
"Place_chat_on_hold": "Chat in de wacht zetten",
|
||||
|
|
|
@ -248,10 +248,10 @@
|
|||
"Forward_to_user": "Encaminhar para usuário",
|
||||
"Full_table": "Clique para ver a tabela completa",
|
||||
"Generate_New_Link": "Gerar novo convite",
|
||||
"Has_joined_the_channel": "entrou no canal",
|
||||
"Has_joined_the_team": "entrou na equipe",
|
||||
"Has_joined_the_conversation": "entrou na conversa",
|
||||
"Has_left_the_channel": "saiu da conversa",
|
||||
"User_joined_the_channel": "entrou no canal",
|
||||
"User_joined_the_team": "entrou na equipe",
|
||||
"User_joined_the_conversation": "entrou na conversa",
|
||||
"User_left_this_channel": "saiu da conversa",
|
||||
"Has_left_the_team": "saiu da equipe",
|
||||
"Hide_System_Messages": "Esconder mensagens do sistema",
|
||||
"Hide_type_messages": "Esconder mensagens de \"{{type}}\"",
|
||||
|
@ -309,7 +309,7 @@
|
|||
"Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}",
|
||||
"Message_actions": "Ações",
|
||||
"Message_pinned": "Fixou uma mensagem",
|
||||
"Message_removed": "Mensagem removida",
|
||||
"Message_removed": "mensagem removida",
|
||||
"message": "mensagem",
|
||||
"messages": "mensagens",
|
||||
"Message": "Mensagem",
|
||||
|
@ -382,6 +382,7 @@
|
|||
"Preferences": "Preferências",
|
||||
"Preferences_saved": "Preferências salvas!",
|
||||
"Privacy_Policy": " Política de Privacidade",
|
||||
"Private_Channel": "Canal Privado",
|
||||
"Private": "Privado",
|
||||
"Processing": "Processando...",
|
||||
"Profile_saved_successfully": "Perfil salvo com sucesso!",
|
||||
|
@ -426,19 +427,21 @@
|
|||
"Roles": "Papéis",
|
||||
"Room_actions": "Ações",
|
||||
"Room_changed_announcement": "O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}",
|
||||
"Room_changed_avatar": "Avatar da sala alterado por {{userBy}}",
|
||||
"room_avatar_changed": "alterou avatar da sala",
|
||||
"Room_changed_description": "A descrição da sala foi alterada para: {{description}} por {{userBy}}",
|
||||
"Room_changed_privacy": "Tipo da sala mudou para: {{type}} por {{userBy}}",
|
||||
"Room_changed_topic": "Tópico da sala mudou para: {{topic}} por {{userBy}}",
|
||||
"changed_room_description": "alterou a descrição da sala para: {{description}}",
|
||||
"changed_room_announcement": "alterou o anúncio da sala para: {{announcement}}",
|
||||
"room_changed_type": "mudou sala para {{type}}",
|
||||
"room_changed_topic_to": "mudou tópico da sala para: {{topic}}",
|
||||
"Room_Files": "Arquivos",
|
||||
"Room_Info_Edit": "Editar",
|
||||
"Room_Info": "Informações da Sala",
|
||||
"Room_Members": "Membros",
|
||||
"Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}",
|
||||
"Room_disallowed_reacting": "Permissão de reagir removida da sala por {{userBy}}",
|
||||
"Room_allowed_reacting": "Permissão de reagir adicionada à sala por {{userBy}}",
|
||||
"Room_set_read_only": "Sala definida como somente leitura por {{userBy}}",
|
||||
"Room_removed_read_only": "Permissão de escrita adicionada à sala por {{userBy}}",
|
||||
"Room_name_changed_to": "alterou o nome da sala para: {{name}}",
|
||||
"room_disallowed_reactions": "removeu a permissão de reagir",
|
||||
"room_allowed_reactions": "adicionou permissão de reagir",
|
||||
"room_removed_read_only_permission": "removeu permissão de escrita da sala",
|
||||
"room_set_read_only_permission": "adicionou permissão de escrita à sala",
|
||||
"SAVE": "SALVAR",
|
||||
"Save_Changes": "Salvar Alterações",
|
||||
"Save": "Salvar",
|
||||
|
@ -454,6 +457,7 @@
|
|||
"Search_Messages": "Buscar Mensagens",
|
||||
"Search": "Buscar",
|
||||
"Search_by": "Buscar por",
|
||||
"Search_emoji": "Buscar emoji",
|
||||
"Search_global_users": "Busca por usuários globais",
|
||||
"Search_global_users_description": "Caso ativado, busca por usuários de outras empresas ou servidores.",
|
||||
"Security_and_privacy": "Segurança e privacidade",
|
||||
|
@ -530,14 +534,14 @@
|
|||
"Upload_file_question_mark": "Enviar arquivo?",
|
||||
"User": "Usuário",
|
||||
"Users": "Usuários",
|
||||
"User_added_by": "Usuário {{userAdded}} adicionado por {{userBy}}",
|
||||
"User_added_to": "adicionou o usuário {{userAdded}}",
|
||||
"User_Info": "Informações do usuário",
|
||||
"User_has_been_key": "Usuário foi {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}",
|
||||
"User_muted_by": "User {{userMuted}} muted por {{userBy}}",
|
||||
"User_removed_by": "Usuário {{userRemoved}} removido por {{userBy}}",
|
||||
"User_has_been_muted": "silenciou o usuário {{userMuted}}",
|
||||
"User_has_been_removed": "removeu {{userRemoved}}",
|
||||
"User_sent_an_attachment": "{{user}} enviou um anexo",
|
||||
"User_unmuted_by": "{{userBy}} permitiu que {{userUnmuted}} fale na sala",
|
||||
"User_has_been_unmuted": "permitiu que {{userUnmuted}} fale na sala",
|
||||
"User_was_set_role_by_": "{{user}} foi definido como {{role}} por {{userBy}}",
|
||||
"Username_is_empty": "Usuário está vazio",
|
||||
"Username": "Usuário",
|
||||
|
@ -674,8 +678,8 @@
|
|||
"Message_Ignored": "Mensagem ignorada. Toque para mostrar.",
|
||||
"Enter_workspace_URL": "Digite a URL da sua workspace",
|
||||
"Workspace_URL_Example": "Ex. sua-empresa.rocket.chat",
|
||||
"This_room_encryption_has_been_enabled_by__username_": "A criptografia para essa sala foi habilitada por {{username}}",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}",
|
||||
"Enabled_E2E_Encryption_for_this_room": "habilitou criptografia para essa sala",
|
||||
"Disabled_E2E_Encryption_for_this_room": "desabilitou criptografia para essa sala",
|
||||
"Teams": "Times",
|
||||
"No_team_channels_found": "Nenhum canal encontrado",
|
||||
"Team_not_found": "Time não encontrado",
|
||||
|
@ -756,11 +760,11 @@
|
|||
"Downloaded_file": "Arquivo baixado",
|
||||
"Error_Download_file": "Erro ao baixar o arquivo",
|
||||
"error-init-video-conf": "Erro ao iniciar chamada de video",
|
||||
"added__roomName__to_team": "#{{roomName}} adicionada a esta equipe",
|
||||
"Added__username__to_team": "@{{user_added}} adicionado a esta equipe",
|
||||
"Converted__roomName__to_team": "#{{roomName}} convertida em equipe",
|
||||
"Converted__roomName__to_channel": "#{{roomName}} convertida em canal",
|
||||
"added__roomName__to_this_team": "adicionou #{{roomName}} a esta equipe",
|
||||
"Added__username__to_this_team": "adicionou @{{user_added}} a esta equipe",
|
||||
"Converting_team_to_channel": "Convertendo equipe em canal",
|
||||
"Converted__roomName__to_a_team": "converteu #{{roomName}} em equipe",
|
||||
"Converted__roomName__to_a_channel": "converteu #{{roomName}} em canal",
|
||||
"Deleted__roomName__": "#{{roomName}} apagada",
|
||||
"Message_HideType_added_user_to_team": "Ocultar mensagens de \"Usuário adicionado à equipe\"",
|
||||
"Message_HideType_removed_user_from_team": "Ocultar mensagens de \"Usuário removido da equipe\"",
|
||||
|
@ -771,8 +775,8 @@
|
|||
"Message_HideType_user_converted_to_team": "Ocultar mensagens de \"Usuário converteu canal em equipe\"",
|
||||
"Message_HideType_user_deleted_room_from_team": "Ocultar mensagens de \"Usuário apagou sala da equipe\"",
|
||||
"Message_HideType_user_removed_room_from_team": "Ocultar mensagens de \"Usuário removeu sala da equipe\"",
|
||||
"Removed__roomName__from_this_team": "#{{roomName}} removida desta equipe",
|
||||
"Removed__username__from_team": "@{{user_removed}} removido desta equipe",
|
||||
"Removed__roomName__from_the_team": "removeu #{{roomName}} desta equipe",
|
||||
"Removed__username__from_the_team": "removeu @{{userRemoved}} desta equipe",
|
||||
"User_joined_team": "entrou nesta equipe",
|
||||
"User_left_team": "saiu desta equipe",
|
||||
"Place_chat_on_hold": "Colocar conversa em espera",
|
||||
|
@ -812,5 +816,7 @@
|
|||
"Team": "Time",
|
||||
"Select_Members": "Selecionar Membros",
|
||||
"Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal",
|
||||
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal"
|
||||
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal",
|
||||
"room_archived": "{{username}} arquivou a sala",
|
||||
"room_unarchived": "{{username}} desarquivou a sala"
|
||||
}
|
|
@ -246,9 +246,6 @@
|
|||
"Forward_to_user": "Reencaminhar para o utilizador",
|
||||
"Full_table": "Clique para ver a tabela completa",
|
||||
"Generate_New_Link": "Gerar Novo Link",
|
||||
"Has_joined_the_channel": "entrou no canal",
|
||||
"Has_joined_the_conversation": "entrou na conversa",
|
||||
"Has_left_the_channel": "saiu do canal",
|
||||
"Hide_System_Messages": "Esconder mensagens do sistema",
|
||||
"Hide_type_messages": "Esconder mensagens \"{{type}}\"",
|
||||
"How_It_Works": "Como Funciona",
|
||||
|
@ -416,13 +413,10 @@
|
|||
"Room_actions": "Ações de sala",
|
||||
"Room_changed_announcement": "Anúncio da sala alterado para: {{announcement}} por {{userBy}}",
|
||||
"Room_changed_description": "Descrição da sala alterada para: {{description}} por {{userBy}}",
|
||||
"Room_changed_privacy": "Tipo de sala alterado para: {{type}} por {{userBy}}",
|
||||
"Room_changed_topic": "Tópico da sala alterado para: {{topic}} por {{userBy}}",
|
||||
"Room_Files": "Fiheiros da Sala",
|
||||
"Room_Info_Edit": "Editar Informação da Sala",
|
||||
"Room_Info": "Informação da Sala",
|
||||
"Room_Members": "Membros da Sala",
|
||||
"Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}",
|
||||
"SAVE": "GUARDAR",
|
||||
"Save_Changes": "Guardar Alterações",
|
||||
"Save": "Guardar",
|
||||
|
@ -479,11 +473,8 @@
|
|||
"Updating": "A actualizar...",
|
||||
"Uploading": "A enviar",
|
||||
"Upload_file_question_mark": "Enviar ficheiro?",
|
||||
"User_added_by": "Utilizador {{userAdded}} adicionado por {{userBy}}",
|
||||
"User_has_been_key": "Utilizador foi {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{userBy}} removeu o estatuto de {{role}} de {{user}}",
|
||||
"User_muted_by": "Utilizador {{userMuted}} foi silenciado por {{userBy}}",
|
||||
"User_removed_by": "Utilizador {{userRemoved}} removido por {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} enviou um ficheiro",
|
||||
"User_unmuted_by": "{{userBy}} retirou o silêncio a {{userUnmuted}}",
|
||||
"User_was_set_role_by_": "{{userBy}} deu estatuto de {{role}} a {{user}}",
|
||||
|
|
|
@ -247,10 +247,6 @@
|
|||
"Forward_to_user": "Перенаправить пользователю",
|
||||
"Full_table": "Нажмите, чтобы увидеть полную таблицу",
|
||||
"Generate_New_Link": "Сгенерировать Новую Ссылку",
|
||||
"Has_joined_the_channel": "присоединился к каналу",
|
||||
"Has_joined_the_team": "присоединился к Команде",
|
||||
"Has_joined_the_conversation": "присоединился к беседе",
|
||||
"Has_left_the_channel": "покинул канал",
|
||||
"Has_left_the_team": "покинул Команду",
|
||||
"Hide_System_Messages": "Скрыть Системные Сообщения",
|
||||
"Hide_type_messages": "Скрыть \"{{type}}\" сообщения",
|
||||
|
@ -436,15 +432,11 @@
|
|||
"Roles": "Роли",
|
||||
"Room_actions": "Действия с чатом",
|
||||
"Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}",
|
||||
"Room_changed_avatar": "Аватар чата изменен пользователем {{userBy}}",
|
||||
"Room_changed_description": "Описание чата было изменено на: {{description}} пользователем {{userBy}}",
|
||||
"Room_changed_privacy": "Тип чата был изменен на: {{type}} пользователем {{userBy}}",
|
||||
"Room_changed_topic": "Тема чата была изменена на: {{topic}} пользователем {{userBy}}",
|
||||
"Room_Files": "Файлы",
|
||||
"Room_Info_Edit": "Изменить информацию о чате",
|
||||
"Room_Info": "Информация о канале",
|
||||
"Room_Members": "Пользователи",
|
||||
"Room_name_changed": "Название чата было изменено на: {{name}} пользователем {{userBy}}",
|
||||
"SAVE": "СОХРАНИТЬ",
|
||||
"Save_Changes": "Сохранить изменения",
|
||||
"Save": "Сохранить",
|
||||
|
@ -546,12 +538,9 @@
|
|||
"Upload_file_question_mark": "Загрузить файл?",
|
||||
"User": "Пользователь",
|
||||
"Users": "Пользователи",
|
||||
"User_added_by": "Пользователь {{userAdded}} добавлен по решению {{userBy}}",
|
||||
"User_Info": "Информация о пользователе",
|
||||
"User_has_been_key": "Пользователь был {{key}}",
|
||||
"User_is_no_longer_role_by_": "{{user}} больше не {{role}} по решению {{userBy}}",
|
||||
"User_muted_by": "Пользователь {{userMuted}} заглушен по решению {{userBy}}",
|
||||
"User_removed_by": "Пользователь {{userRemoved}} удален по решению {{userBy}}",
|
||||
"User_sent_an_attachment": "{{user}} отправил вложение",
|
||||
"User_unmuted_by": "Пользователь {{userUnmuted}} перестал быть заглушенным по решению {{userBy}}",
|
||||
"User_was_set_role_by_": "{{user}} был назначен {{role}} пользователем {{userBy}}",
|
||||
|
@ -696,8 +685,6 @@
|
|||
"Message_Ignored": "Сообщение игнорируется. Тапните по нему, чтобы отобразить его.",
|
||||
"Enter_workspace_URL": "Введите URL вашего рабочего пространства",
|
||||
"Workspace_URL_Example": "Например, your-company.rocket.chat",
|
||||
"This_room_encryption_has_been_enabled_by__username_": "Шифрование для этого чата включено {{username}}",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "Шифрование для этого чата выключено {{username}}",
|
||||
"Teams": "Команды",
|
||||
"No_team_channels_found": "Каналы не найдены",
|
||||
"Team_not_found": "Команда не найдена",
|
||||
|
@ -794,8 +781,6 @@
|
|||
"Message_HideType_user_converted_to_team": "Скрыть сообщения \"Пользователь преобразовал канал в Команду\"",
|
||||
"Message_HideType_user_deleted_room_from_team": "Скрыть сообщения \"Пользователь удалил чат из Команды\"",
|
||||
"Message_HideType_user_removed_room_from_team": "Скрыть сообщения \"Пользователь удалил чат из Команды\"",
|
||||
"Removed__roomName__from_this_team": "удалил(-а) #{{roomName}} из этой рабочей группы",
|
||||
"Removed__username__from_team": "удалил(-а) @{{user_removed}} из этой рабочей группы",
|
||||
"User_joined_team": "присоединился к Команде",
|
||||
"User_left_team": "покинул Команду",
|
||||
"Place_chat_on_hold": "Поставить чат на удержание",
|
||||
|
|
|
@ -241,9 +241,6 @@
|
|||
"Forward_to_user": "Kullanıcıya İlet",
|
||||
"Full_table": "Tam tabloyu görmek için tıklayın",
|
||||
"Generate_New_Link": "Yeni Bağlantı Oluştur",
|
||||
"Has_joined_the_channel": "kanala katıldı",
|
||||
"Has_joined_the_conversation": "sohbete katıldı",
|
||||
"Has_left_the_channel": "kanaldan ayrıldı",
|
||||
"Hide_System_Messages": "Sistem İletilerını Gizle",
|
||||
"Hide_type_messages": "\"{{type}}\" iletilerini gizle",
|
||||
"How_It_Works": "Nasıl Çalışır",
|
||||
|
@ -425,14 +422,11 @@
|
|||
"Roles": "Roller",
|
||||
"Room_actions": "Oda işlemleri",
|
||||
"Room_changed_announcement": "Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi",
|
||||
"Room_changed_avatar": "Oda profil fotoğrafı {{userBy}} tarafından değiştirildi",
|
||||
"Room_changed_description": "Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi",
|
||||
"Room_changed_topic": "Oda konusu, {{userBy}} tarafından {{topic}} olarak değiştirildi",
|
||||
"Room_Files": "Oda Dosyaları",
|
||||
"Room_Info_Edit": "Oda Bilgilerini Düzenle",
|
||||
"Room_Info": "Oda Bilgisi",
|
||||
"Room_Members": "Oda Üyeleri",
|
||||
"Room_name_changed": "Oda adı, {{userBy}} tarafından {{name}} olarak değiştirildi",
|
||||
"SAVE": "KAYDET",
|
||||
"Save_Changes": "Değişiklikleri Kaydet",
|
||||
"Save": "Kaydet",
|
||||
|
@ -534,12 +528,9 @@
|
|||
"Upload_file_question_mark": "Dosya gönderilsin mi?",
|
||||
"User": "Kullanıcı",
|
||||
"Users": "Kullanıcılar",
|
||||
"User_added_by": "{{userAdded}} adlı kullanıcı {{userBy}} tarafından eklendi",
|
||||
"User_Info": "Kullanıcı bilgisi",
|
||||
"User_has_been_key": "Kullanıcı {{key}} olmuştur",
|
||||
"User_is_no_longer_role_by_": "{{user}} artık {{role}} değil ({{userBy}} tarafından)",
|
||||
"User_muted_by": "{{userMuted}} adlı kullanıcının sesini {{userBy}} kapattı",
|
||||
"User_removed_by": "{{userRemoved}} kullanıcısı {{userBy}} tarafından kaldırıldı",
|
||||
"User_sent_an_attachment": "{{user}} bir ek gönderdi",
|
||||
"User_unmuted_by": "{{userUnmuted}} kullanıcısının sesi {{userBy}} tarafından açıldı",
|
||||
"User_was_set_role_by_": "{{user}}, {{userBy}} tarafından {{role}} ayarlandı",
|
||||
|
|
|
@ -241,9 +241,6 @@
|
|||
"Forward_to_user": "转发给用戶",
|
||||
"Full_table": "点击以查看完整表格",
|
||||
"Generate_New_Link": "产生新的链接",
|
||||
"Has_joined_the_channel": "已加入频道",
|
||||
"Has_joined_the_conversation": "已经加入此对话",
|
||||
"Has_left_the_channel": "已离开频道",
|
||||
"Hide_System_Messages": "隐藏系统信息",
|
||||
"Hide_type_messages": "隐藏 \"{{type}}\" 信息",
|
||||
"How_It_Works": "运作方式",
|
||||
|
@ -422,15 +419,11 @@
|
|||
"Roles": "角色",
|
||||
"Room_actions": "聊天室操作",
|
||||
"Room_changed_announcement": "{{userBy}}将聊天室通知改为:{{announcement}}",
|
||||
"Room_changed_avatar": "Room avatar changed by {{userBy}}",
|
||||
"Room_changed_description": "{{userBy}}将聊天室说明改为:{{description}}",
|
||||
"Room_changed_privacy": "{{userBy}}将聊天室类型改为:{{type}}",
|
||||
"Room_changed_topic": "{{userBy}}将聊天室主题改为:{{topic}}",
|
||||
"Room_Files": "聊天室文件",
|
||||
"Room_Info_Edit": "聊天室信息编辑",
|
||||
"Room_Info": "聊天室信息",
|
||||
"Room_Members": "聊天室成员",
|
||||
"Room_name_changed": "{{userBy}} 将聊天室名称改为:{{name}}",
|
||||
"SAVE": "保存",
|
||||
"Save_Changes": "保存更改",
|
||||
"Save": "保存",
|
||||
|
@ -532,12 +525,9 @@
|
|||
"Upload_file_question_mark": "上传文件?",
|
||||
"User": "用戶",
|
||||
"Users": "用戶",
|
||||
"User_added_by": "由{{userBy}}添加的用户 {{userAdded}}",
|
||||
"User_Info": "用戶资讯",
|
||||
"User_has_been_key": "用户已被{{key}}",
|
||||
"User_is_no_longer_role_by_": "{{userBy}}将角色 {{role}} 从用户 {{user}} 身上移除",
|
||||
"User_muted_by": "用户 {{userMuted}} 被 {{userBy}} 静音",
|
||||
"User_removed_by": "用户 {{userRemoved}} 被 {{userBy}} 移除",
|
||||
"User_sent_an_attachment": "{{user}} 寄送了一个附件",
|
||||
"User_unmuted_by": "用户 {{userUnmuted}} 被 {{userBy}} 取消静音",
|
||||
"User_was_set_role_by_": "用户 {{user}} 被 {{userBy}} 设置角色 {{role}}",
|
||||
|
|
|
@ -242,9 +242,6 @@
|
|||
"Forward_to_user": "轉發給使用者",
|
||||
"Full_table": "點擊以查看完整表格",
|
||||
"Generate_New_Link": "產生新的連結",
|
||||
"Has_joined_the_channel": "已加入頻道",
|
||||
"Has_joined_the_conversation": "已經加入此對話",
|
||||
"Has_left_the_channel": "已離開頻道",
|
||||
"Hide_System_Messages": "隱藏系統訊息",
|
||||
"Hide_type_messages": "隱藏 '{{type}}' 訊息",
|
||||
"How_It_Works": "運作方式",
|
||||
|
@ -424,15 +421,11 @@
|
|||
"Roles": "角色",
|
||||
"Room_actions": "聊天室操作",
|
||||
"Room_changed_announcement": "{{userBy}}將聊天室通知改為:{{announcement}}",
|
||||
"Room_changed_avatar": "Room avatar changed by {{userBy}}",
|
||||
"Room_changed_description": "{{userBy}}將聊天室說明改為:{{description}}",
|
||||
"Room_changed_privacy": "{{userBy}}將聊天室類型改為:{{type}}",
|
||||
"Room_changed_topic": "{{userBy}}將聊天室主題改為:{{topic}}",
|
||||
"Room_Files": "聊天室檔案",
|
||||
"Room_Info_Edit": "修改聊天室資訊",
|
||||
"Room_Info": "聊天室資訊",
|
||||
"Room_Members": "聊天室成員",
|
||||
"Room_name_changed": "{{userBy}} 將聊天室名稱改為:{{name}}",
|
||||
"SAVE": "儲存",
|
||||
"Save_Changes": "儲存更改",
|
||||
"Save": "儲存",
|
||||
|
@ -534,12 +527,9 @@
|
|||
"Upload_file_question_mark": "上傳文件?",
|
||||
"User": "使用者",
|
||||
"Users": "使用者",
|
||||
"User_added_by": "由{{userBy}}添加的使用者 {{userAdded}}",
|
||||
"User_Info": "使用者資訊",
|
||||
"User_has_been_key": "使用者已被{{key}}",
|
||||
"User_is_no_longer_role_by_": "{{userBy}}將角色 {{role}} 從使用者 {{user}} 身上移除",
|
||||
"User_muted_by": "使用者 {{userMuted}} 被 {{userBy}} 靜音",
|
||||
"User_removed_by": "使用者 {{userRemoved}} 被 {{userBy}} 移除",
|
||||
"User_sent_an_attachment": "{{user}} 寄送了一個附件",
|
||||
"User_unmuted_by": "使用者 {{userUnmuted}} 被 {{userBy}} 取消靜音",
|
||||
"User_was_set_role_by_": "使用者 {{user}} 被 {{userBy}} 設置角色 {{role}}",
|
||||
|
|
|
@ -70,6 +70,7 @@ export const colors = {
|
|||
collapsibleQuoteBorder: '#CBCED1',
|
||||
collapsibleChevron: '#6C727A',
|
||||
cancelButton: '#E4E7EA',
|
||||
textInputSecondaryBackground: '#E4E7EA',
|
||||
...mentions
|
||||
},
|
||||
dark: {
|
||||
|
@ -122,6 +123,7 @@ export const colors = {
|
|||
collapsibleQuoteBorder: '#CBCED1',
|
||||
collapsibleChevron: '#6C727A',
|
||||
cancelButton: '#E4E7EA',
|
||||
textInputSecondaryBackground: '#030b1b', // backgroundColor
|
||||
...mentions
|
||||
},
|
||||
black: {
|
||||
|
@ -174,6 +176,7 @@ export const colors = {
|
|||
collapsibleQuoteBorder: '#CBCED1',
|
||||
collapsibleChevron: '#6C727A',
|
||||
cancelButton: '#E4E7EA',
|
||||
textInputSecondaryBackground: '#000000', // backgroundColor
|
||||
...mentions
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
const list = ['frequentlyUsed', 'custom', 'people', 'nature', 'food', 'activity', 'travel', 'objects', 'symbols', 'flags'];
|
||||
const tabs = [
|
||||
{
|
||||
tabLabel: '🕒',
|
||||
tabLabel: 'clock',
|
||||
category: list[0]
|
||||
},
|
||||
{
|
||||
tabLabel: '🚀',
|
||||
tabLabel: 'rocket',
|
||||
category: list[1]
|
||||
},
|
||||
{
|
||||
tabLabel: '😃',
|
||||
tabLabel: 'emoji',
|
||||
category: list[2]
|
||||
},
|
||||
{
|
||||
tabLabel: '🐶',
|
||||
tabLabel: 'leaf',
|
||||
category: list[3]
|
||||
},
|
||||
{
|
||||
tabLabel: '🍔',
|
||||
tabLabel: 'burger',
|
||||
category: list[4]
|
||||
},
|
||||
{
|
||||
tabLabel: '⚽',
|
||||
tabLabel: 'basketball',
|
||||
category: list[5]
|
||||
},
|
||||
{
|
||||
tabLabel: '🚌',
|
||||
tabLabel: 'airplane',
|
||||
category: list[6]
|
||||
},
|
||||
{
|
||||
tabLabel: '💡',
|
||||
tabLabel: 'lamp-bulb',
|
||||
category: list[7]
|
||||
},
|
||||
{
|
||||
tabLabel: '💛',
|
||||
tabLabel: 'percentage',
|
||||
category: list[8]
|
||||
},
|
||||
{
|
||||
tabLabel: '🏁',
|
||||
tabLabel: 'flag',
|
||||
category: list[9]
|
||||
}
|
||||
];
|
||||
export default { list, tabs };
|
||||
export const categories = { list, tabs };
|
|
@ -2813,3 +2813,5 @@ export const emojis = [
|
|||
'flag_tc',
|
||||
'flag_mf'
|
||||
];
|
||||
|
||||
export const DEFAULT_EMOJIS = ['clap', 'thumbsup', 'heart_eyes', 'grinning', 'thinking', 'smiley'];
|
|
@ -0,0 +1,2 @@
|
|||
export * from './emojis';
|
||||
export * from './categories';
|
|
@ -1,5 +1,6 @@
|
|||
export * from './colors';
|
||||
export * from './constantDisplayMode';
|
||||
export * from './emojis';
|
||||
export * from './environment';
|
||||
export * from './keys';
|
||||
export * from './links';
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './useAppSelector';
|
||||
export * from './usePermissions';
|
||||
export * from './useFrequentlyUsedEmoji';
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { unstable_batchedUpdates } from 'react-native';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import database from '../database';
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { DEFAULT_EMOJIS } from '../constants';
|
||||
|
||||
export const useFrequentlyUsedEmoji = (
|
||||
withDefaultEmojis = false
|
||||
): {
|
||||
frequentlyUsed: IEmoji[];
|
||||
loaded: boolean;
|
||||
} => {
|
||||
const [frequentlyUsed, setFrequentlyUsed] = useState<IEmoji[]>([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
useEffect(() => {
|
||||
const getFrequentlyUsedEmojis = async () => {
|
||||
const db = database.active;
|
||||
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query(Q.experimentalSortBy('count', Q.desc)).fetch();
|
||||
let frequentlyUsedEmojis = frequentlyUsedRecords.map(item => {
|
||||
if (item.isCustom) {
|
||||
return { name: item.content, extension: item.extension! }; // if isCustom is true, extension is not null
|
||||
}
|
||||
return item.content;
|
||||
});
|
||||
|
||||
if (withDefaultEmojis && frequentlyUsedEmojis.length < DEFAULT_EMOJIS.length) {
|
||||
frequentlyUsedEmojis = frequentlyUsedEmojis
|
||||
.concat(DEFAULT_EMOJIS.filter(de => !frequentlyUsedEmojis.find(fue => typeof fue === 'string' && fue === de)))
|
||||
.slice(0, DEFAULT_EMOJIS.length);
|
||||
}
|
||||
|
||||
// TODO: remove once we update to React 18
|
||||
unstable_batchedUpdates(() => {
|
||||
setFrequentlyUsed(frequentlyUsedEmojis);
|
||||
setLoaded(true);
|
||||
});
|
||||
};
|
||||
getFrequentlyUsedEmojis();
|
||||
}, []);
|
||||
return { frequentlyUsed, loaded };
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import database from '../database';
|
||||
import { IEmoji, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
import log from './helpers/log';
|
||||
import { sanitizeLikeString } from '../database/utils';
|
||||
import { emojis } from '../constants';
|
||||
|
||||
export const addFrequentlyUsed = async (emoji: IEmoji) => {
|
||||
const db = database.active;
|
||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
|
||||
try {
|
||||
if (typeof emoji === 'string') {
|
||||
freqEmojiRecord = await freqEmojiCollection.find(emoji);
|
||||
} else {
|
||||
freqEmojiRecord = await freqEmojiCollection.find(emoji.name);
|
||||
}
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
try {
|
||||
await db.write(async () => {
|
||||
if (freqEmojiRecord) {
|
||||
await freqEmojiRecord.update(f => {
|
||||
if (f.count) {
|
||||
f.count += 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await freqEmojiCollection.create(f => {
|
||||
if (typeof emoji === 'string') {
|
||||
f._raw = sanitizedRaw({ id: emoji }, freqEmojiCollection.schema);
|
||||
Object.assign(f, { content: emoji, isCustom: false });
|
||||
} else {
|
||||
f._raw = sanitizedRaw({ id: emoji.name }, freqEmojiCollection.schema);
|
||||
Object.assign(f, { content: emoji.name, extension: emoji.extension, isCustom: true });
|
||||
}
|
||||
f.count = 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const searchEmojis = async (keyword: string): Promise<IEmoji[]> => {
|
||||
const likeString = sanitizeLikeString(keyword);
|
||||
const whereClause = [];
|
||||
if (likeString) {
|
||||
whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
|
||||
}
|
||||
const db = database.active;
|
||||
const customEmojisCollection = await db
|
||||
.get('custom_emojis')
|
||||
.query(...whereClause)
|
||||
.fetch();
|
||||
const customEmojis = customEmojisCollection?.map(emoji => ({
|
||||
name: emoji?.name,
|
||||
extension: emoji?.extension
|
||||
}));
|
||||
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1);
|
||||
return [...customEmojis, ...filteredEmojis];
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
import { LISTENER } from '../../../containers/Toast';
|
||||
import I18n from '../../../i18n';
|
||||
import EventEmitter from './events';
|
||||
import log from './log';
|
||||
import { Services } from '../../services';
|
||||
|
||||
export const handleIgnore = async (userId: string, ignore: boolean, rid: string) => {
|
||||
try {
|
||||
await Services.ignoreUser({
|
||||
rid,
|
||||
userId,
|
||||
ignore
|
||||
});
|
||||
const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored');
|
||||
EventEmitter.emit(LISTENER, { message });
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
|
@ -187,6 +187,7 @@ export default {
|
|||
ROOM_SEND_MESSAGE: 'room_send_message',
|
||||
ROOM_ENCRYPTED_PRESS: 'room_encrypted_press',
|
||||
ROOM_OPEN_EMOJI: 'room_open_emoji',
|
||||
ROOM_CLOSE_EMOJI: 'room_close_emoji',
|
||||
ROOM_AUDIO_RECORD: 'room_audio_record',
|
||||
ROOM_AUDIO_RECORD_F: 'room_audio_record_f',
|
||||
ROOM_AUDIO_FINISH: 'room_audio_finish',
|
||||
|
@ -238,6 +239,13 @@ export default {
|
|||
ROOM_MENTION_GO_USER_INFO: 'room_mention_go_user_info',
|
||||
COMMAND_RUN: 'command_run',
|
||||
COMMAND_RUN_F: 'command_run_f',
|
||||
MB_BACKSPACE: 'mb_backspace',
|
||||
MB_EMOJI_SELECTED: 'mb_emoji_selected',
|
||||
MB_EMOJI_SEARCH_PRESSED: 'mb_emoji_search_pressed',
|
||||
MB_SB_EMOJI_SEARCH: 'mb_sb_emoji_search',
|
||||
MB_SB_EMOJI_SELECTED: 'mb_sb_emoji_selected',
|
||||
REACTION_PICKER_EMOJI_SELECTED: 'reaction_picker_emoji_selected',
|
||||
REACTION_PICKER_SEARCH_EMOJIS: 'reaction_picker_search_emojis',
|
||||
|
||||
// ROOM ACTIONS VIEW
|
||||
RA_JITSI_VIDEO: 'ra_jitsi_video',
|
||||
|
@ -280,6 +288,7 @@ export default {
|
|||
RI_GO_RI_EDIT: 'ri_go_ri_edit',
|
||||
RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit',
|
||||
RI_GO_ROOM_USER: 'ri_go_room_user',
|
||||
RI_TOGGLE_BLOCK_USER: 'ri_toggle_block_user',
|
||||
|
||||
// ROOM INFO EDIT VIEW
|
||||
RI_EDIT_TOGGLE_ROOM_TYPE: 'ri_edit_toggle_room_type',
|
||||
|
|
|
@ -3,6 +3,7 @@ export * from './callJitsi';
|
|||
export * from './canOpenRoom';
|
||||
export * from './clearCache';
|
||||
export * from './enterpriseModules';
|
||||
export * from './emojis';
|
||||
export * from './getCustomEmojis';
|
||||
export * from './getPermalinks';
|
||||
export * from './getPermissions';
|
||||
|
|
|
@ -160,7 +160,7 @@ export const createTeam = ({
|
|||
}) => {
|
||||
const params = {
|
||||
name,
|
||||
users,
|
||||
members: users,
|
||||
type: type ? TEAM_TYPE.PRIVATE : TEAM_TYPE.PUBLIC,
|
||||
room: {
|
||||
readOnly,
|
||||
|
|
|
@ -68,6 +68,7 @@ export type ChatsStackParamList = {
|
|||
rid: string;
|
||||
t: SubscriptionType;
|
||||
showCloseModal?: boolean;
|
||||
fromRid?: string;
|
||||
};
|
||||
RoomInfoEditView: {
|
||||
rid: string;
|
||||
|
|
|
@ -23,13 +23,14 @@ import { IRoomInfoParam } from '../SearchMessagesView';
|
|||
import {
|
||||
IApplicationState,
|
||||
TMessageModel,
|
||||
IEmoji,
|
||||
ISubscription,
|
||||
SubscriptionType,
|
||||
IAttachment,
|
||||
IMessage,
|
||||
TAnyMessageModel,
|
||||
IUrl
|
||||
IUrl,
|
||||
TGetCustomEmoji,
|
||||
ICustomEmoji
|
||||
} from '../../definitions';
|
||||
import { Services } from '../../lib/services';
|
||||
|
||||
|
@ -45,7 +46,7 @@ interface IMessagesViewProps {
|
|||
StackNavigationProp<MasterDetailInsideStackParamList>
|
||||
>;
|
||||
route: RouteProp<ChatsStackParamList, 'MessagesView'>;
|
||||
customEmojis: { [key: string]: IEmoji };
|
||||
customEmojis: { [key: string]: ICustomEmoji };
|
||||
theme: TSupportedThemes;
|
||||
showActionSheet: (params: { options: string[]; hasCancel: boolean }) => void;
|
||||
useRealName: boolean;
|
||||
|
@ -297,7 +298,7 @@ class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewStat
|
|||
}
|
||||
};
|
||||
|
||||
getCustomEmoji = (name: string) => {
|
||||
getCustomEmoji: TGetCustomEmoji = name => {
|
||||
const { customEmojis } = this.props;
|
||||
const emoji = customEmojis[name];
|
||||
if (emoji) {
|
||||
|
|
|
@ -760,7 +760,8 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
|||
rid,
|
||||
t,
|
||||
room,
|
||||
member
|
||||
member,
|
||||
fromRid: room.rid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
|||
import { callJitsi } from '../../lib/methods';
|
||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||
import { Services } from '../../lib/services';
|
||||
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
|
||||
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
||||
|
||||
interface IGetRoomTitle {
|
||||
room: ISubscription;
|
||||
|
@ -108,6 +110,7 @@ interface IRoomInfoViewState {
|
|||
room: ISubscription;
|
||||
roomUser: IUserParsed | ILivechatVisitorModified;
|
||||
showEdit: boolean;
|
||||
roomFromRid?: TSubscriptionModel;
|
||||
}
|
||||
|
||||
class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewState> {
|
||||
|
@ -121,22 +124,29 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
|
||||
private roomObservable?: Observable<TSubscriptionModel>;
|
||||
|
||||
private fromRid?: string;
|
||||
|
||||
private subscriptionRoomFromRid?: Subscription;
|
||||
|
||||
constructor(props: IRoomInfoViewProps) {
|
||||
super(props);
|
||||
const room = props.route.params?.room;
|
||||
const roomUser = props.route.params?.member;
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.fromRid = props.route.params?.fromRid;
|
||||
this.state = {
|
||||
room: (room || { rid: this.rid, t: this.t }) as any,
|
||||
roomUser: roomUser || {},
|
||||
showEdit: false
|
||||
showEdit: false,
|
||||
roomFromRid: undefined
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.isDirect) {
|
||||
this.loadUser();
|
||||
this.loadRoomFromRid();
|
||||
} else {
|
||||
this.loadRoom();
|
||||
}
|
||||
|
@ -154,6 +164,9 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
if (this.subscription && this.subscription.unsubscribe) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.subscriptionRoomFromRid && this.subscriptionRoomFromRid.unsubscribe) {
|
||||
this.subscriptionRoomFromRid.unsubscribe();
|
||||
}
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
|
@ -266,6 +279,19 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
}
|
||||
};
|
||||
|
||||
loadRoomFromRid = async () => {
|
||||
if (this.fromRid) {
|
||||
try {
|
||||
const sub = await getSubscriptionByRoomId(this.fromRid);
|
||||
this.subscriptionRoomFromRid = sub?.observe().subscribe(roomFromRid => {
|
||||
this.setState({ roomFromRid });
|
||||
});
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadRoom = async () => {
|
||||
const { room: roomState } = this.state;
|
||||
const { route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields } = this.props;
|
||||
|
@ -351,11 +377,32 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
}
|
||||
};
|
||||
|
||||
handleCreateDirectMessage = async (onPress: () => void) => {
|
||||
try {
|
||||
if (this.isDirect) {
|
||||
await this.createDirect();
|
||||
}
|
||||
onPress();
|
||||
} catch {
|
||||
EventEmitter.emit(LISTENER, {
|
||||
message: I18n.t('error-action-not-allowed', { action: I18n.t('Create_Direct_Messages') })
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
videoCall = () => {
|
||||
const { room } = this.state;
|
||||
callJitsi(room);
|
||||
};
|
||||
|
||||
handleBlockUser = async (rid: string, blocked: string, block: boolean) => {
|
||||
logEvent(events.RI_TOGGLE_BLOCK_USER);
|
||||
try {
|
||||
await Services.toggleBlockUser(rid, blocked, block);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
renderAvatar = (room: ISubscription, roomUser: IUserParsed) => {
|
||||
const { theme } = this.props;
|
||||
|
||||
|
@ -370,36 +417,54 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
);
|
||||
};
|
||||
|
||||
renderButton = (onPress: () => void, iconName: TIconsName, text: string) => {
|
||||
renderButton = (onPress: () => void, iconName: TIconsName, text: string, danger?: boolean) => {
|
||||
const { theme } = this.props;
|
||||
|
||||
const onActionPress = async () => {
|
||||
try {
|
||||
if (this.isDirect) {
|
||||
await this.createDirect();
|
||||
}
|
||||
onPress();
|
||||
} catch {
|
||||
EventEmitter.emit(LISTENER, {
|
||||
message: I18n.t('error-action-not-allowed', { action: I18n.t('Create_Direct_Messages') })
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const color = danger ? themes[theme].dangerColor : themes[theme].actionTintColor;
|
||||
return (
|
||||
<BorderlessButton onPress={onActionPress} style={styles.roomButton}>
|
||||
<CustomIcon name={iconName} size={30} color={themes[theme].actionTintColor} />
|
||||
<Text style={[styles.roomButtonText, { color: themes[theme].actionTintColor }]}>{text}</Text>
|
||||
<BorderlessButton testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}>
|
||||
<CustomIcon name={iconName} size={30} color={color} />
|
||||
<Text style={[styles.roomButtonText, { color }]}>{text}</Text>
|
||||
</BorderlessButton>
|
||||
);
|
||||
};
|
||||
|
||||
renderButtons = () => {
|
||||
const { roomFromRid, roomUser } = this.state;
|
||||
const { jitsiEnabled } = this.props;
|
||||
|
||||
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
|
||||
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
|
||||
|
||||
// Following the web behavior, when is a DM with myself, shouldn't appear block or ignore option
|
||||
const isDmWithMyself = roomFromRid?.uids && roomFromRid.uids?.filter(uid => uid !== roomUser._id).length === 0;
|
||||
|
||||
const ignored = roomFromRid?.ignored;
|
||||
const isIgnored = ignored?.includes?.(roomUser._id);
|
||||
|
||||
const blocker = roomFromRid?.blocker;
|
||||
|
||||
return (
|
||||
<View style={styles.roomButtonsContainer}>
|
||||
{this.renderButton(this.goRoom, 'message', I18n.t('Message'))}
|
||||
{jitsiEnabled && this.isDirect ? this.renderButton(this.videoCall, 'camera', I18n.t('Video_call')) : null}
|
||||
{this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
|
||||
{jitsiEnabled && this.isDirect
|
||||
? this.renderButton(() => this.handleCreateDirectMessage(this.videoCall), 'camera', I18n.t('Video_call'))
|
||||
: null}
|
||||
{isDirectFromSaved && !isFromDm && !isDmWithMyself
|
||||
? this.renderButton(
|
||||
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
|
||||
'ignore',
|
||||
I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
|
||||
true
|
||||
)
|
||||
: null}
|
||||
{isDirectFromSaved && isFromDm
|
||||
? this.renderButton(
|
||||
() => this.handleBlockUser(roomFromRid.rid, roomUser._id, !blocker),
|
||||
'ignore',
|
||||
I18n.t(`${blocker ? 'Unblock' : 'Block'}_user`),
|
||||
true
|
||||
)
|
||||
: null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -217,20 +217,6 @@ export const handleRemoveUserFromRoom = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const handleIgnore = async (selectedUser: TUserModel, ignore: boolean, rid: string) => {
|
||||
try {
|
||||
await Services.ignoreUser({
|
||||
rid,
|
||||
userId: selectedUser._id,
|
||||
ignore
|
||||
});
|
||||
const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored');
|
||||
EventEmitter.emit(LISTENER, { message });
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const handleOwner = async (
|
||||
selectedUser: TUserModel,
|
||||
isOwner: boolean,
|
||||
|
|
|
@ -16,6 +16,7 @@ import { TSubscriptionModel, TUserModel } from '../../definitions';
|
|||
import I18n from '../../i18n';
|
||||
import { useAppSelector, usePermissions } from '../../lib/hooks';
|
||||
import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
|
||||
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
||||
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||
import log from '../../lib/methods/helpers/log';
|
||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
|
@ -28,7 +29,6 @@ import ActionsSection from './components/ActionsSection';
|
|||
import {
|
||||
fetchRole,
|
||||
fetchRoomMembersRoles,
|
||||
handleIgnore,
|
||||
handleLeader,
|
||||
handleModerator,
|
||||
handleMute,
|
||||
|
@ -207,7 +207,7 @@ const RoomMembersView = (): React.ReactElement => {
|
|||
options.push({
|
||||
icon: 'ignore',
|
||||
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
|
||||
onPress: () => handleIgnore(selectedUser, !isIgnored, room.rid),
|
||||
onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid),
|
||||
testID: 'action-sheet-ignore-user'
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,85 +1,52 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Modal from 'react-native-modal';
|
||||
|
||||
import EmojiPicker from '../../containers/EmojiPicker';
|
||||
import { isAndroid } from '../../lib/methods/helpers';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
import { IApplicationState } from '../../definitions';
|
||||
|
||||
const margin = isAndroid ? 40 : 20;
|
||||
const maxSize = 400;
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { EventTypes } from '../../containers/EmojiPicker/interfaces';
|
||||
import { searchEmojis } from '../../lib/methods';
|
||||
import { useDebounce } from '../../lib/methods/helpers/debounce';
|
||||
import { EmojiSearch } from '../../containers/EmojiPicker/EmojiSearch';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
|
||||
interface IReactionPickerProps {
|
||||
message?: any;
|
||||
show: boolean;
|
||||
isMasterDetail: boolean;
|
||||
reactionClose: () => void;
|
||||
onEmojiSelected: (shortname: string, id: string) => void;
|
||||
width: number;
|
||||
height: number;
|
||||
theme: TSupportedThemes;
|
||||
onEmojiSelected: (emoji: IEmoji, id: string) => void;
|
||||
}
|
||||
|
||||
class ReactionPicker extends React.Component<IReactionPickerProps> {
|
||||
shouldComponentUpdate(nextProps: IReactionPickerProps) {
|
||||
const { show, width, height } = this.props;
|
||||
return nextProps.show !== show || width !== nextProps.width || height !== nextProps.height;
|
||||
}
|
||||
const ReactionPicker = ({ onEmojiSelected, message, reactionClose }: IReactionPickerProps): React.ReactElement => {
|
||||
const [searchedEmojis, setSearchedEmojis] = React.useState<IEmoji[]>([]);
|
||||
const [searching, setSearching] = React.useState<boolean>(false);
|
||||
|
||||
onEmojiSelected = (emoji: string, shortname?: string) => {
|
||||
// standard emojis: `emoji` is unicode and `shortname` is :joy:
|
||||
// custom emojis: only `emoji` is returned with shortname type (:joy:)
|
||||
// to set reactions, we need shortname type
|
||||
const { onEmojiSelected, message } = this.props;
|
||||
if (message) {
|
||||
onEmojiSelected(shortname || emoji, message.id);
|
||||
}
|
||||
const handleTextChange = useDebounce((text: string) => {
|
||||
setSearching(text !== '');
|
||||
handleSearchEmojis(text);
|
||||
}, 300);
|
||||
|
||||
const handleSearchEmojis = async (text: string) => {
|
||||
logEvent(events.REACTION_PICKER_SEARCH_EMOJIS);
|
||||
const emojis = await searchEmojis(text);
|
||||
setSearchedEmojis(emojis);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { width, height, show, reactionClose, isMasterDetail, theme } = this.props;
|
||||
|
||||
let widthStyle = width - margin;
|
||||
let heightStyle = Math.min(width, height) - margin * 2;
|
||||
|
||||
if (isMasterDetail) {
|
||||
widthStyle = maxSize;
|
||||
heightStyle = maxSize;
|
||||
const handleEmojiSelect = (_eventType: EventTypes, emoji?: IEmoji) => {
|
||||
logEvent(events.REACTION_PICKER_EMOJI_SELECTED);
|
||||
if (message && emoji) {
|
||||
onEmojiSelected(emoji, message.id);
|
||||
}
|
||||
reactionClose();
|
||||
};
|
||||
|
||||
return show ? (
|
||||
<Modal
|
||||
isVisible={show}
|
||||
style={{ alignItems: 'center' }}
|
||||
onBackdropPress={reactionClose}
|
||||
onBackButtonPress={reactionClose}
|
||||
animationIn='fadeIn'
|
||||
animationOut='fadeOut'
|
||||
backdropOpacity={themes[theme].backdropOpacity}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.reactionPickerContainer,
|
||||
{
|
||||
width: widthStyle,
|
||||
height: heightStyle
|
||||
}
|
||||
]}
|
||||
testID='reaction-picker'
|
||||
>
|
||||
<EmojiPicker theme={theme} onEmojiSelected={this.onEmojiSelected} />
|
||||
</View>
|
||||
</Modal>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View style={styles.reactionPickerContainer} testID='reaction-picker'>
|
||||
<View style={styles.reactionSearchContainer}>
|
||||
<EmojiSearch onChangeText={handleTextChange} bottomSheet />
|
||||
</View>
|
||||
<EmojiPicker onItemClicked={handleEmojiSelect} searching={searching} searchedEmojis={searchedEmojis} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(ReactionPicker));
|
||||
export default ReactionPicker;
|
||||
|
|
|
@ -74,8 +74,9 @@ import {
|
|||
TMessageModel,
|
||||
TSubscriptionModel,
|
||||
TThreadModel,
|
||||
ICustomEmojis,
|
||||
IEmoji,
|
||||
ICustomEmojis
|
||||
TGetCustomEmoji
|
||||
} from '../../definitions';
|
||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
|
||||
import { TListRef } from './List/List';
|
||||
|
@ -112,7 +113,6 @@ const stateAttrsUpdate = [
|
|||
'loading',
|
||||
'editing',
|
||||
'replying',
|
||||
'reacting',
|
||||
'readOnly',
|
||||
'member',
|
||||
'canForwardGuest',
|
||||
|
@ -164,7 +164,6 @@ interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackPar
|
|||
isMasterDetail: boolean;
|
||||
replyBroadcast: Function;
|
||||
width: number;
|
||||
height: number;
|
||||
insets: EdgeInsets;
|
||||
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
|
||||
viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type
|
||||
|
@ -200,7 +199,6 @@ interface IRoomViewState {
|
|||
editing: boolean;
|
||||
replying: boolean;
|
||||
replyWithMention: boolean;
|
||||
reacting: boolean;
|
||||
readOnly: boolean;
|
||||
unreadsCount: number | null;
|
||||
roomUserId?: string | null;
|
||||
|
@ -274,7 +272,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
editing: false,
|
||||
replying: !!selectedMessage,
|
||||
replyWithMention: false,
|
||||
reacting: false,
|
||||
readOnly: false,
|
||||
unreadsCount: null,
|
||||
roomUserId,
|
||||
|
@ -828,12 +825,27 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
this.setState({ selectedMessage: undefined, replying: false, replyWithMention: false });
|
||||
};
|
||||
|
||||
showReactionPicker = () => {
|
||||
const { showActionSheet } = this.props;
|
||||
const { selectedMessage } = this.state;
|
||||
showActionSheet({
|
||||
children: (
|
||||
<ReactionPicker message={selectedMessage} onEmojiSelected={this.onReactionPress} reactionClose={this.onReactionClose} />
|
||||
),
|
||||
snaps: [400],
|
||||
enableContentPanningGesture: false
|
||||
});
|
||||
};
|
||||
|
||||
onReactionInit = (message: TAnyMessageModel) => {
|
||||
this.setState({ selectedMessage: message, reacting: true });
|
||||
this.messagebox?.current?.closeEmojiAndAction(() => {
|
||||
this.setState({ selectedMessage: message }, this.showReactionPicker);
|
||||
});
|
||||
};
|
||||
|
||||
onReactionClose = () => {
|
||||
this.setState({ selectedMessage: undefined, reacting: false });
|
||||
const { hideActionSheet } = this.props;
|
||||
this.setState({ selectedMessage: undefined }, hideActionSheet);
|
||||
};
|
||||
|
||||
onMessageLongPress = (message: TAnyMessageModel) => {
|
||||
|
@ -850,8 +862,14 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
navigation.navigate('AttachmentView', { attachment });
|
||||
};
|
||||
|
||||
onReactionPress = async (shortname: string, messageId: string) => {
|
||||
onReactionPress = async (emoji: IEmoji, messageId: string) => {
|
||||
try {
|
||||
let shortname = '';
|
||||
if (typeof emoji === 'string') {
|
||||
shortname = emoji;
|
||||
} else {
|
||||
shortname = emoji.name;
|
||||
}
|
||||
await Services.setReaction(shortname, messageId);
|
||||
this.onReactionClose();
|
||||
Review.pushPositiveEvent();
|
||||
|
@ -1026,7 +1044,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
});
|
||||
};
|
||||
|
||||
getCustomEmoji = (name: string): Pick<IEmoji, 'name' | 'extension'> | null => {
|
||||
getCustomEmoji: TGetCustomEmoji = name => {
|
||||
const { customEmojis } = this.props;
|
||||
const emoji = customEmojis[name];
|
||||
if (emoji) {
|
||||
|
@ -1109,10 +1127,13 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
|
||||
navToRoomInfo = (navParam: any) => {
|
||||
const { navigation, user, isMasterDetail } = this.props;
|
||||
const { room } = this.state;
|
||||
|
||||
logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]);
|
||||
if (navParam.rid === user.id) {
|
||||
return;
|
||||
}
|
||||
navParam.fromRid = room.rid;
|
||||
if (isMasterDetail) {
|
||||
navParam.showCloseModal = true;
|
||||
// @ts-ignore
|
||||
|
@ -1486,8 +1507,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
|
||||
render() {
|
||||
console.count(`${this.constructor.name}.render calls`);
|
||||
const { room, selectedMessage, loading, reacting } = this.state;
|
||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props;
|
||||
const { room, loading } = this.state;
|
||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, serverVersion } = this.props;
|
||||
const { rid, t } = room;
|
||||
let sysMes;
|
||||
let bannerClosed;
|
||||
|
@ -1518,15 +1539,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
/>
|
||||
{this.renderFooter()}
|
||||
{this.renderActions()}
|
||||
<ReactionPicker
|
||||
show={reacting}
|
||||
message={selectedMessage}
|
||||
onEmojiSelected={this.onReactionPress}
|
||||
reactionClose={this.onReactionClose}
|
||||
width={width}
|
||||
height={height}
|
||||
theme={theme}
|
||||
/>
|
||||
<UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
|
||||
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
|
||||
</SafeAreaView>
|
||||
|
|
|
@ -14,10 +14,13 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
marginVertical: 15
|
||||
},
|
||||
reactionSearchContainer: {
|
||||
marginHorizontal: 12,
|
||||
marginBottom: 8
|
||||
},
|
||||
reactionPickerContainer: {
|
||||
borderRadius: 4,
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden'
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
bannerContainer: {
|
||||
paddingVertical: 12,
|
||||
|
@ -64,5 +67,14 @@ export default StyleSheet.create({
|
|||
previewMode: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
searchbarContainer: {
|
||||
height: 56,
|
||||
marginBottom: 8,
|
||||
paddingHorizontal: 12
|
||||
},
|
||||
reactionPickerSearchbar: {
|
||||
paddingHorizontal: 20,
|
||||
minHeight: 48
|
||||
}
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue