Merge branch 'develop' into FIX-Links-do-not-work-if-protocol-is-not-set-in-URL-prefix

This commit is contained in:
Debojyoti Singha 2022-10-25 12:46:32 +05:30 committed by GitHub
commit 5a2a0e7c0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 1736 additions and 1324 deletions

View File

@ -3,7 +3,13 @@ import { Provider } from 'react-redux';
import { themes } from '../app/lib/constants'; import { themes } from '../app/lib/constants';
import MessageContext from '../app/containers/message/Context'; import MessageContext from '../app/containers/message/Context';
import { selectServerRequest } from '../app/actions/server';
import { mockedStore as store } from '../app/reducers/mockedStore'; 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 = [ export const decorators = [
Story => ( Story => (
@ -15,7 +21,7 @@ export const decorators = [
username: 'diego.mello', username: 'diego.mello',
token: 'abc' token: 'abc'
}, },
baseUrl: 'https://open.rocket.chat', baseUrl,
onPress: () => {}, onPress: () => {},
onLongPress: () => {}, onLongPress: () => {},
reactionInit: () => {}, reactionInit: () => {},

View File

@ -1,11 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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 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 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

View File

@ -147,7 +147,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer versionCode VERSIONCODE as Integer
versionName "4.32.0" versionName "4.33.0"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
if (!isFoss) { if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

View File

@ -18,10 +18,10 @@ const HANDLE_HEIGHT = isIOS ? 40 : 56;
const MIN_SNAP_HEIGHT = 16; const MIN_SNAP_HEIGHT = 16;
const CANCEL_HEIGHT = 64; const CANCEL_HEIGHT = 64;
const ANIMATION_DURATION = 250; export const ACTION_SHEET_ANIMATION_DURATION = 250;
const ANIMATION_CONFIG = { const ANIMATION_CONFIG = {
duration: ANIMATION_DURATION, duration: ACTION_SHEET_ANIMATION_DURATION,
// https://easings.net/#easeInOutCubic // https://easings.net/#easeInOutCubic
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0) easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
}; };
@ -140,7 +140,7 @@ const ActionSheet = React.memo(
style={{ ...styles.container, ...bottomSheet }} style={{ ...styles.container, ...bottomSheet }}
backgroundStyle={{ backgroundColor: colors.focusedBackground }} backgroundStyle={{ backgroundColor: colors.focusedBackground }}
onChange={index => index === -1 && onClose()} 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} enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
{...androidTablet} {...androidTablet}
> >

View File

@ -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; export default BottomSheetContent;

View File

@ -1 +1,2 @@
export * from './Provider'; export * from './Provider';
export * from './ActionSheet';

View File

@ -43,9 +43,7 @@ const Avatar = React.memo(
let image; let image;
if (emoji) { if (emoji) {
image = ( image = <Emoji getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />;
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
);
} else { } else {
let uri = avatar; let uri = avatar;
if (!isStatic) { if (!isStatic) {

View File

@ -1,24 +1,30 @@
import React from 'react'; 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( const CustomEmoji = React.memo(
({ baseUrl, emoji, style }: ICustomEmoji) => ( ({ emoji, style }: ICustomEmojiProps) => {
<FastImage const baseUrl = useAppSelector(state => state.share.server.server || state.server.server);
style={style} return (
source={{ <FastImage
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`, style={style}
priority: FastImage.priority.high source={{
}} uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.name)}.${emoji.extension}`,
resizeMode={FastImage.resizeMode.contain} 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; () => true
}
); );
export default CustomEmoji; export default CustomEmoji;

View File

@ -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} />;
};

View File

@ -1,75 +1,45 @@
import React from 'react'; 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 { EMOJI_BUTTON_SIZE } from './styles';
import styles from './styles';
import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; 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) => { const EmojiCategory = ({ onEmojiSelected, emojis }: IEmojiCategoryProps): React.ReactElement | null => {
if (emoji && emoji.isCustom) { const { width } = useWindowDimensions();
return (
<CustomEmoji const numColumns = Math.trunc(width / EMOJI_BUTTON_SIZE);
style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]} const marginHorizontal = (width % EMOJI_BUTTON_SIZE) / 2;
emoji={emoji}
baseUrl={baseUrl} const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
/>
); if (!width) {
return null;
} }
return ( return (
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}> <FlatList
{shortnameToUnicode(`:${emoji}:`)} // needed to update the numColumns when the width changes
</Text> 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; export default EmojiCategory;

View File

@ -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
/>
);
};

View File

@ -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;

View File

@ -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>
);
};

View File

@ -1,56 +1,42 @@
import React from 'react'; import React from 'react';
import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native'; import { Pressable, View } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../lib/constants'; import { useTheme } from '../../theme';
import { TSupportedThemes } from '../../theme'; import { ITabBarProps } from './interfaces';
import { isIOS } from '../../lib/methods/helpers';
import { CustomIcon } from '../CustomIcon';
interface ITabBarProps { const TabBar = ({ activeTab, tabs, goToPage }: ITabBarProps): React.ReactElement => {
goToPage?: (page: number) => void; const { colors } = useTheme();
activeTab?: number;
tabs?: string[];
tabEmojiStyle: StyleProp<TextStyle>;
theme: TSupportedThemes;
}
export default class TabBar extends React.Component<ITabBarProps> { return (
shouldComponentUpdate(nextProps: ITabBarProps) { <View style={styles.tabsContainer}>
const { activeTab, theme } = this.props; {tabs?.map((tab, i) => (
if (nextProps.activeTab !== activeTab) { <Pressable
return true; key={tab}
} onPress={() => goToPage?.(i)}
if (nextProps.theme !== theme) { testID={`emoji-picker-tab-${tab}`}
return true; android_ripple={{ color: colors.bannerBackground }}
} style={({ pressed }: { pressed: boolean }) => [
return false; 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() { export default TabBar;
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>
);
}
}

View File

@ -1,155 +1,44 @@
import React, { Component } from 'react'; import React from 'react';
import { StyleProp, TextStyle, View } from 'react-native'; import { View } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view'; 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 TabBar from './TabBar';
import EmojiCategory from './EmojiCategory'; import EmojiCategory from './EmojiCategory';
import Footer from './Footer';
import styles from './styles'; import styles from './styles';
import categories from './categories'; import { categories, emojisByCategory } from '../../lib/constants';
import database from '../../lib/database'; import { useTheme } from '../../theme';
import { emojisByCategory } from './emojis'; import { IEmoji, ICustomEmojis } from '../../definitions';
import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import { useAppSelector, useFrequentlyUsedEmoji } from '../../lib/hooks';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import { addFrequentlyUsed } from '../../lib/methods';
import log from '../../lib/methods/helpers/log'; import { IEmojiPickerProps, EventTypes } from './interfaces';
import { themes } from '../../lib/constants';
import { TSupportedThemes } from '../../theme';
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
interface IEmojiPickerProps { const EmojiPicker = ({
isMessageContainsOnlyEmoji?: boolean; onItemClicked,
getCustomEmoji?: TGetCustomEmoji; isEmojiKeyboard = false,
baseUrl: string; searching = false,
customEmojis: ICustomEmojis; searchedEmojis = []
style?: StyleProp<ImageStyle>; }: IEmojiPickerProps): React.ReactElement | null => {
theme: TSupportedThemes; const { colors } = useTheme();
onEmojiSelected: (emoji: string, shortname?: string) => void; const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
tabEmojiStyle?: StyleProp<TextStyle>;
}
interface IEmojiPickerState { const allCustomEmojis: ICustomEmojis = useAppSelector(
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[]; state => state.customEmojis,
customEmojis: any; () => true
show: boolean; );
width: number | null; 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> { const handleEmojiSelect = (emoji: IEmoji) => {
constructor(props: IEmojiPickerProps) { onItemClicked(EventTypes.EMOJI_PRESSED, emoji);
super(props); addFrequentlyUsed(emoji);
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);
}
}; };
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => { const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string) => {
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;
let emojis = []; let emojis = [];
if (i === 0) { if (i === 0) {
emojis = frequentlyUsed; emojis = frequentlyUsed;
@ -158,49 +47,40 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
} else { } else {
emojis = emojisByCategory[category]; emojis = emojisByCategory[category];
} }
return ( if (!emojis.length) {
<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) {
return null; return null;
} }
return ( return <EmojiCategory emojis={emojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} tabLabel={label} />;
<View onLayout={this.onLayout} style={{ flex: 1 }}> };
if (!loaded) {
return null;
}
return (
<View style={styles.emojiPickerContainer}>
{searching ? (
<EmojiCategory emojis={searchedEmojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} />
) : (
<ScrollableTabView <ScrollableTabView
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />} renderTabBar={() => <TabBar />}
contentProps={{ contentProps={{
keyboardShouldPersistTaps: 'always', keyboardShouldPersistTaps: 'always',
keyboardDismissMode: 'none' keyboardDismissMode: 'none'
}} }}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: colors.messageboxBackground }}
> >
{categories.tabs.map((tab: any, i) => {categories.tabs.map((tab: any, i) => renderCategory(tab.category, i, tab.tabLabel))}
i === 0 && frequentlyUsed.length === 0
? null // when no frequentlyUsed don't show the tab
: this.renderCategory(tab.category, i, tab.tabLabel)
)}
</ScrollableTabView> </ScrollableTabView>
</View> )}
); {isEmojiKeyboard && (
} <Footer
} onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
/>
)}
</View>
);
};
const mapStateToProps = (state: IApplicationState) => ({ export default EmojiPicker;
customEmojis: state.customEmojis,
baseUrl: state.share.server.server || state.server.server
});
export default connect(mapStateToProps)(EmojiPicker);

View File

@ -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[];
}

View File

@ -2,20 +2,23 @@ import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
export const EMOJI_BUTTON_SIZE = 44;
export const EMOJI_SIZE = EMOJI_BUTTON_SIZE - 16;
export default StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1 flex: 1
}, },
tabsContainer: { tabsContainer: {
height: 45, height: EMOJI_BUTTON_SIZE,
flexDirection: 'row', flexDirection: 'row'
paddingTop: 5
}, },
tab: { tab: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingBottom: 10 paddingVertical: 10,
width: EMOJI_BUTTON_SIZE
}, },
tabEmoji: { tabEmoji: {
fontSize: 20, fontSize: 20,
@ -33,7 +36,6 @@ export default StyleSheet.create({
left: 0, left: 0,
right: 0, right: 0,
height: 2, height: 2,
backgroundColor: 'rgba(0,0,0,0.05)',
bottom: 0 bottom: 0
}, },
categoryContainer: { categoryContainer: {
@ -49,10 +51,34 @@ export default StyleSheet.create({
}, },
categoryEmoji: { categoryEmoji: {
...sharedStyles.textAlignCenter, ...sharedStyles.textAlignCenter,
textAlignVertical: 'center',
fontSize: EMOJI_SIZE,
backgroundColor: 'transparent', backgroundColor: 'transparent',
color: '#ffffff' color: '#ffffff'
}, },
customCategoryEmoji: { 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 }
}); });

View File

@ -1,32 +1,29 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native'; import { FlatList, StyleSheet, Text, View } from 'react-native';
import { TSupportedThemes, useTheme } from '../../theme'; import { TSupportedThemes, useTheme } from '../../theme';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import { CustomIcon } from '../CustomIcon'; import { CustomIcon } from '../CustomIcon';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import { addFrequentlyUsed } from '../../lib/methods';
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
import CustomEmoji from '../EmojiPicker/CustomEmoji'; import CustomEmoji from '../EmojiPicker/CustomEmoji';
import database from '../../lib/database';
import { useDimensions } from '../../dimensions'; import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions'; import { IEmoji, TAnyMessageModel } from '../../definitions';
import Touch from '../Touch'; import Touch from '../Touch';
type TItem = TFrequentlyUsedEmojiModel | string;
export interface IHeader { export interface IHeader {
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void; handleReaction: (emoji: IEmoji, message: TAnyMessageModel) => void;
server: string;
message: TAnyMessageModel; message: TAnyMessageModel;
isMasterDetail: boolean; isMasterDetail: boolean;
} }
type TOnReaction = ({ emoji }: { emoji: TItem }) => void; type TOnReaction = ({ emoji }: { emoji: IEmoji }) => void;
interface THeaderItem { interface THeaderItem {
item: TItem; item: IEmoji;
onReaction: TOnReaction; onReaction: TOnReaction;
server: string;
theme: TSupportedThemes; theme: TSupportedThemes;
} }
@ -64,30 +61,19 @@ const styles = StyleSheet.create({
} }
}); });
const keyExtractor = (item: TItem) => { const HeaderItem = ({ item, onReaction, theme }: THeaderItem) => (
const emojiModel = item as TFrequentlyUsedEmojiModel; <Touch
return (emojiModel.id ? emojiModel.content : item) as string; testID={`message-actions-emoji-${item}`}
}; onPress={() => onReaction({ emoji: item })}
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley']; >
{typeof item === 'string' ? (
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => { <Text style={styles.headerIcon}>{shortnameToUnicode(`:${item}:`)}</Text>
const emojiModel = item as TFrequentlyUsedEmojiModel; ) : (
const emoji = (emojiModel.id ? emojiModel.content : item) as string; <CustomEmoji style={styles.customEmoji} emoji={item} />
return ( )}
<Touch </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 HeaderFooter = ({ onReaction, theme }: THeaderFooter) => ( const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
<Touch <Touch
@ -99,49 +85,35 @@ const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
</Touch> </Touch>
); );
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => { const Header = React.memo(({ handleReaction, message, isMasterDetail }: IHeader) => {
const [items, setItems] = useState<TItem[]>([]);
const { width, height } = useDimensions(); const { width, height } = useDimensions();
const { theme } = useTheme(); 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 onReaction: TOnReaction = ({ emoji }) => {
const setEmojis = async () => { handleReaction(emoji, message);
try { addFrequentlyUsed(emoji);
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
}
}; };
useEffect(() => { const renderItem = ({ item }: { item: IEmoji }) => <HeaderItem item={item} onReaction={onReaction} theme={theme} />;
setEmojis();
}, []);
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
const renderItem = ({ item }: { item: TItem }) => (
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
);
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />; const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
if (!loaded) {
return null;
}
return ( return (
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}> <View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
<FlatList <FlatList
data={items} data={frequentlyUsed.slice(0, quantity)}
renderItem={renderItem} renderItem={renderItem}
ListFooterComponent={renderFooter} ListFooterComponent={renderFooter}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme].focusedBackground }}
keyExtractor={keyExtractor} keyExtractor={item => (typeof item === 'string' ? item : item.name)}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
scrollEnabled={false} scrollEnabled={false}
horizontal horizontal

View File

@ -12,10 +12,10 @@ import { getMessageTranslation } from '../message/utils';
import { LISTENER } from '../Toast'; import { LISTENER } from '../Toast';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import { showConfirmationAlert } from '../../lib/methods/helpers/info'; 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 Header, { HEADER_HEIGHT, IHeader } from './Header';
import events from '../../lib/methods/helpers/log/events'; 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 { getPermalinkMessage } from '../../lib/methods';
import { hasPermission } from '../../lib/methods/helpers'; import { hasPermission } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
@ -26,7 +26,7 @@ export interface IMessageActionsProps {
user: Pick<ILoggedUser, 'id'>; user: Pick<ILoggedUser, 'id'>;
editInit: (message: TAnyMessageModel) => void; editInit: (message: TAnyMessageModel) => void;
reactionInit: (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; replyInit: (message: TAnyMessageModel, mention: boolean) => void;
isMasterDetail: boolean; isMasterDetail: boolean;
isReadOnly: boolean; isReadOnly: boolean;
@ -37,7 +37,6 @@ export interface IMessageActionsProps {
Message_AllowPinning?: boolean; Message_AllowPinning?: boolean;
Message_AllowStarring?: boolean; Message_AllowStarring?: boolean;
Message_Read_Receipt_Store_Users?: boolean; Message_Read_Receipt_Store_Users?: boolean;
server: string;
editMessagePermission?: string[]; editMessagePermission?: string[];
deleteMessagePermission?: string[]; deleteMessagePermission?: string[];
forceDeleteMessagePermission?: string[]; forceDeleteMessagePermission?: string[];
@ -60,7 +59,6 @@ const MessageActions = React.memo(
onReactionPress, onReactionPress,
replyInit, replyInit,
isReadOnly, isReadOnly,
server,
Message_AllowDeleting, Message_AllowDeleting,
Message_AllowDeleting_BlockDeleteInMinutes, Message_AllowDeleting_BlockDeleteInMinutes,
Message_AllowEditing, Message_AllowEditing,
@ -261,12 +259,10 @@ const MessageActions = React.memo(
const handleReaction: IHeader['handleReaction'] = (shortname, message) => { const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
logEvent(events.ROOM_MSG_ACTION_REACTION); logEvent(events.ROOM_MSG_ACTION_REACTION);
if (shortname) { if (shortname) {
// TODO: evaluate unification with IEmoji onReactionPress(shortname, message.id);
onReactionPress(shortname as any, message.id);
} else { } else {
reactionInit(message); setTimeout(() => reactionInit(message), ACTION_SHEET_ANIMATION_DURATION);
} }
// close actionSheet when click at header
hideActionSheet(); hideActionSheet();
}; };
@ -460,7 +456,7 @@ const MessageActions = React.memo(
headerHeight: HEADER_HEIGHT, headerHeight: HEADER_HEIGHT,
customHeader: customHeader:
!isReadOnly || room.reactWhenReadOnly ? ( !isReadOnly || room.reactWhenReadOnly ? (
<Header server={server} handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} /> <Header handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
) : null ) : null
}); });
}; };

View File

@ -6,22 +6,31 @@ import { Provider } from 'react-redux';
import store from '../../lib/store'; import store from '../../lib/store';
import EmojiPicker from '../EmojiPicker'; import EmojiPicker from '../EmojiPicker';
import styles from './styles'; import styles from './styles';
import { themes } from '../../lib/constants'; import { ThemeContext, TSupportedThemes } from '../../theme';
import { TSupportedThemes } from '../../theme'; import { EventTypes } from '../EmojiPicker/interfaces';
import { IEmoji } from '../../definitions';
import { colors } from '../../lib/constants';
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => { const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
const onEmojiSelected = (emoji: string) => { const onItemClicked = (eventType: EventTypes, emoji?: IEmoji) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji });
}; };
return ( return (
<Provider store={store}> <Provider store={store}>
<View <ThemeContext.Provider
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]} value={{
testID='messagebox-keyboard-emoji' 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> </Provider>
); );
}; };

View File

@ -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;

View File

@ -1,10 +1,9 @@
import React, { useContext } from 'react'; import React from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { IEmoji } from '../../../definitions/IEmoji'; import { IEmoji } from '../../../definitions/IEmoji';
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
import CustomEmoji from '../../EmojiPicker/CustomEmoji'; import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import MessageboxContext from '../Context';
import styles from '../styles'; import styles from '../styles';
interface IMessageBoxMentionEmoji { interface IMessageBoxMentionEmoji {
@ -12,13 +11,10 @@ interface IMessageBoxMentionEmoji {
} }
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => { const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
const context = useContext(MessageboxContext); if (typeof item === 'string') {
const { baseUrl } = context; return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
if (item.name) {
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} baseUrl={baseUrl} />;
} }
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>; return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} />;
}; };
export default MentionEmoji; export default MentionEmoji;

View File

@ -4,5 +4,6 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
export const MENTIONS_TRACKING_TYPE_ROOMS = '#'; export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
export const MENTIONS_TRACKING_TYPE_CANNED = '!'; export const MENTIONS_TRACKING_TYPE_CANNED = '!';
export const MENTIONS_COUNT_TO_DISPLAY = 4; export const MENTIONS_COUNT_TO_DISPLAY = 4;
export const MAX_EMOJIS_TO_DISPLAY = 20;
export const TIMEOUT_CLOSE_EMOJI = 300; export const TIMEOUT_CLOSE_EMOJI = 300;

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; 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 { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker'; 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 { userTyping as userTypingAction } from '../../actions/room';
import styles from './styles'; import styles from './styles';
import database from '../../lib/database'; import database from '../../lib/database';
import { emojis } from '../EmojiPicker/emojis';
import log, { events, logEvent } from '../../lib/methods/helpers/log'; import log, { events, logEvent } from '../../lib/methods/helpers/log';
import RecordAudio from './RecordAudio'; import RecordAudio from './RecordAudio';
import I18n from '../../i18n'; import I18n from '../../i18n';
import ReplyPreview from './ReplyPreview'; import ReplyPreview from './ReplyPreview';
import { themes } from '../../lib/constants'; import { themes, emojis } from '../../lib/constants';
import LeftButtons from './LeftButtons'; import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { canUploadFile } from '../../lib/methods/helpers/media'; import { canUploadFile } from '../../lib/methods/helpers/media';
@ -51,7 +50,8 @@ import {
TGetCustomEmoji, TGetCustomEmoji,
TSubscriptionModel, TSubscriptionModel,
TThreadModel, TThreadModel,
IMessage IMessage,
IEmoji
} from '../../definitions'; } from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods'; import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
@ -59,6 +59,9 @@ import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersi
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { TSupportedThemes } from '../../theme'; import { TSupportedThemes } from '../../theme';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { EventTypes } from '../EmojiPicker/interfaces';
import EmojiSearchbar from './EmojiSearchbar';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
require('./EmojiKeyboard'); require('./EmojiKeyboard');
@ -129,6 +132,7 @@ interface IMessageBoxState {
tshow: boolean; tshow: boolean;
mentionLoading: boolean; mentionLoading: boolean;
permissionToUpload: boolean; permissionToUpload: boolean;
showEmojiSearchbar: boolean;
} }
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> { class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
@ -183,7 +187,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
command: {}, command: {},
tshow: this.sendThreadToChannel, tshow: this.sendThreadToChannel,
mentionLoading: false, mentionLoading: false,
permissionToUpload: true permissionToUpload: true,
showEmojiSearchbar: false
}; };
this.text = ''; this.text = '';
this.selection = { start: 0, end: 0 }; this.selection = { start: 0, end: 0 };
@ -209,6 +214,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
...videoPickerConfig, ...videoPickerConfig,
...libPickerLabels ...libPickerLabels
}; };
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
} }
get sendThreadToChannel() { get sendThreadToChannel() {
@ -326,7 +333,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
tshow, tshow,
mentionLoading, mentionLoading,
trackingType, trackingType,
permissionToUpload permissionToUpload,
showEmojiSearchbar
} = this.state; } = this.state;
const { const {
@ -346,6 +354,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) { if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
return true; return true;
} }
if (nextState.showEmojiSearchbar !== showEmojiSearchbar) {
return true;
}
if (!isFocused()) { if (!isFocused()) {
return false; return false;
} }
@ -438,6 +449,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (isTablet) { if (isTablet) {
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands); EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
} }
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
} }
setOptions = async () => { setOptions = async () => {
@ -519,7 +531,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}, 100); }, 100);
onKeyboardResigned = () => { onKeyboardResigned = () => {
this.closeEmoji(); const { showEmojiSearchbar } = this.state;
if (!showEmojiSearchbar) {
this.closeEmoji();
}
}; };
onPressMention = (item: any) => { 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 { text } = this;
const { emoji } = params;
let newText = ''; let newText = '';
// if messagebox has an active cursor // if messagebox has an active cursor
const { start, end } = this.selection; const { start, end } = this.selection;
const cursor = Math.max(start, end); const cursor = Math.max(start, end);
newText = `${text.substr(0, cursor)}${emoji}${text.substr(cursor)}`; let newCursor;
const newCursor = cursor + emoji.length;
this.setInput(newText, { start: newCursor, end: newCursor }); switch (eventType) {
this.setShowSend(true); 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) => { getPermalink = async (message: any) => {
@ -621,16 +668,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.setState({ mentions: res, mentionLoading: false }); this.setState({ mentions: res, mentionLoading: false });
}, 300); }, 300);
getEmojis = debounce(async (keyword: any) => { getCustomEmojis = async (keyword: any, count: number) => {
const db = database.active;
const customEmojisCollection = db.get('custom_emojis');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
const whereClause = []; const whereClause = [];
if (likeString) { if (likeString) {
whereClause.push(Q.where('name', Q.like(`${likeString}%`))); whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
} }
let customEmojis = await customEmojisCollection.query(...whereClause).fetch(); const db = database.active;
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY); 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 filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY); const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
this.setState({ mentions: mergedEmojis || [], mentionLoading: false }); this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
@ -881,7 +932,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
openEmoji = () => { openEmoji = () => {
logEvent(events.ROOM_OPEN_EMOJI); logEvent(events.ROOM_OPEN_EMOJI);
this.setState({ showEmojiKeyboard: true }); this.setState({ showEmojiKeyboard: true, showEmojiSearchbar: false });
this.stopTrackingMention();
}; };
recordingCallback = (recording: any) => { recordingCallback = (recording: any) => {
@ -903,7 +955,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
closeEmoji = () => { 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) => { closeEmojiAndAction = (action?: Function, params?: any) => {
@ -926,7 +984,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.clearInput(); this.clearInput();
this.debouncedOnChangeText.stop(); this.debouncedOnChangeText.stop();
this.closeEmoji(); this.closeEmojiKeyboardAndFocus();
this.stopTrackingMention(); this.stopTrackingMention();
this.handleTyping(false); this.handleTyping(false);
if (message.trim() === '' && !showSend) { 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 = () => { renderContent = () => {
const { const {
recording, recording,
showEmojiKeyboard, showEmojiKeyboard,
showEmojiSearchbar,
showSend, showSend,
mentions, mentions,
trackingType, trackingType,
@ -1149,11 +1231,11 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const textInputAndButtons = !recording ? ( const textInputAndButtons = !recording ? (
<> <>
<LeftButtons <LeftButtons
showEmojiKeyboard={showEmojiKeyboard} showEmojiKeyboard={showEmojiKeyboard || showEmojiSearchbar}
editing={editing} editing={editing}
editCancel={this.editCancel} editCancel={this.editCancel}
openEmoji={this.openEmoji} openEmoji={this.openEmoji}
closeEmoji={this.closeEmoji} closeEmoji={this.closeEmojiKeyboardAndFocus}
/> />
<TextInput <TextInput
ref={component => (this.component = component)} ref={component => (this.component = component)}
@ -1197,6 +1279,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
{recordAudio} {recordAudio}
</View> </View>
{this.renderSendToChannel()} {this.renderSendToChannel()}
{this.renderEmojiSearchbar()}
</View> </View>
{children} {children}
</> </>
@ -1224,7 +1307,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
kbInitialProps={{ theme }} kbInitialProps={{ theme }}
onKeyboardResigned={this.onKeyboardResigned} onKeyboardResigned={this.onKeyboardResigned}
onItemSelected={this.onEmojiSelected} onItemSelected={this.onKeyboardItemSelected}
trackInteractive trackInteractive
requiresSameParentToManageScrollView requiresSameParentToManageScrollView
addBottomView addBottomView

View File

@ -105,7 +105,7 @@ export default StyleSheet.create({
}, },
emojiKeyboardContainer: { emojiKeyboardContainer: {
flex: 1, flex: 1,
borderTopWidth: StyleSheet.hairlineWidth borderTopWidth: 1
}, },
slash: { slash: {
height: 30, height: 30,

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text } from 'react-native'; import { Text, StyleProp, ViewStyle } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
@ -12,16 +12,17 @@ interface IPasscodeButton {
icon?: TIconsName; icon?: TIconsName;
disabled?: boolean; disabled?: boolean;
onPress?: Function; 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 { theme } = useTheme();
const press = () => onPress && onPress(text); const press = () => onPress && onPress(text);
return ( return (
<Touch <Touch
style={[styles.buttonView, { backgroundColor: 'transparent' }]} style={[styles.buttonView, { backgroundColor: 'transparent' }, style]}
underlayColor={themes[theme].passcodeButtonActive} underlayColor={themes[theme].passcodeButtonActive}
rippleColor={themes[theme].passcodeButtonActive} rippleColor={themes[theme].passcodeButtonActive}
enabled={!disabled} enabled={!disabled}

View File

@ -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 { Col, Grid, Row } from 'react-native-easy-grid';
import range from 'lodash/range'; import range from 'lodash/range';
import { View } from 'react-native'; import { View } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import Orientation from 'react-native-orientation-locker';
import styles from './styles'; import styles from './styles';
import Button from './Button'; import Button from './Button';
@ -14,6 +15,8 @@ import { useTheme } from '../../../theme';
import LockIcon from './LockIcon'; import LockIcon from './LockIcon';
import Title from './Title'; import Title from './Title';
import Subtitle from './Subtitle'; import Subtitle from './Subtitle';
import { useDimensions } from '../../../dimensions';
import { isTablet } from '../../../lib/methods/helpers';
interface IPasscodeBase { interface IPasscodeBase {
type: string; type: string;
@ -34,7 +37,25 @@ export interface IBase {
const Base = forwardRef<IBase, IPasscodeBase>( const Base = forwardRef<IBase, IPasscodeBase>(
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => { ({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
useLayoutEffect(() => {
if (!isTablet) {
Orientation.lockToPortrait();
}
return () => {
if (!isTablet) {
Orientation.unlockAllOrientations();
}
};
}, []);
const { theme } = useTheme(); 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 rootRef = useRef<Animatable.View & View>(null);
const dotsRef = 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} /> <Dots passcode={passcode} length={PASSCODE_LENGTH} />
</Animatable.View> </Animatable.View>
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{range(1, 4).map(i => ( {range(1, 4).map(i => (
<Col key={i} style={styles.colButton}> <Col key={i} style={[styles.colButton, heightButtonRow]}>
<Button text={i.toString()} onPress={onPressNumber} /> <Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{range(4, 7).map(i => ( {range(4, 7).map(i => (
<Col key={i} style={styles.colButton}> <Col key={i} style={[styles.colButton, heightButtonRow]}>
<Button text={i.toString()} onPress={onPressNumber} /> <Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{range(7, 10).map(i => ( {range(7, 10).map(i => (
<Col key={i} style={styles.colButton}> <Col key={i} style={[styles.colButton, heightButtonRow]}>
<Button text={i.toString()} onPress={onPressNumber} /> <Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, heightButtonRow]}>
{showBiometry ? ( {showBiometry ? (
<Col style={styles.colButton}> <Col style={[styles.colButton, heightButtonRow]}>
<Button icon='fingerprint' onPress={onBiometryPress} /> <Button style={heightButtonRow} icon='fingerprint' onPress={onBiometryPress} />
</Col> </Col>
) : ( ) : (
<Col style={styles.colButton} /> <Col style={[styles.colButton, heightButtonRow]} />
)} )}
<Col style={styles.colButton}> <Col style={[styles.colButton, heightButtonRow]}>
<Button text='0' onPress={onPressNumber} /> <Button style={heightButtonRow} text='0' onPress={onPressNumber} />
</Col> </Col>
<Col style={styles.colButton}> <Col style={[styles.colButton, heightButtonRow]}>
<Button icon='backspace' onPress={onPressDelete} /> <Button style={heightButtonRow} icon='backspace' onPress={onPressDelete} />
</Col> </Col>
</Row> </Row>
</Grid> </Grid>

View File

@ -18,16 +18,12 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}, },
buttonRow: {
height: 102
},
colButton: { colButton: {
flex: 0, flex: 0,
marginLeft: 12, marginLeft: 12,
marginRight: 12, marginRight: 12,
alignItems: 'center', alignItems: 'center',
width: 78, width: 78
height: 78
}, },
buttonText: { buttonText: {
fontSize: 28, fontSize: 28,
@ -37,7 +33,6 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
width: 78, width: 78,
height: 78,
borderRadius: 4 borderRadius: 4
}, },
textTitle: { textTitle: {

View File

@ -23,7 +23,6 @@ interface IAllTabProps {
const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => { const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => {
const { colors } = useTheme(); const { colors } = useTheme();
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); 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 username = useAppSelector(state => state.login.user.username);
const count = item.usernames.length; const count = item.usernames.length;
@ -50,7 +49,6 @@ const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemPro
content={item.emoji} content={item.emoji}
standardEmojiStyle={styles.allTabStandardEmojiStyle} standardEmojiStyle={styles.allTabStandardEmojiStyle}
customEmojiStyle={styles.allTabCustomEmojiStyle} customEmojiStyle={styles.allTabCustomEmojiStyle}
baseUrl={server}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
/> />
<View style={styles.textContainer}> <View style={styles.textContainer}>

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { TGetCustomEmoji, IEmoji } from '../../definitions'; import { TGetCustomEmoji, ICustomEmoji } from '../../definitions';
import ReactionsList from '.'; import ReactionsList from '.';
import { mockedStore as store } from '../../reducers/mockedStore'; import { mockedStore as store } from '../../reducers/mockedStore';
import { updateSettings } from '../../actions/settings'; import { updateSettings } from '../../actions/settings';
@ -11,7 +11,7 @@ const getCustomEmoji: TGetCustomEmoji = content => {
marioparty: { name: content, extension: 'gif' }, marioparty: { name: content, extension: 'gif' },
react_rocket: { name: content, extension: 'png' }, react_rocket: { name: content, extension: 'png' },
nyan_rocket: { name: content, extension: 'png' } nyan_rocket: { name: content, extension: 'png' }
}[content] as IEmoji; }[content] as ICustomEmoji;
return customEmoji; return customEmoji;
}; };

View File

@ -8,7 +8,6 @@ import { TGetCustomEmoji } from '../../definitions/IEmoji';
import I18n from '../../i18n'; import I18n from '../../i18n';
import styles, { MIN_TAB_WIDTH } from './styles'; import styles, { MIN_TAB_WIDTH } from './styles';
import { useDimensions, useOrientation } from '../../dimensions'; import { useDimensions, useOrientation } from '../../dimensions';
import { useAppSelector } from '../../lib/hooks';
interface ITabBarItem { interface ITabBarItem {
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
@ -25,7 +24,6 @@ interface IReactionsTabBar {
const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => { const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => {
const { colors } = useTheme(); const { colors } = useTheme();
const server = useAppSelector(state => state.server.server);
return ( return (
<Pressable <Pressable
key={tab.emoji} key={tab.emoji}
@ -46,7 +44,6 @@ const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => {
content={tab.emoji} content={tab.emoji}
standardEmojiStyle={styles.standardEmojiStyle} standardEmojiStyle={styles.standardEmojiStyle}
customEmojiStyle={styles.customEmojiStyle} customEmojiStyle={styles.customEmojiStyle}
baseUrl={server}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
/> />
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text> <Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react'; 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 DateTimePicker, { Event } from '@react-native-community/datetimepicker';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { BlockContext } from '@rocket.chat/ui-kit'; 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 // timestamp as number exists in Event
// @ts-ignore // @ts-ignore
const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => { const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => {
const newDate = date || new Date(timestamp); if (date || timestamp) {
onChangeDate(newDate); const newDate = date || new Date(timestamp);
action({ value: moment(newDate).format('YYYY-MM-DD') }); unstable_batchedUpdates(() => {
if (isAndroid) { onChangeDate(newDate);
onShow(false); 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 ? ( 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; ) : null;
return ( return (

View File

@ -10,33 +10,24 @@ interface IEmoji {
literal: string; literal: string;
isMessageContainsOnlyEmoji: boolean; isMessageContainsOnlyEmoji: boolean;
getCustomEmoji?: Function; getCustomEmoji?: Function;
baseUrl: string;
customEmojis?: any; customEmojis?: any;
style?: object; style?: object;
onEmojiSelected?: Function; onEmojiSelected?: Function;
tabEmojiStyle?: object; tabEmojiStyle?: object;
} }
const Emoji = React.memo( const Emoji = React.memo(({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, customEmojis = true, style = {} }: IEmoji) => {
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {} }: IEmoji) => { const { colors } = useTheme();
const { colors } = useTheme(); const emojiUnicode = shortnameToUnicode(literal);
const emojiUnicode = shortnameToUnicode(literal); const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, '')); if (emoji && customEmojis) {
if (emoji && customEmojis) { return <CustomEmoji style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]} emoji={emoji} />;
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>
);
} }
); return (
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
{emojiUnicode}
</Text>
);
});
export default Emoji; export default Emoji;

View File

@ -4,7 +4,7 @@ import { NavigationContainer } from '@react-navigation/native';
import Markdown, { MarkdownPreview } from '.'; import Markdown, { MarkdownPreview } from '.';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import { TGetCustomEmoji, IEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji, ICustomEmoji } from '../../definitions/IEmoji';
const theme = 'light'; const theme = 'light';
@ -16,7 +16,6 @@ const styles = StyleSheet.create({
} }
}); });
const baseUrl = 'https://open.rocket.chat';
const longText = const longText =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
const lineBreakText = `a const lineBreakText = `a
@ -34,7 +33,7 @@ const getCustomEmoji: TGetCustomEmoji = content => {
marioparty: { name: content, extension: 'gif' }, marioparty: { name: content, extension: 'gif' },
react_rocket: { name: content, extension: 'png' }, react_rocket: { name: content, extension: 'png' },
nyan_rocket: { name: content, extension: 'png' } nyan_rocket: { name: content, extension: 'png' }
}[content] as IEmoji; }[content] as ICustomEmoji;
return customEmoji; return customEmoji;
}; };
@ -108,13 +107,8 @@ export const Emoji = () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='Unicode: 😃😇👍' theme={theme} /> <Markdown msg='Unicode: 😃😇👍' theme={theme} />
<Markdown msg='Shortnames: :joy::+1:' theme={theme} /> <Markdown msg='Shortnames: :joy::+1:' theme={theme} />
<Markdown <Markdown msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} />
msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' <Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} />
theme={theme}
getCustomEmoji={getCustomEmoji}
baseUrl={baseUrl}
/>
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
</View> </View>
); );

View File

@ -33,7 +33,6 @@ interface IMarkdownProps {
md?: MarkdownAST; md?: MarkdownAST;
mentions?: IUserMention[]; mentions?: IUserMention[];
getCustomEmoji?: TGetCustomEmoji; getCustomEmoji?: TGetCustomEmoji;
baseUrl?: string;
username?: string; username?: string;
tmid?: string; tmid?: string;
numberOfLines?: number; numberOfLines?: number;
@ -235,13 +234,12 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
}; };
renderEmoji = ({ literal }: TLiteral) => { renderEmoji = ({ literal }: TLiteral) => {
const { getCustomEmoji, baseUrl = '', customEmojis, style } = this.props; const { getCustomEmoji, customEmojis, style } = this.props;
return ( return (
<MarkdownEmoji <MarkdownEmoji
literal={literal} literal={literal}
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji} isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
baseUrl={baseUrl}
customEmojis={customEmojis} customEmojis={customEmojis}
style={style} style={style}
/> />
@ -312,18 +310,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
}; };
render() { render() {
const { const { msg, md, mentions, channels, navToRoomInfo, useRealName, username = '', getCustomEmoji, onLinkPress } = this.props;
msg,
md,
mentions,
channels,
navToRoomInfo,
useRealName,
username = '',
getCustomEmoji,
baseUrl = '',
onLinkPress
} = this.props;
if (!msg) { if (!msg) {
return null; return null;
@ -333,7 +320,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
return ( return (
<NewMarkdown <NewMarkdown
username={username} username={username}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
useRealName={useRealName} useRealName={useRealName}
tokens={md} tokens={md}

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { Emoji as EmojiProps } from '@rocket.chat/message-parser'; import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import styles from '../styles'; import styles from '../styles';
import CustomEmoji from '../../EmojiPicker/CustomEmoji'; import CustomEmoji from '../../EmojiPicker/CustomEmoji';
@ -15,21 +14,21 @@ interface IEmojiProps {
} }
const Emoji = ({ block, isBigEmoji }: IEmojiProps) => { const Emoji = ({ block, isBigEmoji }: IEmojiProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext); const { getCustomEmoji } = useContext(MarkdownContext);
if ('unicode' in block) { 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 emojiToken = block?.shortCode ? `:${block.shortCode}:` : `:${block.value?.value}:`;
const emojiUnicode = shortnameToUnicode(emojiToken); const emojiUnicode = shortnameToUnicode(emojiToken);
const emoji = getCustomEmoji?.(block.value?.value); const emoji = getCustomEmoji?.(block.value?.value);
if (emoji) { 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 ( 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} {emojiUnicode}
</Text> </Text>
); );

View File

@ -7,7 +7,6 @@ interface IMarkdownContext {
channels?: IUserChannel[]; channels?: IUserChannel[];
useRealName?: boolean; useRealName?: boolean;
username?: string; username?: string;
baseUrl?: string;
navToRoomInfo?: Function; navToRoomInfo?: Function;
getCustomEmoji?: Function; getCustomEmoji?: Function;
onLinkPress?: Function; onLinkPress?: Function;
@ -18,7 +17,6 @@ const defaultState = {
channels: [], channels: [],
useRealName: false, useRealName: false,
username: '', username: '',
baseUrl: '',
navToRoomInfo: () => {} navToRoomInfo: () => {}
}; };

View File

@ -34,11 +34,8 @@ const getCustomEmoji = (content: string) => {
}[content]; }[content];
return customEmoji; return customEmoji;
}; };
const baseUrl = 'https://open.rocket.chat';
const NewMarkdown = ({ ...props }) => ( const NewMarkdown = ({ ...props }) => <NewMarkdownComponent getCustomEmoji={getCustomEmoji} username='rocket.cat' {...props} />;
<NewMarkdownComponent baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} username='rocket.cat' {...props} />
);
const simpleTextMsg = [ const simpleTextMsg = [
{ {
@ -340,7 +337,7 @@ const emojiTokens = [
export const Emoji = () => ( export const Emoji = () => (
<View style={styles.container}> <View style={styles.container}>
<NewMarkdown tokens={bigEmojiTokens} /> <NewMarkdown tokens={bigEmojiTokens} />
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} /> <NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} />
</View> </View>
); );

View File

@ -24,7 +24,6 @@ interface IBodyProps {
navToRoomInfo?: Function; navToRoomInfo?: Function;
useRealName?: boolean; useRealName?: boolean;
username: string; username: string;
baseUrl: string;
} }
const Body = ({ const Body = ({
@ -35,7 +34,6 @@ const Body = ({
username, username,
navToRoomInfo, navToRoomInfo,
getCustomEmoji, getCustomEmoji,
baseUrl,
onLinkPress onLinkPress
}: IBodyProps): React.ReactElement | null => { }: IBodyProps): React.ReactElement | null => {
if (isEmpty(tokens)) { if (isEmpty(tokens)) {
@ -51,7 +49,6 @@ const Body = ({
username, username,
navToRoomInfo, navToRoomInfo,
getCustomEmoji, getCustomEmoji,
baseUrl,
onLinkPress onLinkPress
}} }}
> >

View File

@ -283,7 +283,6 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
<Markdown <Markdown
msg={description} msg={description}
style={[isReply && style]} style={[isReply && style]}
baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme} theme={theme}

View File

@ -30,7 +30,7 @@ export const Item = () => (
user: { username: 'Marcos' } 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> </MessageContext.Provider>
</View> </View>
); );

View File

@ -82,7 +82,7 @@ interface IMessageReply {
const Fields = React.memo( const Fields = React.memo(
({ attachment, getCustomEmoji }: IMessageFields) => { ({ attachment, getCustomEmoji }: IMessageFields) => {
const { theme } = useTheme(); const { theme } = useTheme();
const { baseUrl, user } = useContext(MessageContext); const { user } = useContext(MessageContext);
if (!attachment.fields) { if (!attachment.fields) {
return null; return null;
@ -97,7 +97,6 @@ const Fields = React.memo(
</Text> </Text>
<Markdown <Markdown
msg={field?.value || ''} msg={field?.value || ''}
baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme} theme={theme}

View File

@ -5,7 +5,7 @@ import { CustomIcon } from '../../../CustomIcon';
import styles from '../../styles'; import styles from '../../styles';
import { useTheme } from '../../../../theme'; 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(); const { theme } = useTheme();
if (isReadReceiptEnabled && !unread && unread !== null) { if (isReadReceiptEnabled && !unread && unread !== null) {
return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={styles.rightIcons} />; return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={styles.rightIcons} />;

View File

@ -17,8 +17,8 @@ interface IRightIcons {
type: MessageType; type: MessageType;
msg?: string; msg?: string;
isEdited: boolean; isEdited: boolean;
isReadReceiptEnabled: boolean; isReadReceiptEnabled?: boolean;
unread: boolean; unread?: boolean;
hasError: boolean; hasError: boolean;
} }
@ -27,7 +27,7 @@ const RightIcons = ({ type, msg, isEdited, hasError, isReadReceiptEnabled, unrea
<Encrypted type={type} /> <Encrypted type={type} />
<Edited testID={`${msg}-edited`} isEdited={isEdited} /> <Edited testID={`${msg}-edited`} isEdited={isEdited} />
<MessageError hasError={hasError} /> <MessageError hasError={hasError} />
<ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread || false} /> <ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread} />
</View> </View>
); );

View File

@ -6,16 +6,17 @@ import I18n from '../../i18n';
import styles from './styles'; import styles from './styles';
import Markdown, { MarkdownPreview } from '../markdown'; import Markdown, { MarkdownPreview } from '../markdown';
import User from './User'; import User from './User';
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils'; import { messageHaveAuthorName, getInfoMessage } from './utils';
import MessageContext from './Context'; import MessageContext from './Context';
import { IMessageContent } from './interfaces'; import { IMessageContent } from './interfaces';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import { MessageTypesValues } from '../../definitions';
const Content = React.memo( const Content = React.memo(
(props: IMessageContent) => { (props: IMessageContent) => {
const { theme } = useTheme(); const { theme } = useTheme();
const { baseUrl, user, onLinkPress } = useContext(MessageContext); const { user, onLinkPress } = useContext(MessageContext);
if (props.isInfo) { if (props.isInfo) {
// @ts-ignore // @ts-ignore
@ -26,8 +27,7 @@ const Content = React.memo(
{infoMessage} {infoMessage}
</Text> </Text>
); );
if (messageHaveAuthorName(props.type as MessageTypesValues)) {
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(props.type)) {
return ( return (
<Text> <Text>
<User {...props} /> {renderMessageContent} <User {...props} /> {renderMessageContent}
@ -54,7 +54,6 @@ const Content = React.memo(
<Markdown <Markdown
msg={props.msg} msg={props.msg}
md={props.md} md={props.md}
baseUrl={baseUrl}
getCustomEmoji={props.getCustomEmoji} getCustomEmoji={props.getCustomEmoji}
enableMessageParser={user.enableMessageParserEarlyAdoption} enableMessageParser={user.enableMessageParserEarlyAdoption}
username={user.username} username={user.username}

View File

@ -6,11 +6,11 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { IMessageEmoji } from './interfaces'; import { IMessageEmoji } from './interfaces';
const Emoji = React.memo( const Emoji = React.memo(
({ content, baseUrl, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => { ({ content, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => {
const parsedContent = content.replace(/^:|:$/g, ''); const parsedContent = content.replace(/^:|:$/g, '');
const emoji = getCustomEmoji(parsedContent); const emoji = getCustomEmoji(parsedContent);
if (emoji) { 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>; return <Text style={standardEmojiStyle}>{shortnameToUnicode(content)}</Text>;
}, },

View File

@ -81,7 +81,6 @@ const ImageContainer = React.memo(
<Markdown <Markdown
msg={file.description} msg={file.description}
style={[isReply && style]} style={[isReply && style]}
baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme} theme={theme}

View File

@ -820,8 +820,8 @@ export const SystemMessages = () => (
<Message msg='public' type='room_changed_privacy' isInfo /> <Message msg='public' type='room_changed_privacy' isInfo />
<Message type='room_e2e_disabled' isInfo /> <Message type='room_e2e_disabled' isInfo />
<Message type='room_e2e_enabled' isInfo /> <Message type='room_e2e_enabled' isInfo />
<Message type='removed-user-from-team' isInfo /> <Message msg='rocket.cat' type='removed-user-from-team' isInfo />
<Message type='added-user-to-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-added-room-to-team' isInfo msg='channel-name' />
<Message type='user-converted-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' /> <Message type='user-converted-to-channel' isInfo msg='channel-name' />

View File

@ -97,8 +97,8 @@ const Message = React.memo((props: IMessage) => {
msg={props.msg} msg={props.msg}
isEdited={props.isEdited} isEdited={props.isEdited}
hasError={props.hasError} hasError={props.hasError}
isReadReceiptEnabled={props.isReadReceiptEnabled || false} isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread || false} unread={props.unread}
/> />
) : null} ) : null}
</View> </View>

View File

@ -47,7 +47,7 @@ const AddReaction = React.memo(({ theme }: { theme: TSupportedThemes }) => {
}); });
const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReaction) => { 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; const reacted = reaction.usernames.findIndex((item: string) => item === user.username) !== -1;
return ( return (
<Touchable <Touchable
@ -67,7 +67,6 @@ const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReacti
content={reaction.emoji} content={reaction.emoji}
standardEmojiStyle={styles.reactionEmoji} standardEmojiStyle={styles.reactionEmoji}
customEmojiStyle={styles.reactionCustomEmoji} customEmojiStyle={styles.reactionCustomEmoji}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
/> />
<Text style={[styles.reactionCount, { color: themes[theme].tintColor }]}>{reaction.usernames.length}</Text> <Text style={[styles.reactionCount, { color: themes[theme].tintColor }]}>{reaction.usernames.length}</Text>

View File

@ -121,7 +121,7 @@ const Description = React.memo(
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
theme: TSupportedThemes; theme: TSupportedThemes;
}) => { }) => {
const { baseUrl, user } = useContext(MessageContext); const { user } = useContext(MessageContext);
const text = attachment.text || attachment.title; const text = attachment.text || attachment.title;
if (!text) { if (!text) {
@ -132,7 +132,6 @@ const Description = React.memo(
<Markdown <Markdown
msg={text} msg={text}
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14 }]} style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14 }]}
baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme} theme={theme}
@ -177,7 +176,7 @@ const Fields = React.memo(
theme: TSupportedThemes; theme: TSupportedThemes;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
}) => { }) => {
const { baseUrl, user } = useContext(MessageContext); const { user } = useContext(MessageContext);
if (!attachment.fields) { if (!attachment.fields) {
return null; return null;
@ -188,13 +187,7 @@ const Fields = React.memo(
{attachment.fields.map(field => ( {attachment.fields.map(field => (
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}> <View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text> <Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
<Markdown <Markdown msg={field?.value || ''} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
msg={field?.value || ''}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
</View> </View>
))} ))}
</View> </View>
@ -278,13 +271,7 @@ const Reply = React.memo(
) : null} ) : null}
</View> </View>
</Touchable> </Touchable>
<Markdown <Markdown msg={attachment.description} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
msg={attachment.description}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
</> </>
); );
}, },

View File

@ -7,8 +7,8 @@ import { useTheme } from '../../theme';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import messageStyles from './styles'; import messageStyles from './styles';
import MessageContext from './Context'; import MessageContext from './Context';
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils'; import { messageHaveAuthorName } from './utils';
import { MessageType, SubscriptionType } from '../../definitions'; import { MessageType, MessageTypesValues, SubscriptionType } from '../../definitions';
import { IRoomInfoParam } from '../../views/SearchMessagesView'; import { IRoomInfoParam } from '../../views/SearchMessagesView';
import RightIcons from './Components/RightIcons'; import RightIcons from './Components/RightIcons';
@ -88,8 +88,7 @@ const User = React.memo(
{aliasUsername} {aliasUsername}
</> </>
); );
if (messageHaveAuthorName(type as MessageTypesValues)) {
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(type)) {
return ( return (
<Text <Text
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]} style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
@ -114,8 +113,8 @@ const User = React.memo(
type={type} type={type}
isEdited={isEdited} isEdited={isEdited}
hasError={hasError} hasError={hasError}
isReadReceiptEnabled={props.isReadReceiptEnabled || false} isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread || false} unread={props.unread}
/> />
</View> </View>
); );

View File

@ -79,7 +79,6 @@ const Video = React.memo(
<> <>
<Markdown <Markdown
msg={file.description} msg={file.description}
baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
style={[isReply && style]} style={[isReply && style]}

View File

@ -67,7 +67,6 @@ export interface IMessageContent {
export interface IMessageEmoji { export interface IMessageEmoji {
content: string; content: string;
baseUrl: string;
standardEmojiStyle: { fontSize: number }; standardEmojiStyle: { fontSize: number };
customEmojiStyle: StyleProp<ImageStyle>; customEmojiStyle: StyleProp<ImageStyle>;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;

View File

@ -1,5 +1,5 @@
/* eslint-disable complexity */ /* eslint-disable complexity */
import { TMessageModel } from '../../definitions/IMessage'; import { MessageTypesValues, TMessageModel } from '../../definitions/IMessage';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DISCUSSION } from './constants'; import { DISCUSSION } from './constants';
@ -26,30 +26,18 @@ export const BUTTON_HIT_SLOP = {
left: 4 left: 4
}; };
export const SYSTEM_MESSAGES = [ const messagesWithAuthorName: MessageTypesValues[] = [
'r', 'r',
'au',
'ru', 'ru',
'ul', 'au',
'ult', 'rm',
'uj', 'uj',
'ujt', 'ujt',
'ut', 'ut',
'rm', 'ul',
'user-muted', 'ult',
'user-unmuted',
'message_pinned', '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', 'message_snippeted',
'thread-created',
'room_e2e_enabled',
'room_e2e_disabled',
'removed-user-from-team', 'removed-user-from-team',
'added-user-to-team', 'added-user-to-team',
'user-added-room-to-team', 'user-added-room-to-team',
@ -57,81 +45,39 @@ export const SYSTEM_MESSAGES = [
'user-converted-to-channel', 'user-converted-to-channel',
'user-deleted-room-from-team', 'user-deleted-room-from-team',
'user-removed-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_placed_chat_on_hold',
'omnichannel_on_hold_chat_resumed' 'omnichannel_on_hold_chat_resumed',
];
export const IGNORED_LIVECHAT_SYSTEM_MESSAGES = [
'livechat_navigation_history', 'livechat_navigation_history',
'livechat_transcript_history', 'livechat_transcript_history',
'livechat_transfer_history',
'command', 'command',
'livechat-close',
'livechat-started', 'livechat-started',
'livechat-close',
'livechat_video_call', '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 = { export const messageHaveAuthorName = (type: MessageTypesValues): boolean => messagesWithAuthorName.includes(type);
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
];
type TInfoMessage = { type TInfoMessage = {
type: string; type: MessageTypesValues;
role: string; role: string;
msg: string; msg: string;
author: { username: string }; author: { username: string };
@ -141,137 +87,109 @@ type TInfoMessage = {
export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessage): string => { export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessage): string => {
const { username } = author; const { username } = author;
if (type === 'rm') { switch (type) {
return I18n.t('Message_removed'); // 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) { if (!autoTranslateLanguage) {
return null; return null;
} }
const { translations } = message; const { translations } = message;
if (translations) { if (translations) {
const translation = translations.find((trans: any) => trans.language === autoTranslateLanguage); const translation = translations.find((trans: any) => trans.language === autoTranslateLanguage);
return translation && translation.value; return translation?.value || null;
} }
return null; return null;
}; };

View File

@ -1,43 +1,35 @@
import Model from '@nozbe/watermelondb/Model'; 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; content: string;
name: string; extension?: string;
extension: string;
isCustom: boolean; isCustom: boolean;
count?: number; count?: number;
} }
export interface ICustomEmojis { type TBasicEmoji = string;
[key: string]: Pick<IEmoji, 'name' | 'extension'>;
}
export interface ICustomEmoji { export interface ICustomEmoji {
baseUrl?: string; name: string;
emoji: IEmoji; extension: string;
style: StyleProp<ImageStyle>;
} }
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 { export interface ICustomEmojiModel {
_id: string; _id: string;
name?: string; name: string;
aliases?: string[]; aliases?: string[];
extension: string; extension: string;
_updatedAt: Date; _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; export type TCustomEmojiModel = ICustomEmojiModel & Model;

View File

@ -8,7 +8,7 @@ import { TThreadMessageModel } from './IThreadMessage';
import { TThreadModel } from './IThread'; import { TThreadModel } from './IThread';
import { IUrl, IUrlFromServer } from './IUrl'; 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 { export interface IUserMessage {
_id: string; _id: string;
@ -153,3 +153,81 @@ export interface IReadReceipts {
ts: string; ts: string;
user?: IUserMessage; 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';

View File

@ -24,7 +24,7 @@ export type TeamsEndpoints = {
'teams.create': { 'teams.create': {
POST: (params: { POST: (params: {
name: string; name: string;
users: string[]; members: string[];
type: TEAM_TYPE; type: TEAM_TYPE;
room: { readOnly: boolean; extraData: { broadcast: boolean; encrypted: boolean } }; room: { readOnly: boolean; extraData: { broadcast: boolean; encrypted: boolean } };
}) => { team: ITeam }; }) => { team: ITeam };

View File

@ -26,6 +26,9 @@ export const DimensionsContext = React.createContext<IDimensionsContextProps>(
Dimensions.get('window') as 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 { export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
const DimensionsComponent = (props: T) => ( const DimensionsComponent = (props: T) => (
<DimensionsContext.Consumer>{contexts => <Component {...props} {...contexts} />}</DimensionsContext.Consumer> <DimensionsContext.Consumer>{contexts => <Component {...props} {...contexts} />}</DimensionsContext.Consumer>
@ -35,8 +38,14 @@ export function withDimensions<T extends object>(Component: React.ComponentType<
return DimensionsComponent; return DimensionsComponent;
} }
/**
* @deprecated use RN's useWindowDimensions hook instead
*/
export const useDimensions = () => React.useContext(DimensionsContext); export const useDimensions = () => React.useContext(DimensionsContext);
/**
* @deprecated use RN's useWindowDimensions hook instead
*/
export const useOrientation = () => { export const useOrientation = () => {
const { width, height } = React.useContext(DimensionsContext); const { width, height } = React.useContext(DimensionsContext);
const isPortrait = height > width; const isPortrait = height > width;

View File

@ -244,9 +244,6 @@
"Forward_to_user": "إعادة توجيه لمستخدم", "Forward_to_user": "إعادة توجيه لمستخدم",
"Full_table": "انقر لرؤية الجدول كاملاً", "Full_table": "انقر لرؤية الجدول كاملاً",
"Generate_New_Link": "إنشاء رابط جديد", "Generate_New_Link": "إنشاء رابط جديد",
"Has_joined_the_channel": "انضم إلى القناة",
"Has_joined_the_conversation": "انضم إلى المحادثة",
"Has_left_the_channel": "غادر القناة",
"Hide_System_Messages": "إخفاء رسائل النظام", "Hide_System_Messages": "إخفاء رسائل النظام",
"Hide_type_messages": "إخفاء رسائل \"{{type}}\"", "Hide_type_messages": "إخفاء رسائل \"{{type}}\"",
"How_It_Works": "طريقة العمل", "How_It_Works": "طريقة العمل",
@ -430,15 +427,11 @@
"Roles": "أدوار", "Roles": "أدوار",
"Room_actions": "إجراءات الغرفة", "Room_actions": "إجراءات الغرفة",
"Room_changed_announcement": "تم تغيير إعلان الغرفة إلى: {{announcement}} من قبل {{userBy}}", "Room_changed_announcement": "تم تغيير إعلان الغرفة إلى: {{announcement}} من قبل {{userBy}}",
"Room_changed_avatar": " {{userBy}}تم تغيير الصورة الرمزية للغرفة",
"Room_changed_description": "تم تغيير وصف الغرفة إلى: {{description}} من قبل {{userBy}}", "Room_changed_description": "تم تغيير وصف الغرفة إلى: {{description}} من قبل {{userBy}}",
"Room_changed_privacy": "تم تغيير نوع الغرفة إلى: {{type}} من قبل {{userBy}}",
"Room_changed_topic": "تم تغيير موضوع الغرفة إلى: {{topic}} من قبل {{userBy}}",
"Room_Files": "ملفات الغرفة", "Room_Files": "ملفات الغرفة",
"Room_Info_Edit": "تعديل معلومات الغرفة", "Room_Info_Edit": "تعديل معلومات الغرفة",
"Room_Info": "معلومات الغرفة", "Room_Info": "معلومات الغرفة",
"Room_Members": "أعضاء الغرفة", "Room_Members": "أعضاء الغرفة",
"Room_name_changed": "تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}",
"SAVE": "حفظ", "SAVE": "حفظ",
"Save_Changes": "حفظ التغيرات", "Save_Changes": "حفظ التغيرات",
"Save": "حفظ", "Save": "حفظ",
@ -540,12 +533,9 @@
"Upload_file_question_mark": "رفع الملف؟", "Upload_file_question_mark": "رفع الملف؟",
"User": "مستخدم", "User": "مستخدم",
"Users": "مستخدمين", "Users": "مستخدمين",
"User_added_by": "مستخدم {{userAdded}} أضيف من قبل {{userBy}}",
"User_Info": "معلومات المستخدم", "User_Info": "معلومات المستخدم",
"User_has_been_key": "تمت {{key}} المستخدم!", "User_has_been_key": "تمت {{key}} المستخدم!",
"User_is_no_longer_role_by_": "تم إزالة الدور {{role}} عن المستخدم {{user}} من قبل {{userBy}}", "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_sent_an_attachment": "{{user}} أرسل مرفقًا",
"User_unmuted_by": "ألغى {{userBy}} الكتم عن {{userUnmuted}}", "User_unmuted_by": "ألغى {{userBy}} الكتم عن {{userUnmuted}}",
"User_was_set_role_by_": "أضيف دور {{role}} للمستخدم {{user}} من قبل {{userBy}}", "User_was_set_role_by_": "أضيف دور {{role}} للمستخدم {{user}} من قبل {{userBy}}",

View File

@ -247,10 +247,6 @@
"Forward_to_user": "Weiterleiten an Benutzer", "Forward_to_user": "Weiterleiten an Benutzer",
"Full_table": "Klicken, um die ganze Tabelle anzuzeigen", "Full_table": "Klicken, um die ganze Tabelle anzuzeigen",
"Generate_New_Link": "Neuen Link erstellen", "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", "Has_left_the_team": "Hat das Team verlassen",
"Hide_System_Messages": "Systemnachrichten ausblenden", "Hide_System_Messages": "Systemnachrichten ausblenden",
"Hide_type_messages": "\"{{type}}\"-Nachrichten ausblenden", "Hide_type_messages": "\"{{type}}\"-Nachrichten ausblenden",
@ -436,15 +432,11 @@
"Roles": "Rollen", "Roles": "Rollen",
"Room_actions": "Room-Aktionen", "Room_actions": "Room-Aktionen",
"Room_changed_announcement": "Room-Ansage geändert in: {{announcement}} von {{userBy}}", "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_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_Files": "Room-Dateien",
"Room_Info_Edit": "Room-Info bearbeiten", "Room_Info_Edit": "Room-Info bearbeiten",
"Room_Info": "Room-Info", "Room_Info": "Room-Info",
"Room_Members": "Room-Mitglieder", "Room_Members": "Room-Mitglieder",
"Room_name_changed": "Room-Name geändert in {{name}} von {{userBy}}",
"SAVE": "SPEICHERN", "SAVE": "SPEICHERN",
"Save_Changes": "Änderungen speichern", "Save_Changes": "Änderungen speichern",
"Save": "speichern", "Save": "speichern",
@ -546,12 +538,9 @@
"Upload_file_question_mark": "Datei hochladen?", "Upload_file_question_mark": "Datei hochladen?",
"User": "Benutzer", "User": "Benutzer",
"Users": "Benutzer", "Users": "Benutzer",
"User_added_by": "Benutzer {{userAdded}} hinzugefügt von {{userBy}}",
"User_Info": "Benutzerinfo", "User_Info": "Benutzerinfo",
"User_has_been_key": "Benutzer wurde {{key}}", "User_has_been_key": "Benutzer wurde {{key}}",
"User_is_no_longer_role_by_": "{{user}} ist nicht länger {{role}} von {{userBy}}", "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_sent_an_attachment": "{{user}}: eine Datei gesendet",
"User_unmuted_by": "Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}", "User_unmuted_by": "Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}",
"User_was_set_role_by_": "{{user}} wurde von {{userBy}} {{role}} festgelegt.", "User_was_set_role_by_": "{{user}} wurde von {{userBy}} {{role}} festgelegt.",
@ -695,8 +684,6 @@
"Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.", "Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.",
"Enter_workspace_URL": "Arbeitsbereich-URL", "Enter_workspace_URL": "Arbeitsbereich-URL",
"Workspace_URL_Example": "z.B. https://rocketchat.deine-firma.de", "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", "Teams": "Teams",
"No_team_channels_found": "Keine Kanäle gefunden", "No_team_channels_found": "Keine Kanäle gefunden",
"Team_not_found": "Team nicht 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_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_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", "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_joined_team": "ist dem Team beigetreten",
"User_left_team": "hat das Team verlassen" "User_left_team": "hat das Team verlassen"
} }

View File

@ -257,10 +257,10 @@
"Forward_to_user": "Forward to user", "Forward_to_user": "Forward to user",
"Full_table": "Click to see full table", "Full_table": "Click to see full table",
"Generate_New_Link": "Generate New Link", "Generate_New_Link": "Generate New Link",
"Has_joined_the_channel": "has joined the channel", "User_joined_the_channel": "joined the channel",
"Has_joined_the_team": "has joined the team", "User_joined_the_conversation": "joined the conversation",
"Has_joined_the_conversation": "has joined the conversation", "User_joined_the_team": "joined this team",
"Has_left_the_channel": "has left the channel", "User_left_this_channel": "left the channel",
"Has_left_the_team": "has left the team", "Has_left_the_team": "has left the team",
"Hide_System_Messages": "Hide System Messages", "Hide_System_Messages": "Hide System Messages",
"Hide_type_messages": "Hide \"{{type}}\" messages", "Hide_type_messages": "Hide \"{{type}}\" messages",
@ -326,7 +326,7 @@
"Message_accessibility": "Message from {{user}} at {{time}}: {{message}}", "Message_accessibility": "Message from {{user}} at {{time}}: {{message}}",
"Message_actions": "Message actions", "Message_actions": "Message actions",
"Message_pinned": "Message pinned", "Message_pinned": "Message pinned",
"Message_removed": "Message removed", "Message_removed": "message removed",
"Message_starred": "Message starred", "Message_starred": "Message starred",
"Message_unstarred": "Message unstarred", "Message_unstarred": "Message unstarred",
"message": "message", "message": "message",
@ -450,19 +450,21 @@
"Roles": "Roles", "Roles": "Roles",
"Room_actions": "Room actions", "Room_actions": "Room actions",
"Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}", "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_description": "Room description changed to: {{description}} by {{userBy}}",
"Room_changed_privacy": "Room type changed to: {{type}} by {{userBy}}", "changed_room_description": "changed room description to: {{description}}",
"Room_changed_topic": "Room topic changed to: {{topic}} by {{userBy}}", "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_Files": "Room Files",
"Room_Info_Edit": "Room Info Edit", "Room_Info_Edit": "Room Info Edit",
"Room_Info": "Room Info", "Room_Info": "Room Info",
"Room_Members": "Room Members", "Room_Members": "Room Members",
"Room_name_changed": "Room name changed to: {{name}} by {{userBy}}", "Room_name_changed_to": "changed room name to: {{name}}",
"Room_disallowed_reacting": "Room disallowed reacting by {{userBy}}", "room_disallowed_reactions": "disallowed reactions",
"Room_allowed_reacting": "Room allowed reacting by {{userBy}}", "room_allowed_reactions": "allowed reactions",
"Room_set_read_only": "Room set read only by {{userBy}}", "room_removed_read_only_permission": "removed read only permission",
"Room_removed_read_only": "Room removed read only by {{userBy}}", "room_set_read_only_permission": "set room to read only",
"SAVE": "SAVE", "SAVE": "SAVE",
"Save_Changes": "Save Changes", "Save_Changes": "Save Changes",
"Save": "Save", "Save": "Save",
@ -478,6 +480,7 @@
"Search_Messages": "Search Messages", "Search_Messages": "Search Messages",
"Search": "Search", "Search": "Search",
"Search_by": "Search by", "Search_by": "Search by",
"Search_emoji": "Search emoji",
"Search_global_users": "Search for global users", "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.", "Search_global_users_description": "If you turn-on, you can search for any user from others companies or servers.",
"Seconds": "{{second}} seconds", "Seconds": "{{second}} seconds",
@ -565,15 +568,16 @@
"Upload_file_question_mark": "Upload file?", "Upload_file_question_mark": "Upload file?",
"User": "User", "User": "User",
"Users": "Users", "Users": "Users",
"User_added_by": "User {{userAdded}} added by {{userBy}}", "User_added_to": "added {{userAdded}}",
"User_Info": "User Info", "User_Info": "User Info",
"User_has_been_key": "User has been {{key}}", "User_has_been_key": "User has been {{key}}",
"User_is_no_longer_role_by_": "{{user}} is no longer {{role}} by {{userBy}}", "User_is_no_longer_role_by_": "{{user}} is no longer {{role}} by {{userBy}}",
"User_muted_by": "User {{userMuted}} muted by {{userBy}}", "User_has_been_muted": "muted {{userMuted}}",
"User_removed_by": "User {{userRemoved}} removed by {{userBy}}", "User_has_been_removed": "removed {{userRemoved}}",
"User_sent_an_attachment": "{{user}} sent an attachment", "User_sent_an_attachment": "{{user}} sent an attachment",
"User_unmuted_by": "User {{userUnmuted}} unmuted by {{userBy}}", "User_has_been_unmuted": "unmuted {{userUnmuted}}",
"User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}", "Defined_user_as_role": "defined {{user}} as {{role}}",
"Removed_user_as_role": "removed {{user}} as {{role}}",
"Username_is_empty": "Username is empty", "Username_is_empty": "Username is empty",
"Username": "Username", "Username": "Username",
"Username_or_email": "Username or email", "Username_or_email": "Username or email",
@ -715,8 +719,8 @@
"Message_Ignored": "Message ignored. Tap to display it.", "Message_Ignored": "Message ignored. Tap to display it.",
"Enter_workspace_URL": "Enter workspace URL", "Enter_workspace_URL": "Enter workspace URL",
"Workspace_URL_Example": "Ex. your-company.rocket.chat", "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}}", "Enabled_E2E_Encryption_for_this_room": "enabled E2E Encryption for this room",
"This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}", "Disabled_E2E_Encryption_for_this_room": "disabled E2E Encryption for this room",
"Teams": "Teams", "Teams": "Teams",
"No_team_channels_found": "No channels found", "No_team_channels_found": "No channels found",
"Team_not_found": "Team not found", "Team_not_found": "Team not found",
@ -800,11 +804,11 @@
"Unsupported_format": "Unsupported format", "Unsupported_format": "Unsupported format",
"Downloaded_file": "Downloaded file", "Downloaded_file": "Downloaded file",
"Error_Download_file": "Error while downloading file", "Error_Download_file": "Error while downloading file",
"added__roomName__to_team": "added #{{roomName}} to this Team", "added__roomName__to_this_team": "added #{{roomName}} to this team",
"Added__username__to_team": "added @{{user_added}} to this Team", "Added__username__to_this_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",
"Converting_team_to_channel": "Converting Team to Channel", "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}}", "Deleted__roomName__": "deleted #{{roomName}}",
"Message_HideType_added_user_to_team": "Hide \"User Added to Team\" messages", "Message_HideType_added_user_to_team": "Hide \"User Added to Team\" messages",
"Message_HideType_removed_user_from_team": "Hide \"User Removed from 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_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_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", "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__roomName__from_the_team": "removed #{{roomName}} from this team",
"Removed__username__from_team": "removed @{{user_removed}} from this Team", "Removed__username__from_the_team": "removed @{{userRemoved}} from this team",
"User_joined_team": "joined 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", "Place_chat_on_hold": "Place chat on-hold",
"Would_like_to_place_on_hold": "Would you like to place this chat On-Hold?", "Would_like_to_place_on_hold": "Would you like to place this chat On-Hold?",
"Open_Livechats": "Omnichannel chats in progress", "Open_Livechats": "Omnichannel chats in progress",
@ -857,5 +861,7 @@
"Team": "Team", "Team": "Team",
"Select_Members": "Select Members", "Select_Members": "Select Members",
"Also_send_thread_message_to_channel_behavior": "Also send thread message to channel", "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"
} }

View File

@ -185,9 +185,6 @@
"Forgot_password": "¿Ha olvidado su contraseña?", "Forgot_password": "¿Ha olvidado su contraseña?",
"Forgot_Password": "Olvidé la contraseña", "Forgot_Password": "Olvidé la contraseña",
"Full_table": "Click para ver la tabla completa", "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": "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", "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", "Invisible": "Invisible",
@ -305,13 +302,10 @@
"Room_actions": "Acciones de sala", "Room_actions": "Acciones de sala",
"Room_changed_announcement": "El anuncio de la sala cambió a: {{announcement}} por {{userBy}}", "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_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_Files": "Archivos",
"Room_Info_Edit": "Editar información de la sala", "Room_Info_Edit": "Editar información de la sala",
"Room_Info": "Información de la sala", "Room_Info": "Información de la sala",
"Room_Members": "Miembros 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": "GUARDAR",
"Save_Changes": "Guardar cambios", "Save_Changes": "Guardar cambios",
"Save": "Guardar", "Save": "Guardar",
@ -389,11 +383,8 @@
"Uploading": "Subiendo", "Uploading": "Subiendo",
"Upload_file_question_mark": "¿Subir fichero?", "Upload_file_question_mark": "¿Subir fichero?",
"Users": "Usuarios", "Users": "Usuarios",
"User_added_by": "Usuario {{userAdded}} añadido por {{userBy}}",
"User_has_been_key": "El usuario ha sido {{key}}", "User_has_been_key": "El usuario ha sido {{key}}",
"User_is_no_longer_role_by_": "{{user}} ha dejado de ser {{role}} por {{userBy}}", "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_sent_an_attachment": "{{user}} envío un adjunto",
"User_unmuted_by": "Usuario {{userUnmuted}} desmuteado por {{userBy}}", "User_unmuted_by": "Usuario {{userUnmuted}} desmuteado por {{userBy}}",
"User_was_set_role_by_": "{{user}} ha comenzado a ser {{role}} por {{userBy}}", "User_was_set_role_by_": "{{user}} ha comenzado a ser {{role}} por {{userBy}}",

View File

@ -249,10 +249,6 @@
"Forward_to_user": "Transmettre à l'utilisateur", "Forward_to_user": "Transmettre à l'utilisateur",
"Full_table": "Cliquez pour voir le tableau complet", "Full_table": "Cliquez pour voir le tableau complet",
"Generate_New_Link": "Générer un nouveau lien", "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", "Has_left_the_team": "a quitté l'équipe",
"Hide_System_Messages": "Masquer les messages système", "Hide_System_Messages": "Masquer les messages système",
"Hide_type_messages": "Masquer les messages \"{{type}}\"", "Hide_type_messages": "Masquer les messages \"{{type}}\"",
@ -440,19 +436,11 @@
"Roles": "Rôles", "Roles": "Rôles",
"Room_actions": "Actions du salon", "Room_actions": "Actions du salon",
"Room_changed_announcement": "Annonce du salon changé en : {{announcement}} par {{userBy}}", "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_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_Files": "Fichiers du salon",
"Room_Info_Edit": "Modifier les informations du salon", "Room_Info_Edit": "Modifier les informations du salon",
"Room_Info": "Info sur le salon", "Room_Info": "Info sur le salon",
"Room_Members": "Membres du 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": "SAUVEGARDER",
"Save_Changes": "Sauvegarder les modifications", "Save_Changes": "Sauvegarder les modifications",
"Save": "Sauvegarder", "Save": "Sauvegarder",
@ -555,12 +543,9 @@
"Upload_file_question_mark": "Téléverser un fichier ?", "Upload_file_question_mark": "Téléverser un fichier ?",
"User": "Utilisateur", "User": "Utilisateur",
"Users": "Utilisateurs", "Users": "Utilisateurs",
"User_added_by": "Utilisateur {{userAdded}} ajouté par {{userBy}}",
"User_Info": "Info d'utilisateur", "User_Info": "Info d'utilisateur",
"User_has_been_key": "L'utilisateur a été {{key}}", "User_has_been_key": "L'utilisateur a été {{key}}",
"User_is_no_longer_role_by_": "{{user}} n'est plus {{role}} par {{userBy}}", "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_sent_an_attachment": "{{user}} a envoyé une pièce jointe",
"User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole à {{userUnmuted}}", "User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole à {{userUnmuted}}",
"User_was_set_role_by_": "{{user}} a été défini {{role}} par {{userBy}}", "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.", "Message_Ignored": "Message ignoré. Touchez pour l'afficher.",
"Enter_workspace_URL": "Entrez l'URL de l'espace de travail", "Enter_workspace_URL": "Entrez l'URL de l'espace de travail",
"Workspace_URL_Example": "Ex. votre-société.rocket.chat", "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", "Teams": "Equipes",
"No_team_channels_found": "Aucun canal trouvé", "No_team_channels_found": "Aucun canal trouvé",
"Team_not_found": "Equipe non trouvée", "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_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_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\"", "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_joined_team": "a rejoint cette équipe",
"User_left_team": "a quitté cette équipe", "User_left_team": "a quitté cette équipe",
"Place_chat_on_hold": "Mettre le chat en attente", "Place_chat_on_hold": "Mettre le chat en attente",

View File

@ -241,9 +241,6 @@
"Forward_to_user": "Inoltra ad udente", "Forward_to_user": "Inoltra ad udente",
"Full_table": "Clicca per la tabella completa", "Full_table": "Clicca per la tabella completa",
"Generate_New_Link": "Genera nuovo link", "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_System_Messages": "Nascondi messaggi di sistema",
"Hide_type_messages": "Nascondi messaggi di \"{{type}}\"", "Hide_type_messages": "Nascondi messaggi di \"{{type}}\"",
"How_It_Works": "Come funziona", "How_It_Works": "Come funziona",
@ -424,15 +421,11 @@
"Roles": "Ruoli", "Roles": "Ruoli",
"Room_actions": "Azioni stanza", "Room_actions": "Azioni stanza",
"Room_changed_announcement": "Annuncio stanza cambiato in: {{announcement}} da {{userBy}}", "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_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_Files": "File stanza",
"Room_Info_Edit": "Modifica informazioni stanza", "Room_Info_Edit": "Modifica informazioni stanza",
"Room_Info": "Informazioni stanza", "Room_Info": "Informazioni stanza",
"Room_Members": "Membri stanza", "Room_Members": "Membri stanza",
"Room_name_changed": "Nome stanza cambiato in: {{name}} da {{userBy}}",
"SAVE": "SALVA", "SAVE": "SALVA",
"Save_Changes": "Salva cambiamenti", "Save_Changes": "Salva cambiamenti",
"Save": "Salva", "Save": "Salva",
@ -534,12 +527,9 @@
"Upload_file_question_mark": "Carica file?", "Upload_file_question_mark": "Carica file?",
"User": "Utente", "User": "Utente",
"Users": "Utenti", "Users": "Utenti",
"User_added_by": "Utente {{userAdded}} aggiunto da {{userBy}}",
"User_Info": "Informazioni utente", "User_Info": "Informazioni utente",
"User_has_been_key": "Utente {{key}}", "User_has_been_key": "Utente {{key}}",
"User_is_no_longer_role_by_": "{{user}} non è più {{role}} da {{userBy}}", "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_sent_an_attachment": "{{user}} ha inviato un allegato",
"User_unmuted_by": "Utente {{userUnmuted}} de-silenziato da {{userBy}}", "User_unmuted_by": "Utente {{userUnmuted}} de-silenziato da {{userBy}}",
"User_was_set_role_by_": "{{user}} è stato impostato come {{role}} da {{userBy}}", "User_was_set_role_by_": "{{user}} è stato impostato come {{role}} da {{userBy}}",

View File

@ -247,10 +247,6 @@
"Forward_to_user": "ユーザーに転送する", "Forward_to_user": "ユーザーに転送する",
"Full_table": "クリックしてテーブル全体を見る", "Full_table": "クリックしてテーブル全体を見る",
"Generate_New_Link": "新しいリンクを生成", "Generate_New_Link": "新しいリンクを生成",
"Has_joined_the_channel": "はチャンネルに参加しました",
"Has_joined_the_team": "チームに参加しました",
"Has_joined_the_conversation": "は会話に参加しました",
"Has_left_the_channel": "はチャンネルを退出しました",
"Has_left_the_team": "チームを退出しました", "Has_left_the_team": "チームを退出しました",
"Hide_System_Messages": "システムメッセージを非表示にする", "Hide_System_Messages": "システムメッセージを非表示にする",
"Hide_type_messages": "\"{{type}}\"メッセージを非表示にする", "Hide_type_messages": "\"{{type}}\"メッセージを非表示にする",
@ -410,13 +406,10 @@
"Room_actions": "ルームアクション", "Room_actions": "ルームアクション",
"Room_changed_announcement": "{{userBy}}がアナウンスを変更しました: {{announcement}}", "Room_changed_announcement": "{{userBy}}がアナウンスを変更しました: {{announcement}}",
"Room_changed_description": "{{userBy}}が概要を変更しました: {{description}}", "Room_changed_description": "{{userBy}}が概要を変更しました: {{description}}",
"Room_changed_privacy": "{{userBy}}がルームタイプを変更しました。: {{type}}",
"Room_changed_topic": "{{userBy}}がトピックを変更しました: {{topic}}",
"Room_Files": "ルームのファイル", "Room_Files": "ルームのファイル",
"Room_Info_Edit": "ルーム情報を編集", "Room_Info_Edit": "ルーム情報を編集",
"Room_Info": "ルーム情報", "Room_Info": "ルーム情報",
"Room_Members": "ルームのメンバー", "Room_Members": "ルームのメンバー",
"Room_name_changed": "ルーム名が{{userBy}}により変更されました: {{name}}",
"SAVE": "保存", "SAVE": "保存",
"Save_Changes": "変更を保存", "Save_Changes": "変更を保存",
"Save": "保存", "Save": "保存",
@ -498,12 +491,9 @@
"Upload_file_question_mark": "ファイルをアップロードしますか?", "Upload_file_question_mark": "ファイルをアップロードしますか?",
"User": "ユーザー", "User": "ユーザー",
"Users": "ユーザー", "Users": "ユーザー",
"User_added_by": "{{userBy}} が {{userAdded}} を追加しました",
"User_Info": "ユーザー情報", "User_Info": "ユーザー情報",
"User_has_been_key": "ユーザーは{{key}}", "User_has_been_key": "ユーザーは{{key}}",
"User_is_no_longer_role_by_": "{{userBy}} は {{user}} のロール {{role}} を削除しました。", "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_sent_an_attachment": "{{user}}は添付ファイルを送信しました",
"User_unmuted_by": "{{userUnmuted}} は {{userBy}} にミュート解除されました。", "User_unmuted_by": "{{userUnmuted}} は {{userBy}} にミュート解除されました。",
"User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}", "User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}",

View File

@ -249,10 +249,6 @@
"Forward_to_user": "Doorsturen naar gebruiker", "Forward_to_user": "Doorsturen naar gebruiker",
"Full_table": "Klik om de volledige tabel te zien", "Full_table": "Klik om de volledige tabel te zien",
"Generate_New_Link": "Nieuwe link genereren", "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", "Has_left_the_team": "heeft het team verlaten",
"Hide_System_Messages": "Verberg systeemberichten", "Hide_System_Messages": "Verberg systeemberichten",
"Hide_type_messages": "Verberg \"{{type}}\" berichten", "Hide_type_messages": "Verberg \"{{type}}\" berichten",
@ -440,19 +436,12 @@
"Roles": "Rollen", "Roles": "Rollen",
"Room_actions": "Kameracties", "Room_actions": "Kameracties",
"Room_changed_announcement": "Kameraankondiging gewijzigd in: {{announcement}} door {{userBy}}", "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_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_Files": "Kamerbestanden",
"Room_Info_Edit": "Kamer info bewerken", "Room_Info_Edit": "Kamer info bewerken",
"Room_Info": "Kamer info", "Room_Info": "Kamer info",
"Room_Members": "Kamerleden", "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_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": "OPSLAAN",
"Save_Changes": "Wijzigingen opslaan", "Save_Changes": "Wijzigingen opslaan",
"Save": "Opslaan", "Save": "Opslaan",
@ -555,12 +544,9 @@
"Upload_file_question_mark": "Bestand uploaden?", "Upload_file_question_mark": "Bestand uploaden?",
"User": "Gebruiker", "User": "Gebruiker",
"Users": "Gebruikers", "Users": "Gebruikers",
"User_added_by": "Gebruiker {{userAdded}} toegevoegd door {{userBy}}",
"User_Info": "Gebruikers info", "User_Info": "Gebruikers info",
"User_has_been_key": "Gebruiker is {{key}}", "User_has_been_key": "Gebruiker is {{key}}",
"User_is_no_longer_role_by_": "{{user}} is niet langer {{role}} door {{userBy}}", "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_sent_an_attachment": "{{user}} stuurde een bijlage",
"User_unmuted_by": "Dempen voor {{userUnmuted}} opgeheven door {{userBy}}", "User_unmuted_by": "Dempen voor {{userUnmuted}} opgeheven door {{userBy}}",
"User_was_set_role_by_": "{{user}} is als {{role}} ingesteld 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.", "Message_Ignored": "Bericht genegeerd. Tik om het weer te geven.",
"Enter_workspace_URL": "Voer de werkruimte-URL in", "Enter_workspace_URL": "Voer de werkruimte-URL in",
"Workspace_URL_Example": "Vb. uw-bedrijf.rocket.chat", "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", "Teams": "Teams",
"No_team_channels_found": "Geen kanalen gevonden", "No_team_channels_found": "Geen kanalen gevonden",
"Team_not_found": "Team niet 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_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_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", "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_joined_team": "is lid geworden van dit team",
"User_left_team": "heeft dit team verlaten", "User_left_team": "heeft dit team verlaten",
"Place_chat_on_hold": "Chat in de wacht zetten", "Place_chat_on_hold": "Chat in de wacht zetten",

View File

@ -248,10 +248,10 @@
"Forward_to_user": "Encaminhar para usuário", "Forward_to_user": "Encaminhar para usuário",
"Full_table": "Clique para ver a tabela completa", "Full_table": "Clique para ver a tabela completa",
"Generate_New_Link": "Gerar novo convite", "Generate_New_Link": "Gerar novo convite",
"Has_joined_the_channel": "entrou no canal", "User_joined_the_channel": "entrou no canal",
"Has_joined_the_team": "entrou na equipe", "User_joined_the_team": "entrou na equipe",
"Has_joined_the_conversation": "entrou na conversa", "User_joined_the_conversation": "entrou na conversa",
"Has_left_the_channel": "saiu da conversa", "User_left_this_channel": "saiu da conversa",
"Has_left_the_team": "saiu da equipe", "Has_left_the_team": "saiu da equipe",
"Hide_System_Messages": "Esconder mensagens do sistema", "Hide_System_Messages": "Esconder mensagens do sistema",
"Hide_type_messages": "Esconder mensagens de \"{{type}}\"", "Hide_type_messages": "Esconder mensagens de \"{{type}}\"",
@ -309,7 +309,7 @@
"Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}", "Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}",
"Message_actions": "Ações", "Message_actions": "Ações",
"Message_pinned": "Fixou uma mensagem", "Message_pinned": "Fixou uma mensagem",
"Message_removed": "Mensagem removida", "Message_removed": "mensagem removida",
"message": "mensagem", "message": "mensagem",
"messages": "mensagens", "messages": "mensagens",
"Message": "Mensagem", "Message": "Mensagem",
@ -382,6 +382,7 @@
"Preferences": "Preferências", "Preferences": "Preferências",
"Preferences_saved": "Preferências salvas!", "Preferences_saved": "Preferências salvas!",
"Privacy_Policy": " Política de Privacidade", "Privacy_Policy": " Política de Privacidade",
"Private_Channel": "Canal Privado",
"Private": "Privado", "Private": "Privado",
"Processing": "Processando...", "Processing": "Processando...",
"Profile_saved_successfully": "Perfil salvo com sucesso!", "Profile_saved_successfully": "Perfil salvo com sucesso!",
@ -426,19 +427,21 @@
"Roles": "Papéis", "Roles": "Papéis",
"Room_actions": "Ações", "Room_actions": "Ações",
"Room_changed_announcement": "O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}", "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_description": "A descrição da sala foi alterada para: {{description}} por {{userBy}}",
"Room_changed_privacy": "Tipo da sala mudou para: {{type}} por {{userBy}}", "changed_room_description": "alterou a descrição da sala para: {{description}}",
"Room_changed_topic": "Tópico da sala mudou para: {{topic}} por {{userBy}}", "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_Files": "Arquivos",
"Room_Info_Edit": "Editar", "Room_Info_Edit": "Editar",
"Room_Info": "Informações da Sala", "Room_Info": "Informações da Sala",
"Room_Members": "Membros", "Room_Members": "Membros",
"Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}", "Room_name_changed_to": "alterou o nome da sala para: {{name}}",
"Room_disallowed_reacting": "Permissão de reagir removida da sala por {{userBy}}", "room_disallowed_reactions": "removeu a permissão de reagir",
"Room_allowed_reacting": "Permissão de reagir adicionada à sala por {{userBy}}", "room_allowed_reactions": "adicionou permissão de reagir",
"Room_set_read_only": "Sala definida como somente leitura por {{userBy}}", "room_removed_read_only_permission": "removeu permissão de escrita da sala",
"Room_removed_read_only": "Permissão de escrita adicionada à sala por {{userBy}}", "room_set_read_only_permission": "adicionou permissão de escrita à sala",
"SAVE": "SALVAR", "SAVE": "SALVAR",
"Save_Changes": "Salvar Alterações", "Save_Changes": "Salvar Alterações",
"Save": "Salvar", "Save": "Salvar",
@ -454,6 +457,7 @@
"Search_Messages": "Buscar Mensagens", "Search_Messages": "Buscar Mensagens",
"Search": "Buscar", "Search": "Buscar",
"Search_by": "Buscar por", "Search_by": "Buscar por",
"Search_emoji": "Buscar emoji",
"Search_global_users": "Busca por usuários globais", "Search_global_users": "Busca por usuários globais",
"Search_global_users_description": "Caso ativado, busca por usuários de outras empresas ou servidores.", "Search_global_users_description": "Caso ativado, busca por usuários de outras empresas ou servidores.",
"Security_and_privacy": "Segurança e privacidade", "Security_and_privacy": "Segurança e privacidade",
@ -530,14 +534,14 @@
"Upload_file_question_mark": "Enviar arquivo?", "Upload_file_question_mark": "Enviar arquivo?",
"User": "Usuário", "User": "Usuário",
"Users": "Usuários", "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_Info": "Informações do usuário",
"User_has_been_key": "Usuário foi {{key}}", "User_has_been_key": "Usuário foi {{key}}",
"User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}", "User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}",
"User_muted_by": "User {{userMuted}} muted por {{userBy}}", "User_has_been_muted": "silenciou o usuário {{userMuted}}",
"User_removed_by": "Usuário {{userRemoved}} removido por {{userBy}}", "User_has_been_removed": "removeu {{userRemoved}}",
"User_sent_an_attachment": "{{user}} enviou um anexo", "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}}", "User_was_set_role_by_": "{{user}} foi definido como {{role}} por {{userBy}}",
"Username_is_empty": "Usuário está vazio", "Username_is_empty": "Usuário está vazio",
"Username": "Usuário", "Username": "Usuário",
@ -674,8 +678,8 @@
"Message_Ignored": "Mensagem ignorada. Toque para mostrar.", "Message_Ignored": "Mensagem ignorada. Toque para mostrar.",
"Enter_workspace_URL": "Digite a URL da sua workspace", "Enter_workspace_URL": "Digite a URL da sua workspace",
"Workspace_URL_Example": "Ex. sua-empresa.rocket.chat", "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}}", "Enabled_E2E_Encryption_for_this_room": "habilitou criptografia para essa sala",
"This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}", "Disabled_E2E_Encryption_for_this_room": "desabilitou criptografia para essa sala",
"Teams": "Times", "Teams": "Times",
"No_team_channels_found": "Nenhum canal encontrado", "No_team_channels_found": "Nenhum canal encontrado",
"Team_not_found": "Time não encontrado", "Team_not_found": "Time não encontrado",
@ -756,11 +760,11 @@
"Downloaded_file": "Arquivo baixado", "Downloaded_file": "Arquivo baixado",
"Error_Download_file": "Erro ao baixar o arquivo", "Error_Download_file": "Erro ao baixar o arquivo",
"error-init-video-conf": "Erro ao iniciar chamada de video", "error-init-video-conf": "Erro ao iniciar chamada de video",
"added__roomName__to_team": "#{{roomName}} adicionada a esta equipe", "added__roomName__to_this_team": "adicionou #{{roomName}} a esta equipe",
"Added__username__to_team": "@{{user_added}} adicionado a esta equipe", "Added__username__to_this_team": "adicionou @{{user_added}} a esta equipe",
"Converted__roomName__to_team": "#{{roomName}} convertida em equipe",
"Converted__roomName__to_channel": "#{{roomName}} convertida em canal",
"Converting_team_to_channel": "Convertendo equipe em canal", "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", "Deleted__roomName__": "#{{roomName}} apagada",
"Message_HideType_added_user_to_team": "Ocultar mensagens de \"Usuário adicionado à equipe\"", "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\"", "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_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_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\"", "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__roomName__from_the_team": "removeu #{{roomName}} desta equipe",
"Removed__username__from_team": "@{{user_removed}} removido desta equipe", "Removed__username__from_the_team": "removeu @{{userRemoved}} desta equipe",
"User_joined_team": "entrou nesta equipe", "User_joined_team": "entrou nesta equipe",
"User_left_team": "saiu desta equipe", "User_left_team": "saiu desta equipe",
"Place_chat_on_hold": "Colocar conversa em espera", "Place_chat_on_hold": "Colocar conversa em espera",
@ -812,5 +816,7 @@
"Team": "Time", "Team": "Time",
"Select_Members": "Selecionar Membros", "Select_Members": "Selecionar Membros",
"Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal", "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"
} }

View File

@ -246,9 +246,6 @@
"Forward_to_user": "Reencaminhar para o utilizador", "Forward_to_user": "Reencaminhar para o utilizador",
"Full_table": "Clique para ver a tabela completa", "Full_table": "Clique para ver a tabela completa",
"Generate_New_Link": "Gerar Novo Link", "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_System_Messages": "Esconder mensagens do sistema",
"Hide_type_messages": "Esconder mensagens \"{{type}}\"", "Hide_type_messages": "Esconder mensagens \"{{type}}\"",
"How_It_Works": "Como Funciona", "How_It_Works": "Como Funciona",
@ -416,13 +413,10 @@
"Room_actions": "Ações de sala", "Room_actions": "Ações de sala",
"Room_changed_announcement": "Anúncio da sala alterado para: {{announcement}} por {{userBy}}", "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_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_Files": "Fiheiros da Sala",
"Room_Info_Edit": "Editar Informação da Sala", "Room_Info_Edit": "Editar Informação da Sala",
"Room_Info": "Informação da Sala", "Room_Info": "Informação da Sala",
"Room_Members": "Membros da Sala", "Room_Members": "Membros da Sala",
"Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}",
"SAVE": "GUARDAR", "SAVE": "GUARDAR",
"Save_Changes": "Guardar Alterações", "Save_Changes": "Guardar Alterações",
"Save": "Guardar", "Save": "Guardar",
@ -479,11 +473,8 @@
"Updating": "A actualizar...", "Updating": "A actualizar...",
"Uploading": "A enviar", "Uploading": "A enviar",
"Upload_file_question_mark": "Enviar ficheiro?", "Upload_file_question_mark": "Enviar ficheiro?",
"User_added_by": "Utilizador {{userAdded}} adicionado por {{userBy}}",
"User_has_been_key": "Utilizador foi {{key}}", "User_has_been_key": "Utilizador foi {{key}}",
"User_is_no_longer_role_by_": "{{userBy}} removeu o estatuto de {{role}} de {{user}}", "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_sent_an_attachment": "{{user}} enviou um ficheiro",
"User_unmuted_by": "{{userBy}} retirou o silêncio a {{userUnmuted}}", "User_unmuted_by": "{{userBy}} retirou o silêncio a {{userUnmuted}}",
"User_was_set_role_by_": "{{userBy}} deu estatuto de {{role}} a {{user}}", "User_was_set_role_by_": "{{userBy}} deu estatuto de {{role}} a {{user}}",

View File

@ -247,10 +247,6 @@
"Forward_to_user": "Перенаправить пользователю", "Forward_to_user": "Перенаправить пользователю",
"Full_table": "Нажмите, чтобы увидеть полную таблицу", "Full_table": "Нажмите, чтобы увидеть полную таблицу",
"Generate_New_Link": "Сгенерировать Новую Ссылку", "Generate_New_Link": "Сгенерировать Новую Ссылку",
"Has_joined_the_channel": "присоединился к каналу",
"Has_joined_the_team": "присоединился к Команде",
"Has_joined_the_conversation": "присоединился к беседе",
"Has_left_the_channel": "покинул канал",
"Has_left_the_team": "покинул Команду", "Has_left_the_team": "покинул Команду",
"Hide_System_Messages": "Скрыть Системные Сообщения", "Hide_System_Messages": "Скрыть Системные Сообщения",
"Hide_type_messages": "Скрыть \"{{type}}\" сообщения", "Hide_type_messages": "Скрыть \"{{type}}\" сообщения",
@ -436,15 +432,11 @@
"Roles": "Роли", "Roles": "Роли",
"Room_actions": "Действия с чатом", "Room_actions": "Действия с чатом",
"Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}", "Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}",
"Room_changed_avatar": "Аватар чата изменен пользователем {{userBy}}",
"Room_changed_description": "Описание чата было изменено на: {{description}} пользователем {{userBy}}", "Room_changed_description": "Описание чата было изменено на: {{description}} пользователем {{userBy}}",
"Room_changed_privacy": "Тип чата был изменен на: {{type}} пользователем {{userBy}}",
"Room_changed_topic": "Тема чата была изменена на: {{topic}} пользователем {{userBy}}",
"Room_Files": "Файлы", "Room_Files": "Файлы",
"Room_Info_Edit": "Изменить информацию о чате", "Room_Info_Edit": "Изменить информацию о чате",
"Room_Info": "Информация о канале", "Room_Info": "Информация о канале",
"Room_Members": "Пользователи", "Room_Members": "Пользователи",
"Room_name_changed": "Название чата было изменено на: {{name}} пользователем {{userBy}}",
"SAVE": "СОХРАНИТЬ", "SAVE": "СОХРАНИТЬ",
"Save_Changes": "Сохранить изменения", "Save_Changes": "Сохранить изменения",
"Save": "Сохранить", "Save": "Сохранить",
@ -546,12 +538,9 @@
"Upload_file_question_mark": "Загрузить файл?", "Upload_file_question_mark": "Загрузить файл?",
"User": "Пользователь", "User": "Пользователь",
"Users": "Пользователи", "Users": "Пользователи",
"User_added_by": "Пользователь {{userAdded}} добавлен по решению {{userBy}}",
"User_Info": "Информация о пользователе", "User_Info": "Информация о пользователе",
"User_has_been_key": "Пользователь был {{key}}", "User_has_been_key": "Пользователь был {{key}}",
"User_is_no_longer_role_by_": "{{user}} больше не {{role}} по решению {{userBy}}", "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_sent_an_attachment": "{{user}} отправил вложение",
"User_unmuted_by": "Пользователь {{userUnmuted}} перестал быть заглушенным по решению {{userBy}}", "User_unmuted_by": "Пользователь {{userUnmuted}} перестал быть заглушенным по решению {{userBy}}",
"User_was_set_role_by_": "{{user}} был назначен {{role}} пользователем {{userBy}}", "User_was_set_role_by_": "{{user}} был назначен {{role}} пользователем {{userBy}}",
@ -696,8 +685,6 @@
"Message_Ignored": "Сообщение игнорируется. Тапните по нему, чтобы отобразить его.", "Message_Ignored": "Сообщение игнорируется. Тапните по нему, чтобы отобразить его.",
"Enter_workspace_URL": "Введите URL вашего рабочего пространства", "Enter_workspace_URL": "Введите URL вашего рабочего пространства",
"Workspace_URL_Example": "Например, your-company.rocket.chat", "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": "Команды", "Teams": "Команды",
"No_team_channels_found": "Каналы не найдены", "No_team_channels_found": "Каналы не найдены",
"Team_not_found": "Команда не найдена", "Team_not_found": "Команда не найдена",
@ -794,8 +781,6 @@
"Message_HideType_user_converted_to_team": "Скрыть сообщения \"Пользователь преобразовал канал в Команду\"", "Message_HideType_user_converted_to_team": "Скрыть сообщения \"Пользователь преобразовал канал в Команду\"",
"Message_HideType_user_deleted_room_from_team": "Скрыть сообщения \"Пользователь удалил чат из Команды\"", "Message_HideType_user_deleted_room_from_team": "Скрыть сообщения \"Пользователь удалил чат из Команды\"",
"Message_HideType_user_removed_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_joined_team": "присоединился к Команде",
"User_left_team": "покинул Команду", "User_left_team": "покинул Команду",
"Place_chat_on_hold": "Поставить чат на удержание", "Place_chat_on_hold": "Поставить чат на удержание",

View File

@ -241,9 +241,6 @@
"Forward_to_user": "Kullanıcıya İlet", "Forward_to_user": "Kullanıcıya İlet",
"Full_table": "Tam tabloyu görmek için tıklayın", "Full_table": "Tam tabloyu görmek için tıklayın",
"Generate_New_Link": "Yeni Bağlantı Oluştur", "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_System_Messages": "Sistem İletilerını Gizle",
"Hide_type_messages": "\"{{type}}\" iletilerini gizle", "Hide_type_messages": "\"{{type}}\" iletilerini gizle",
"How_It_Works": "Nasıl Çalışır", "How_It_Works": "Nasıl Çalışır",
@ -425,14 +422,11 @@
"Roles": "Roller", "Roles": "Roller",
"Room_actions": "Oda işlemleri", "Room_actions": "Oda işlemleri",
"Room_changed_announcement": "Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi", "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_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_Files": "Oda Dosyaları",
"Room_Info_Edit": "Oda Bilgilerini Düzenle", "Room_Info_Edit": "Oda Bilgilerini Düzenle",
"Room_Info": "Oda Bilgisi", "Room_Info": "Oda Bilgisi",
"Room_Members": "Oda Üyeleri", "Room_Members": "Oda Üyeleri",
"Room_name_changed": "Oda adı, {{userBy}} tarafından {{name}} olarak değiştirildi",
"SAVE": "KAYDET", "SAVE": "KAYDET",
"Save_Changes": "Değişiklikleri Kaydet", "Save_Changes": "Değişiklikleri Kaydet",
"Save": "Kaydet", "Save": "Kaydet",
@ -534,12 +528,9 @@
"Upload_file_question_mark": "Dosya gönderilsin mi?", "Upload_file_question_mark": "Dosya gönderilsin mi?",
"User": "Kullanıcı", "User": "Kullanıcı",
"Users": "Kullanıcılar", "Users": "Kullanıcılar",
"User_added_by": "{{userAdded}} adlı kullanıcı {{userBy}} tarafından eklendi",
"User_Info": "Kullanıcı bilgisi", "User_Info": "Kullanıcı bilgisi",
"User_has_been_key": "Kullanıcı {{key}} olmuştur", "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_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_sent_an_attachment": "{{user}} bir ek gönderdi",
"User_unmuted_by": "{{userUnmuted}} kullanıcısının sesi {{userBy}} tarafından açıldı", "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ı", "User_was_set_role_by_": "{{user}}, {{userBy}} tarafından {{role}} ayarlandı",

View File

@ -241,9 +241,6 @@
"Forward_to_user": "转发给用戶", "Forward_to_user": "转发给用戶",
"Full_table": "点击以查看完整表格", "Full_table": "点击以查看完整表格",
"Generate_New_Link": "产生新的链接", "Generate_New_Link": "产生新的链接",
"Has_joined_the_channel": "已加入频道",
"Has_joined_the_conversation": "已经加入此对话",
"Has_left_the_channel": "已离开频道",
"Hide_System_Messages": "隐藏系统信息", "Hide_System_Messages": "隐藏系统信息",
"Hide_type_messages": "隐藏 \"{{type}}\" 信息", "Hide_type_messages": "隐藏 \"{{type}}\" 信息",
"How_It_Works": "运作方式", "How_It_Works": "运作方式",
@ -422,15 +419,11 @@
"Roles": "角色", "Roles": "角色",
"Room_actions": "聊天室操作", "Room_actions": "聊天室操作",
"Room_changed_announcement": "{{userBy}}将聊天室通知改为:{{announcement}}", "Room_changed_announcement": "{{userBy}}将聊天室通知改为:{{announcement}}",
"Room_changed_avatar": "Room avatar changed by {{userBy}}",
"Room_changed_description": "{{userBy}}将聊天室说明改为:{{description}}", "Room_changed_description": "{{userBy}}将聊天室说明改为:{{description}}",
"Room_changed_privacy": "{{userBy}}将聊天室类型改为:{{type}}",
"Room_changed_topic": "{{userBy}}将聊天室主题改为:{{topic}}",
"Room_Files": "聊天室文件", "Room_Files": "聊天室文件",
"Room_Info_Edit": "聊天室信息编辑", "Room_Info_Edit": "聊天室信息编辑",
"Room_Info": "聊天室信息", "Room_Info": "聊天室信息",
"Room_Members": "聊天室成员", "Room_Members": "聊天室成员",
"Room_name_changed": "{{userBy}} 将聊天室名称改为:{{name}}",
"SAVE": "保存", "SAVE": "保存",
"Save_Changes": "保存更改", "Save_Changes": "保存更改",
"Save": "保存", "Save": "保存",
@ -532,12 +525,9 @@
"Upload_file_question_mark": "上传文件?", "Upload_file_question_mark": "上传文件?",
"User": "用戶", "User": "用戶",
"Users": "用戶", "Users": "用戶",
"User_added_by": "由{{userBy}}添加的用户 {{userAdded}}",
"User_Info": "用戶资讯", "User_Info": "用戶资讯",
"User_has_been_key": "用户已被{{key}}", "User_has_been_key": "用户已被{{key}}",
"User_is_no_longer_role_by_": "{{userBy}}将角色 {{role}} 从用户 {{user}} 身上移除", "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_sent_an_attachment": "{{user}} 寄送了一个附件",
"User_unmuted_by": "用户 {{userUnmuted}} 被 {{userBy}} 取消静音", "User_unmuted_by": "用户 {{userUnmuted}} 被 {{userBy}} 取消静音",
"User_was_set_role_by_": "用户 {{user}} 被 {{userBy}} 设置角色 {{role}}", "User_was_set_role_by_": "用户 {{user}} 被 {{userBy}} 设置角色 {{role}}",

View File

@ -242,9 +242,6 @@
"Forward_to_user": "轉發給使用者", "Forward_to_user": "轉發給使用者",
"Full_table": "點擊以查看完整表格", "Full_table": "點擊以查看完整表格",
"Generate_New_Link": "產生新的連結", "Generate_New_Link": "產生新的連結",
"Has_joined_the_channel": "已加入頻道",
"Has_joined_the_conversation": "已經加入此對話",
"Has_left_the_channel": "已離開頻道",
"Hide_System_Messages": "隱藏系統訊息", "Hide_System_Messages": "隱藏系統訊息",
"Hide_type_messages": "隱藏 '{{type}}' 訊息", "Hide_type_messages": "隱藏 '{{type}}' 訊息",
"How_It_Works": "運作方式", "How_It_Works": "運作方式",
@ -424,15 +421,11 @@
"Roles": "角色", "Roles": "角色",
"Room_actions": "聊天室操作", "Room_actions": "聊天室操作",
"Room_changed_announcement": "{{userBy}}將聊天室通知改為:{{announcement}}", "Room_changed_announcement": "{{userBy}}將聊天室通知改為:{{announcement}}",
"Room_changed_avatar": "Room avatar changed by {{userBy}}",
"Room_changed_description": "{{userBy}}將聊天室說明改為:{{description}}", "Room_changed_description": "{{userBy}}將聊天室說明改為:{{description}}",
"Room_changed_privacy": "{{userBy}}將聊天室類型改為:{{type}}",
"Room_changed_topic": "{{userBy}}將聊天室主題改為:{{topic}}",
"Room_Files": "聊天室檔案", "Room_Files": "聊天室檔案",
"Room_Info_Edit": "修改聊天室資訊", "Room_Info_Edit": "修改聊天室資訊",
"Room_Info": "聊天室資訊", "Room_Info": "聊天室資訊",
"Room_Members": "聊天室成員", "Room_Members": "聊天室成員",
"Room_name_changed": "{{userBy}} 將聊天室名稱改為:{{name}}",
"SAVE": "儲存", "SAVE": "儲存",
"Save_Changes": "儲存更改", "Save_Changes": "儲存更改",
"Save": "儲存", "Save": "儲存",
@ -534,12 +527,9 @@
"Upload_file_question_mark": "上傳文件?", "Upload_file_question_mark": "上傳文件?",
"User": "使用者", "User": "使用者",
"Users": "使用者", "Users": "使用者",
"User_added_by": "由{{userBy}}添加的使用者 {{userAdded}}",
"User_Info": "使用者資訊", "User_Info": "使用者資訊",
"User_has_been_key": "使用者已被{{key}}", "User_has_been_key": "使用者已被{{key}}",
"User_is_no_longer_role_by_": "{{userBy}}將角色 {{role}} 從使用者 {{user}} 身上移除", "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_sent_an_attachment": "{{user}} 寄送了一個附件",
"User_unmuted_by": "使用者 {{userUnmuted}} 被 {{userBy}} 取消靜音", "User_unmuted_by": "使用者 {{userUnmuted}} 被 {{userBy}} 取消靜音",
"User_was_set_role_by_": "使用者 {{user}} 被 {{userBy}} 設置角色 {{role}}", "User_was_set_role_by_": "使用者 {{user}} 被 {{userBy}} 設置角色 {{role}}",

View File

@ -70,6 +70,7 @@ export const colors = {
collapsibleQuoteBorder: '#CBCED1', collapsibleQuoteBorder: '#CBCED1',
collapsibleChevron: '#6C727A', collapsibleChevron: '#6C727A',
cancelButton: '#E4E7EA', cancelButton: '#E4E7EA',
textInputSecondaryBackground: '#E4E7EA',
...mentions ...mentions
}, },
dark: { dark: {
@ -122,6 +123,7 @@ export const colors = {
collapsibleQuoteBorder: '#CBCED1', collapsibleQuoteBorder: '#CBCED1',
collapsibleChevron: '#6C727A', collapsibleChevron: '#6C727A',
cancelButton: '#E4E7EA', cancelButton: '#E4E7EA',
textInputSecondaryBackground: '#030b1b', // backgroundColor
...mentions ...mentions
}, },
black: { black: {
@ -174,6 +176,7 @@ export const colors = {
collapsibleQuoteBorder: '#CBCED1', collapsibleQuoteBorder: '#CBCED1',
collapsibleChevron: '#6C727A', collapsibleChevron: '#6C727A',
cancelButton: '#E4E7EA', cancelButton: '#E4E7EA',
textInputSecondaryBackground: '#000000', // backgroundColor
...mentions ...mentions
} }
}; };

View File

@ -1,44 +1,44 @@
const list = ['frequentlyUsed', 'custom', 'people', 'nature', 'food', 'activity', 'travel', 'objects', 'symbols', 'flags']; const list = ['frequentlyUsed', 'custom', 'people', 'nature', 'food', 'activity', 'travel', 'objects', 'symbols', 'flags'];
const tabs = [ const tabs = [
{ {
tabLabel: '🕒', tabLabel: 'clock',
category: list[0] category: list[0]
}, },
{ {
tabLabel: '🚀', tabLabel: 'rocket',
category: list[1] category: list[1]
}, },
{ {
tabLabel: '😃', tabLabel: 'emoji',
category: list[2] category: list[2]
}, },
{ {
tabLabel: '🐶', tabLabel: 'leaf',
category: list[3] category: list[3]
}, },
{ {
tabLabel: '🍔', tabLabel: 'burger',
category: list[4] category: list[4]
}, },
{ {
tabLabel: '', tabLabel: 'basketball',
category: list[5] category: list[5]
}, },
{ {
tabLabel: '🚌', tabLabel: 'airplane',
category: list[6] category: list[6]
}, },
{ {
tabLabel: '💡', tabLabel: 'lamp-bulb',
category: list[7] category: list[7]
}, },
{ {
tabLabel: '💛', tabLabel: 'percentage',
category: list[8] category: list[8]
}, },
{ {
tabLabel: '🏁', tabLabel: 'flag',
category: list[9] category: list[9]
} }
]; ];
export default { list, tabs }; export const categories = { list, tabs };

View File

@ -2813,3 +2813,5 @@ export const emojis = [
'flag_tc', 'flag_tc',
'flag_mf' 'flag_mf'
]; ];
export const DEFAULT_EMOJIS = ['clap', 'thumbsup', 'heart_eyes', 'grinning', 'thinking', 'smiley'];

View File

@ -0,0 +1,2 @@
export * from './emojis';
export * from './categories';

View File

@ -1,5 +1,6 @@
export * from './colors'; export * from './colors';
export * from './constantDisplayMode'; export * from './constantDisplayMode';
export * from './emojis';
export * from './environment'; export * from './environment';
export * from './keys'; export * from './keys';
export * from './links'; export * from './links';

View File

@ -1,2 +1,3 @@
export * from './useAppSelector'; export * from './useAppSelector';
export * from './usePermissions'; export * from './usePermissions';
export * from './useFrequentlyUsedEmoji';

View File

@ -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 };
};

67
app/lib/methods/emojis.ts Normal file
View File

@ -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];
};

View File

@ -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);
}
};

View File

@ -187,6 +187,7 @@ export default {
ROOM_SEND_MESSAGE: 'room_send_message', ROOM_SEND_MESSAGE: 'room_send_message',
ROOM_ENCRYPTED_PRESS: 'room_encrypted_press', ROOM_ENCRYPTED_PRESS: 'room_encrypted_press',
ROOM_OPEN_EMOJI: 'room_open_emoji', ROOM_OPEN_EMOJI: 'room_open_emoji',
ROOM_CLOSE_EMOJI: 'room_close_emoji',
ROOM_AUDIO_RECORD: 'room_audio_record', ROOM_AUDIO_RECORD: 'room_audio_record',
ROOM_AUDIO_RECORD_F: 'room_audio_record_f', ROOM_AUDIO_RECORD_F: 'room_audio_record_f',
ROOM_AUDIO_FINISH: 'room_audio_finish', ROOM_AUDIO_FINISH: 'room_audio_finish',
@ -238,6 +239,13 @@ export default {
ROOM_MENTION_GO_USER_INFO: 'room_mention_go_user_info', ROOM_MENTION_GO_USER_INFO: 'room_mention_go_user_info',
COMMAND_RUN: 'command_run', COMMAND_RUN: 'command_run',
COMMAND_RUN_F: 'command_run_f', 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 // ROOM ACTIONS VIEW
RA_JITSI_VIDEO: 'ra_jitsi_video', RA_JITSI_VIDEO: 'ra_jitsi_video',
@ -280,6 +288,7 @@ export default {
RI_GO_RI_EDIT: 'ri_go_ri_edit', RI_GO_RI_EDIT: 'ri_go_ri_edit',
RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit', RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit',
RI_GO_ROOM_USER: 'ri_go_room_user', RI_GO_ROOM_USER: 'ri_go_room_user',
RI_TOGGLE_BLOCK_USER: 'ri_toggle_block_user',
// ROOM INFO EDIT VIEW // ROOM INFO EDIT VIEW
RI_EDIT_TOGGLE_ROOM_TYPE: 'ri_edit_toggle_room_type', RI_EDIT_TOGGLE_ROOM_TYPE: 'ri_edit_toggle_room_type',

View File

@ -3,6 +3,7 @@ export * from './callJitsi';
export * from './canOpenRoom'; export * from './canOpenRoom';
export * from './clearCache'; export * from './clearCache';
export * from './enterpriseModules'; export * from './enterpriseModules';
export * from './emojis';
export * from './getCustomEmojis'; export * from './getCustomEmojis';
export * from './getPermalinks'; export * from './getPermalinks';
export * from './getPermissions'; export * from './getPermissions';

View File

@ -160,7 +160,7 @@ export const createTeam = ({
}) => { }) => {
const params = { const params = {
name, name,
users, members: users,
type: type ? TEAM_TYPE.PRIVATE : TEAM_TYPE.PUBLIC, type: type ? TEAM_TYPE.PRIVATE : TEAM_TYPE.PUBLIC,
room: { room: {
readOnly, readOnly,

View File

@ -68,6 +68,7 @@ export type ChatsStackParamList = {
rid: string; rid: string;
t: SubscriptionType; t: SubscriptionType;
showCloseModal?: boolean; showCloseModal?: boolean;
fromRid?: string;
}; };
RoomInfoEditView: { RoomInfoEditView: {
rid: string; rid: string;

View File

@ -23,13 +23,14 @@ import { IRoomInfoParam } from '../SearchMessagesView';
import { import {
IApplicationState, IApplicationState,
TMessageModel, TMessageModel,
IEmoji,
ISubscription, ISubscription,
SubscriptionType, SubscriptionType,
IAttachment, IAttachment,
IMessage, IMessage,
TAnyMessageModel, TAnyMessageModel,
IUrl IUrl,
TGetCustomEmoji,
ICustomEmoji
} from '../../definitions'; } from '../../definitions';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
@ -45,7 +46,7 @@ interface IMessagesViewProps {
StackNavigationProp<MasterDetailInsideStackParamList> StackNavigationProp<MasterDetailInsideStackParamList>
>; >;
route: RouteProp<ChatsStackParamList, 'MessagesView'>; route: RouteProp<ChatsStackParamList, 'MessagesView'>;
customEmojis: { [key: string]: IEmoji }; customEmojis: { [key: string]: ICustomEmoji };
theme: TSupportedThemes; theme: TSupportedThemes;
showActionSheet: (params: { options: string[]; hasCancel: boolean }) => void; showActionSheet: (params: { options: string[]; hasCancel: boolean }) => void;
useRealName: boolean; useRealName: boolean;
@ -297,7 +298,7 @@ class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewStat
} }
}; };
getCustomEmoji = (name: string) => { getCustomEmoji: TGetCustomEmoji = name => {
const { customEmojis } = this.props; const { customEmojis } = this.props;
const emoji = customEmojis[name]; const emoji = customEmojis[name];
if (emoji) { if (emoji) {

View File

@ -760,7 +760,8 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
rid, rid,
t, t,
room, room,
member member,
fromRid: room.rid
} }
}) })
} }

View File

@ -36,6 +36,8 @@ import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
import { callJitsi } from '../../lib/methods'; import { callJitsi } from '../../lib/methods';
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers'; import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
interface IGetRoomTitle { interface IGetRoomTitle {
room: ISubscription; room: ISubscription;
@ -108,6 +110,7 @@ interface IRoomInfoViewState {
room: ISubscription; room: ISubscription;
roomUser: IUserParsed | ILivechatVisitorModified; roomUser: IUserParsed | ILivechatVisitorModified;
showEdit: boolean; showEdit: boolean;
roomFromRid?: TSubscriptionModel;
} }
class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewState> { class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewState> {
@ -121,22 +124,29 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
private roomObservable?: Observable<TSubscriptionModel>; private roomObservable?: Observable<TSubscriptionModel>;
private fromRid?: string;
private subscriptionRoomFromRid?: Subscription;
constructor(props: IRoomInfoViewProps) { constructor(props: IRoomInfoViewProps) {
super(props); super(props);
const room = props.route.params?.room; const room = props.route.params?.room;
const roomUser = props.route.params?.member; const roomUser = props.route.params?.member;
this.rid = props.route.params?.rid; this.rid = props.route.params?.rid;
this.t = props.route.params?.t; this.t = props.route.params?.t;
this.fromRid = props.route.params?.fromRid;
this.state = { this.state = {
room: (room || { rid: this.rid, t: this.t }) as any, room: (room || { rid: this.rid, t: this.t }) as any,
roomUser: roomUser || {}, roomUser: roomUser || {},
showEdit: false showEdit: false,
roomFromRid: undefined
}; };
} }
componentDidMount() { componentDidMount() {
if (this.isDirect) { if (this.isDirect) {
this.loadUser(); this.loadUser();
this.loadRoomFromRid();
} else { } else {
this.loadRoom(); this.loadRoom();
} }
@ -154,6 +164,9 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
if (this.subscription && this.subscription.unsubscribe) { if (this.subscription && this.subscription.unsubscribe) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
if (this.subscriptionRoomFromRid && this.subscriptionRoomFromRid.unsubscribe) {
this.subscriptionRoomFromRid.unsubscribe();
}
if (this.unsubscribeFocus) { if (this.unsubscribeFocus) {
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 () => { loadRoom = async () => {
const { room: roomState } = this.state; const { room: roomState } = this.state;
const { route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields } = this.props; 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 = () => { videoCall = () => {
const { room } = this.state; const { room } = this.state;
callJitsi(room); 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) => { renderAvatar = (room: ISubscription, roomUser: IUserParsed) => {
const { theme } = this.props; 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 { theme } = this.props;
const color = danger ? themes[theme].dangerColor : themes[theme].actionTintColor;
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') })
});
}
};
return ( return (
<BorderlessButton onPress={onActionPress} style={styles.roomButton}> <BorderlessButton testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}>
<CustomIcon name={iconName} size={30} color={themes[theme].actionTintColor} /> <CustomIcon name={iconName} size={30} color={color} />
<Text style={[styles.roomButtonText, { color: themes[theme].actionTintColor }]}>{text}</Text> <Text style={[styles.roomButtonText, { color }]}>{text}</Text>
</BorderlessButton> </BorderlessButton>
); );
}; };
renderButtons = () => { renderButtons = () => {
const { roomFromRid, roomUser } = this.state;
const { jitsiEnabled } = this.props; 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 ( return (
<View style={styles.roomButtonsContainer}> <View style={styles.roomButtonsContainer}>
{this.renderButton(this.goRoom, 'message', I18n.t('Message'))} {this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
{jitsiEnabled && this.isDirect ? this.renderButton(this.videoCall, 'camera', I18n.t('Video_call')) : null} {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> </View>
); );
}; };

View File

@ -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 ( export const handleOwner = async (
selectedUser: TUserModel, selectedUser: TUserModel,
isOwner: boolean, isOwner: boolean,

View File

@ -16,6 +16,7 @@ import { TSubscriptionModel, TUserModel } from '../../definitions';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { useAppSelector, usePermissions } from '../../lib/hooks'; import { useAppSelector, usePermissions } from '../../lib/hooks';
import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers'; import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import log from '../../lib/methods/helpers/log'; import log from '../../lib/methods/helpers/log';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
@ -28,7 +29,6 @@ import ActionsSection from './components/ActionsSection';
import { import {
fetchRole, fetchRole,
fetchRoomMembersRoles, fetchRoomMembersRoles,
handleIgnore,
handleLeader, handleLeader,
handleModerator, handleModerator,
handleMute, handleMute,
@ -207,7 +207,7 @@ const RoomMembersView = (): React.ReactElement => {
options.push({ options.push({
icon: 'ignore', icon: 'ignore',
title: I18n.t(isIgnored ? 'Unignore' : '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' testID: 'action-sheet-ignore-user'
}); });
} }

View File

@ -1,85 +1,52 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { connect } from 'react-redux';
import Modal from 'react-native-modal';
import EmojiPicker from '../../containers/EmojiPicker'; 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 styles from './styles';
import { IApplicationState } from '../../definitions'; import { IEmoji } from '../../definitions';
import { EventTypes } from '../../containers/EmojiPicker/interfaces';
const margin = isAndroid ? 40 : 20; import { searchEmojis } from '../../lib/methods';
const maxSize = 400; import { useDebounce } from '../../lib/methods/helpers/debounce';
import { EmojiSearch } from '../../containers/EmojiPicker/EmojiSearch';
import { events, logEvent } from '../../lib/methods/helpers/log';
interface IReactionPickerProps { interface IReactionPickerProps {
message?: any; message?: any;
show: boolean;
isMasterDetail: boolean;
reactionClose: () => void; reactionClose: () => void;
onEmojiSelected: (shortname: string, id: string) => void; onEmojiSelected: (emoji: IEmoji, id: string) => void;
width: number;
height: number;
theme: TSupportedThemes;
} }
class ReactionPicker extends React.Component<IReactionPickerProps> { const ReactionPicker = ({ onEmojiSelected, message, reactionClose }: IReactionPickerProps): React.ReactElement => {
shouldComponentUpdate(nextProps: IReactionPickerProps) { const [searchedEmojis, setSearchedEmojis] = React.useState<IEmoji[]>([]);
const { show, width, height } = this.props; const [searching, setSearching] = React.useState<boolean>(false);
return nextProps.show !== show || width !== nextProps.width || height !== nextProps.height;
}
onEmojiSelected = (emoji: string, shortname?: string) => { const handleTextChange = useDebounce((text: string) => {
// standard emojis: `emoji` is unicode and `shortname` is :joy: setSearching(text !== '');
// custom emojis: only `emoji` is returned with shortname type (:joy:) handleSearchEmojis(text);
// to set reactions, we need shortname type }, 300);
const { onEmojiSelected, message } = this.props;
if (message) { const handleSearchEmojis = async (text: string) => {
onEmojiSelected(shortname || emoji, message.id); logEvent(events.REACTION_PICKER_SEARCH_EMOJIS);
} const emojis = await searchEmojis(text);
setSearchedEmojis(emojis);
}; };
render() { const handleEmojiSelect = (_eventType: EventTypes, emoji?: IEmoji) => {
const { width, height, show, reactionClose, isMasterDetail, theme } = this.props; logEvent(events.REACTION_PICKER_EMOJI_SELECTED);
if (message && emoji) {
let widthStyle = width - margin; onEmojiSelected(emoji, message.id);
let heightStyle = Math.min(width, height) - margin * 2;
if (isMasterDetail) {
widthStyle = maxSize;
heightStyle = maxSize;
} }
reactionClose();
};
return show ? ( return (
<Modal <View style={styles.reactionPickerContainer} testID='reaction-picker'>
isVisible={show} <View style={styles.reactionSearchContainer}>
style={{ alignItems: 'center' }} <EmojiSearch onChangeText={handleTextChange} bottomSheet />
onBackdropPress={reactionClose} </View>
onBackButtonPress={reactionClose} <EmojiPicker onItemClicked={handleEmojiSelect} searching={searching} searchedEmojis={searchedEmojis} />
animationIn='fadeIn' </View>
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;
}
}
const mapStateToProps = (state: IApplicationState) => ({ export default ReactionPicker;
isMasterDetail: state.app.isMasterDetail
});
export default connect(mapStateToProps)(withTheme(ReactionPicker));

View File

@ -74,8 +74,9 @@ import {
TMessageModel, TMessageModel,
TSubscriptionModel, TSubscriptionModel,
TThreadModel, TThreadModel,
ICustomEmojis,
IEmoji, IEmoji,
ICustomEmojis TGetCustomEmoji
} from '../../definitions'; } from '../../definitions';
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants'; import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
import { TListRef } from './List/List'; import { TListRef } from './List/List';
@ -112,7 +113,6 @@ const stateAttrsUpdate = [
'loading', 'loading',
'editing', 'editing',
'replying', 'replying',
'reacting',
'readOnly', 'readOnly',
'member', 'member',
'canForwardGuest', 'canForwardGuest',
@ -164,7 +164,6 @@ interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackPar
isMasterDetail: boolean; isMasterDetail: boolean;
replyBroadcast: Function; replyBroadcast: Function;
width: number; width: number;
height: number;
insets: EdgeInsets; insets: EdgeInsets;
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
viewCannedResponsesPermission?: 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; editing: boolean;
replying: boolean; replying: boolean;
replyWithMention: boolean; replyWithMention: boolean;
reacting: boolean;
readOnly: boolean; readOnly: boolean;
unreadsCount: number | null; unreadsCount: number | null;
roomUserId?: string | null; roomUserId?: string | null;
@ -274,7 +272,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
editing: false, editing: false,
replying: !!selectedMessage, replying: !!selectedMessage,
replyWithMention: false, replyWithMention: false,
reacting: false,
readOnly: false, readOnly: false,
unreadsCount: null, unreadsCount: null,
roomUserId, roomUserId,
@ -828,12 +825,27 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
this.setState({ selectedMessage: undefined, replying: false, replyWithMention: false }); 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) => { onReactionInit = (message: TAnyMessageModel) => {
this.setState({ selectedMessage: message, reacting: true }); this.messagebox?.current?.closeEmojiAndAction(() => {
this.setState({ selectedMessage: message }, this.showReactionPicker);
});
}; };
onReactionClose = () => { onReactionClose = () => {
this.setState({ selectedMessage: undefined, reacting: false }); const { hideActionSheet } = this.props;
this.setState({ selectedMessage: undefined }, hideActionSheet);
}; };
onMessageLongPress = (message: TAnyMessageModel) => { onMessageLongPress = (message: TAnyMessageModel) => {
@ -850,8 +862,14 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
navigation.navigate('AttachmentView', { attachment }); navigation.navigate('AttachmentView', { attachment });
}; };
onReactionPress = async (shortname: string, messageId: string) => { onReactionPress = async (emoji: IEmoji, messageId: string) => {
try { try {
let shortname = '';
if (typeof emoji === 'string') {
shortname = emoji;
} else {
shortname = emoji.name;
}
await Services.setReaction(shortname, messageId); await Services.setReaction(shortname, messageId);
this.onReactionClose(); this.onReactionClose();
Review.pushPositiveEvent(); 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 { customEmojis } = this.props;
const emoji = customEmojis[name]; const emoji = customEmojis[name];
if (emoji) { if (emoji) {
@ -1109,10 +1127,13 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
navToRoomInfo = (navParam: any) => { navToRoomInfo = (navParam: any) => {
const { navigation, user, isMasterDetail } = this.props; const { navigation, user, isMasterDetail } = this.props;
const { room } = this.state;
logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]); logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]);
if (navParam.rid === user.id) { if (navParam.rid === user.id) {
return; return;
} }
navParam.fromRid = room.rid;
if (isMasterDetail) { if (isMasterDetail) {
navParam.showCloseModal = true; navParam.showCloseModal = true;
// @ts-ignore // @ts-ignore
@ -1486,8 +1507,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
render() { render() {
console.count(`${this.constructor.name}.render calls`); console.count(`${this.constructor.name}.render calls`);
const { room, selectedMessage, loading, reacting } = this.state; const { room, loading } = this.state;
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props; const { user, baseUrl, theme, navigation, Hide_System_Messages, width, serverVersion } = this.props;
const { rid, t } = room; const { rid, t } = room;
let sysMes; let sysMes;
let bannerClosed; let bannerClosed;
@ -1518,15 +1539,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
/> />
{this.renderFooter()} {this.renderFooter()}
{this.renderActions()} {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} /> <UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} /> <JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
</SafeAreaView> </SafeAreaView>

View File

@ -14,10 +14,13 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginVertical: 15 marginVertical: 15
}, },
reactionSearchContainer: {
marginHorizontal: 12,
marginBottom: 8
},
reactionPickerContainer: { reactionPickerContainer: {
borderRadius: 4, flex: 1,
flexDirection: 'column', flexDirection: 'column'
overflow: 'hidden'
}, },
bannerContainer: { bannerContainer: {
paddingVertical: 12, paddingVertical: 12,
@ -64,5 +67,14 @@ export default StyleSheet.create({
previewMode: { previewMode: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...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