Compare commits

..

35 Commits

Author SHA1 Message Date
Daniel Espino García
01b5fc0f9e Bump app build number to 461 (#7192) 2023-03-07 20:28:41 +01:00
Daniel Espino García
9b932c401a Performance fixes and fix manual sort (#7190)
* Performance fixes and fix manual sort

* Fix test

* Use combineLatestWith

* Revert unread on top
2023-03-07 19:24:40 +01:00
Daniel Espino García
5ccf45622d Fix race condition when the same websocket gets initialized twice (#7185)
* Fix race condition when the same websocket gets initialized twice

* Bump network library
2023-03-07 19:11:28 +01:00
Weblate
279b601b2a Added translation using Weblate (Arabic) 2023-03-07 10:57:36 +02:00
Tom De Moor
049e763204 Deleted translation using Weblate (Arabic) 2023-03-07 10:57:36 +02:00
Weblate
d6e7889afc Added translation using Weblate (Arabic) 2023-03-07 10:57:36 +02:00
MArtin Johnson
46df588a6a Translated using Weblate (Swedish)
Currently translated at 99.9% (1007 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/sv/
2023-03-07 10:57:36 +02:00
Konstantin
a92574cdf3 Translated using Weblate (Russian)
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/ru/
2023-03-07 10:57:36 +02:00
mini-bomba
fdd0bed294 Translated using Weblate (Polish)
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/pl/
2023-03-07 10:57:36 +02:00
Elisabeth Kulzer
72c5271598 Detox: Fix ios connect issue. (#7187)
Co-authored-by: Mattermost Build <build@mattermost.com>
2023-03-07 09:00:39 +01:00
Elisabeth Kulzer
6454a19a37 Detox: Android - fix invite ppl. (#7162)
* Detox: Android - fix invite ppl.

* Remove config modifications.

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2023-03-03 19:04:19 +01:00
Pantelis Vratsalis
71805ed79d Bump app build number to 460 2023-03-03 17:37:48 +02:00
Elias Nahum
fe916ec740 Refactor category channels to react to setting changes and apply the correct order (#7170)
* Refactor category channels to react to setting changes and apply the correct order

* feedback review
2023-03-03 15:53:29 +02:00
Elias Nahum
3c046ae39c Fix push notification token registration race/missing (#7183) 2023-03-03 12:14:08 +02:00
Elias Nahum
a804a7331f support WS connection over TLS1.3 (#7182)
* support WS connection over TLS1.3

* fix updateDraftMessage on unmount
2023-03-03 11:33:01 +02:00
Elias Nahum
903aaf62b5 Fix display name when open own DM (#7181) 2023-03-02 16:57:49 +02:00
Elias Nahum
ef4fb9c8e0 fix entry for tablets (#7179) 2023-03-02 16:52:54 +02:00
Elias Nahum
af07f511f7 use sourceScreen instead of location in post options (#7176) 2023-03-02 12:46:18 +02:00
Elias Nahum
abd388986f trigger Search when hardware keyboard enter key is pressed (#7174) 2023-03-01 14:38:23 +02:00
Elias Nahum
0938045b7d Fix potential reaction crash (#7172) 2023-03-01 13:24:11 +02:00
Elias Nahum
9347e736e5 ignore leading and trailing spaces when editing profile (#7173) 2023-03-01 13:22:35 +02:00
Elias Nahum
276bcba956 Fix iOS push notification when set as generic message with sender name (#7171) 2023-03-01 12:59:07 +02:00
Elisabeth Kulzer
27d7875dd7 Detox: Android - fix smoke server login test (#7157)
* Detox: Android - fix smoke server login test
---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2023-02-28 17:14:07 +01:00
master7
0309d7a60b Translated using Weblate (Polish)
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/pl/
2023-02-28 10:55:22 +02:00
Tom De Moor
5a497ab15b Translated using Weblate (Dutch)
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/nl/
2023-02-28 10:55:22 +02:00
Matthew Williams
3daa91ce9d Translated using Weblate (English (Australia))
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/en_AU/
2023-02-28 10:55:22 +02:00
kaakaa
67a28dd926 Translated using Weblate (Japanese)
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/ja/
2023-02-28 10:55:22 +02:00
jprusch
ef3e45f523 Translated using Weblate (German)
Currently translated at 100.0% (1008 of 1008 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/de/
2023-02-28 10:55:22 +02:00
master7
b0c61b220c Translated using Weblate (Polish)
Currently translated at 100.0% (997 of 997 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/pl/
2023-02-28 10:55:22 +02:00
jprusch
139be16b05 Translated using Weblate (German)
Currently translated at 100.0% (997 of 997 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/de/
2023-02-28 10:55:22 +02:00
Hosted Weblate
c7e5adbc3e Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/
2023-02-28 10:55:22 +02:00
MArtin Johnson
bc4d89c3e1 Translated using Weblate (Swedish)
Currently translated at 100.0% (995 of 995 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/sv/
2023-02-28 10:55:22 +02:00
master7
e75791c98d Translated using Weblate (Polish)
Currently translated at 100.0% (995 of 995 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/pl/
2023-02-28 10:55:22 +02:00
Cédric Stocké
bf33df84d6 Translated using Weblate (Italian)
Currently translated at 99.8% (994 of 995 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/it/
2023-02-28 10:55:22 +02:00
jprusch
b6f1f8999d Translated using Weblate (German)
Currently translated at 100.0% (995 of 995 strings)

Translation: mattermost-languages-shipped/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/de/
2023-02-28 10:55:22 +02:00
69 changed files with 685 additions and 782 deletions

View File

@@ -110,7 +110,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 459
versionCode 461
versionName "2.1.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -11,6 +11,7 @@ import com.mattermost.helpers.database_extension.queryCurrentUserId
import com.nozbe.watermelondb.Database
import java.text.Collator
import java.util.Locale
import kotlin.math.max
suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: Database, serverUrl: String, channelId: String, isCRTEnabled: Boolean): Triple<ReadableMap?, ReadableMap?, ReadableArray?> {
val channel = fetch(serverUrl, "/api/v4/channels/$channelId")

View File

@@ -1,8 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {markChannelAsViewed} from '@actions/local/channel';
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, handleKickFromChannel, markChannelAsRead, MyChannelsRequest} from '@actions/remote/channel';
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, handleKickFromChannel, MyChannelsRequest} from '@actions/remote/channel';
import {fetchGroupsForMember} from '@actions/remote/groups';
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
import {MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference';
@@ -440,9 +439,9 @@ export async function handleEntryAfterLoadNavigation(
if (!currentTeamIdAfterLoad) {
// First load or no team
if (tabletDevice) {
await setCurrentTeamAndChannelId(operator, initialTeamId, '');
} else {
await setCurrentTeamAndChannelId(operator, initialTeamId, initialChannelId);
} else {
await setCurrentTeamAndChannelId(operator, initialTeamId, '');
}
} else if (currentTeamIdAfterLoad !== currentTeamId) {
// Switched teams while loading
@@ -466,9 +465,6 @@ export async function handleEntryAfterLoadNavigation(
} else {
await setCurrentTeamAndChannelId(operator, initialTeamId, initialChannelId);
}
} else if (tabletDevice && initialChannelId === currentChannelId) {
await markChannelAsRead(serverUrl, initialChannelId);
markChannelAsViewed(serverUrl, initialChannelId);
}
} catch (error) {
logDebug('could not manage the entry after load navigation', error);

View File

@@ -130,6 +130,13 @@ async function doReconnect(serverUrl: string) {
if (models?.length) {
await operator.batchRecords(models, 'doReconnect');
}
const tabletDevice = await isTablet();
if (tabletDevice && initialChannelId === currentChannelId) {
await markChannelAsRead(serverUrl, initialChannelId);
markChannelAsViewed(serverUrl, initialChannelId);
}
logInfo('WEBSOCKET RECONNECT MODELS BATCHING TOOK', `${Date.now() - dt}ms`);
setTeamLoading(serverUrl, false);

View File

@@ -6,7 +6,7 @@ import {Platform} from 'react-native';
import {WebsocketEvents} from '@constants';
import DatabaseManager from '@database/manager';
import {getConfig} from '@queries/servers/system';
import {getConfigValue} from '@queries/servers/system';
import {hasReliableWebsocket} from '@utils/config';
import {toMilliseconds} from '@utils/datetime';
import {logError, logInfo, logWarning} from '@utils/log';
@@ -79,8 +79,12 @@ export default class WebSocketClient {
return;
}
const config = await getConfig(database);
const connectionUrl = (config.WebsocketURL || this.serverUrl) + '/api/v4/websocket';
const [websocketUrl, version, reliableWebsocketConfig] = await Promise.all([
getConfigValue(database, 'WebsocketURL'),
getConfigValue(database, 'Version'),
getConfigValue(database, 'EnableReliableWebSockets'),
]);
const connectionUrl = (websocketUrl || this.serverUrl) + '/api/v4/websocket';
if (this.connectingCallback) {
this.connectingCallback();
@@ -101,7 +105,7 @@ export default class WebSocketClient {
this.url = connectionUrl;
const reliableWebSockets = hasReliableWebsocket(config);
const reliableWebSockets = hasReliableWebsocket(version, reliableWebsocketConfig);
if (reliableWebSockets) {
// Add connection id, and last_sequence_number to the query param.
// We cannot also send it as part of the auth_challenge, because the session cookie is already sent with the request.
@@ -129,6 +133,11 @@ export default class WebSocketClient {
headers.Authorization = `Bearer ${this.token}`;
}
const {client} = await getOrCreateWebSocketClient(this.url, {headers, timeoutInterval: WEBSOCKET_TIMEOUT});
// Check again if the client is the same, to avoid race conditions
if (this.conn === client) {
return;
}
this.conn = client;
} catch (error) {
return;

View File

@@ -283,9 +283,9 @@ export default function PostInput({
});
return () => {
listener.remove();
updateDraftMessage(serverUrl, channelId, rootId, value); // safe draft on unmount
updateDraftMessage(serverUrl, channelId, rootId, lastNativeValue.current); // safe draft on unmount
};
}, [updateValue, value, channelId, rootId]);
}, [updateValue, channelId, rootId]);
useEffect(() => {
if (value !== lastNativeValue.current) {

View File

@@ -108,7 +108,7 @@ const CombinedUserActivity = ({
return;
}
const passProps = {post};
const passProps = {post, sourceScreen: location};
Keyboard.dismiss();
const title = isTablet ? intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}) : '';
@@ -117,7 +117,7 @@ const CombinedUserActivity = ({
} else {
showModalOverCurrentContext(Screens.POST_OPTIONS, passProps, bottomSheetModalOptions(theme));
}
}, [post, canDelete, isTablet, intl]);
}, [post, canDelete, isTablet, intl, location]);
const renderMessage = (postType: string, userIds: string[], actorId: string) => {
let actor = '';

View File

@@ -86,11 +86,11 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
if (reaction) {
const emojiAlias = getEmojiFirstAlias(reaction.emojiName);
if (acc.has(emojiAlias)) {
const rs = acc.get(emojiAlias);
const rs = acc.get(emojiAlias)!;
// eslint-disable-next-line max-nested-callbacks
const present = rs!.findIndex((r) => r.userId === reaction.userId) > -1;
const present = rs.findIndex((r) => r.userId === reaction.userId) > -1;
if (!present) {
rs!.push(reaction);
rs.push(reaction);
}
} else {
acc.set(emojiAlias, [reaction]);
@@ -105,7 +105,7 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
}, new Map<string, ReactionModel[]>());
return {reactionsByName, highlightedReactions};
}, [sortedReactions]);
}, [sortedReactions, reactions]);
const handleAddReactionToPost = (emoji: string) => {
addReaction(serverUrl, postId, emoji);
@@ -178,7 +178,7 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
return (
<Reaction
key={r}
count={reaction!.length}
count={reaction?.length || 1}
emojiName={r}
highlight={highlightedReactions.includes(r)}
onPress={handleReactionPress}

View File

@@ -150,6 +150,7 @@ export default function SelectedUsers({
const [isVisible, setIsVisible] = useState(false);
const numberSelectedIds = Object.keys(selectedIds).length;
const bottomSpace = (dimensions.height - containerHeight - modalPosition);
const bottomPaddingBottom = isTablet ? CHIP_HEIGHT_WITH_MARGIN : 0;
const users = useMemo(() => {
const u = [];
@@ -172,8 +173,8 @@ export default function SelectedUsers({
}, [selectedIds, teammateNameDisplay, onRemove]);
const totalPanelHeight = useDerivedValue(() => (
isVisible ? panelHeight.value + BUTTON_HEIGHT : 0
), [isVisible, isTablet]);
isVisible ? panelHeight.value + BUTTON_HEIGHT + bottomPaddingBottom : 0
), [isVisible, isTablet, bottomPaddingBottom]);
const marginBottom = useMemo(() => {
let margin = keyboard.height && Platform.OS === 'ios' ? keyboard.height - insets.bottom : 0;
@@ -208,7 +209,7 @@ export default function SelectedUsers({
}, [onPress]);
const onLayout = useCallback((e: LayoutChangeEvent) => {
panelHeight.value = Math.min(PANEL_MAX_HEIGHT, e.nativeEvent.layout.height);
panelHeight.value = Math.min(PANEL_MAX_HEIGHT + bottomPaddingBottom, e.nativeEvent.layout.height);
}, []);
const androidMaxHeight = Platform.select({
@@ -235,8 +236,8 @@ export default function SelectedUsers({
const animatedViewStyle = useAnimatedStyle(() => ({
height: withTiming(totalPanelHeight.value + insets.bottom, {duration: 250}),
borderWidth: isVisible ? 1 : 0,
maxHeight: isVisible ? PANEL_MAX_HEIGHT + BUTTON_HEIGHT + insets.bottom : 0,
}), [isVisible, insets]);
maxHeight: isVisible ? PANEL_MAX_HEIGHT + BUTTON_HEIGHT + bottomPaddingBottom + insets.bottom : 0,
}), [isVisible, insets, bottomPaddingBottom]);
const animatedButtonStyle = useAnimatedStyle(() => ({
opacity: withTiming(isVisible ? 1 : 0, {duration: isVisible ? 500 : 100}),

View File

@@ -14,7 +14,7 @@ import TeamList from './team_list';
type Props = {
iconPad?: boolean;
canJoinOtherTeams: boolean;
teamsCount: number;
hasMoreThanOneTeam: boolean;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@@ -36,8 +36,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});
export default function TeamSidebar({iconPad, canJoinOtherTeams, teamsCount}: Props) {
const initialWidth = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
export default function TeamSidebar({iconPad, canJoinOtherTeams, hasMoreThanOneTeam}: Props) {
const initialWidth = hasMoreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0;
const width = useSharedValue(initialWidth);
const marginTop = useSharedValue(iconPad ? 44 : 0);
const theme = useTheme();
@@ -58,8 +58,8 @@ export default function TeamSidebar({iconPad, canJoinOtherTeams, teamsCount}: Pr
}, [iconPad]);
useEffect(() => {
width.value = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
}, [teamsCount]);
width.value = hasMoreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0;
}, [hasMoreThanOneTeam]);
return (
<Animated.View style={[styles.container, transform]}>

View File

@@ -3,6 +3,8 @@
export const CATEGORIES_TO_KEEP: Record<string, string> = {
ADVANCED_SETTINGS: 'advanced_settings',
CHANNEL_APPROXIMATE_VIEW_TIME: 'channel_approximate_view_time',
CHANNEL_OPEN_TIME: 'channel_open_time',
DIRECT_CHANNEL_SHOW: 'direct_channel_show',
GROUP_CHANNEL_SHOW: 'group_channel_show',
DISPLAY_SETTINGS: 'display_settings',

View File

@@ -246,10 +246,11 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
const totalMsg = isCRT ? channel.total_msg_count_root! : channel.total_msg_count;
const myMsgCount = isCRT ? my.msg_count_root! : my.msg_count;
const msgCount = Math.max(0, totalMsg - myMsgCount);
const lastPostAt = isCRT ? (channel.last_root_post_at || channel.last_post_at) : channel.last_post_at;
my.msg_count = msgCount;
my.mention_count = isCRT ? my.mention_count_root! : my.mention_count;
my.is_unread = msgCount > 0;
my.last_post_at = (isCRT ? (channel.last_root_post_at || channel.last_post_at) : channel.last_post_at) || 0;
my.last_post_at = lastPostAt;
}
}
@@ -271,7 +272,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
}
const chan = channelMap[my.channel_id];
const lastPostAt = (isCRT ? chan.last_root_post_at : chan.last_post_at) || 0;
const lastPostAt = isCRT ? (chan.last_root_post_at || chan.last_post_at) : chan.last_post_at;
if ((chan && e.lastPostAt < lastPostAt) ||
e.isUnread !== my.is_unread || e.lastViewedAt < my.last_viewed_at ||
e.roles !== my.roles

View File

@@ -37,22 +37,22 @@ class PushNotifications {
configured = false;
init(register: boolean) {
if (register) {
this.registerIfNeeded();
}
Notifications.events().registerNotificationOpened(this.onNotificationOpened);
Notifications.events().registerRemoteNotificationsRegistered(this.onRemoteNotificationsRegistered);
Notifications.events().registerNotificationReceivedBackground(this.onNotificationReceivedBackground);
Notifications.events().registerNotificationReceivedForeground(this.onNotificationReceivedForeground);
if (register) {
this.registerIfNeeded();
}
}
async registerIfNeeded() {
const isRegistered = await Notifications.isRegisteredForRemoteNotifications();
if (!isRegistered) {
await requestNotifications(['alert', 'sound', 'badge']);
Notifications.registerRemoteNotifications();
}
Notifications.registerRemoteNotifications();
}
createReplyCategory = () => {

View File

@@ -11,14 +11,11 @@ import {makeCategoryChannelId} from '@utils/categories';
import {pluckUnique} from '@utils/helpers';
import {logDebug} from '@utils/log';
import {observeChannelsByLastPostAt} from './channel';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
const {SERVER: {CATEGORY, CATEGORY_CHANNEL, CHANNEL}} = MM_TABLES;
const {SERVER: {CATEGORY, CATEGORY_CHANNEL}} = MM_TABLES;
export const getCategoryById = async (database: Database, categoryId: string) => {
try {
@@ -144,24 +141,3 @@ export const observeIsChannelFavorited = (database: Database, teamId: string, ch
distinctUntilChanged(),
);
};
export const observeChannelsByCategoryChannelSortOrder = (database: Database, category: CategoryModel, excludeIds?: string[]) => {
return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe(
switchMap((categoryChannels) => {
const ids = categoryChannels.map((cc) => cc.channelId);
const idsStr = `'${ids.join("','")}'`;
const exclude = excludeIds?.length ? `AND c.id NOT IN ('${excludeIds.join("','")}')` : '';
return database.get<ChannelModel>(CHANNEL).query(
Q.unsafeSqlQuery(`SELECT DISTINCT c.* FROM ${CHANNEL} c INNER JOIN
${CATEGORY_CHANNEL} cc ON cc.channel_id=c.id AND c.id IN (${idsStr}) ${exclude}
ORDER BY cc.sort_order`),
).observe();
}),
);
};
export const observeChannelsByLastPostAtInCategory = (database: Database, category: CategoryModel, excludeIds?: string[]) => {
return category.myChannels.observeWithColumns(['last_post_at']).pipe(
switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels, excludeIds)),
);
};

View File

@@ -11,6 +11,7 @@ import {General, Permissions} from '@constants';
import {MM_TABLES} from '@constants/database';
import {sanitizeLikeString} from '@helpers/database';
import {hasPermission} from '@utils/role';
import {getUserIdFromChannelName} from '@utils/user';
import {prepareDeletePost} from './post';
import {queryRoles} from './role';
@@ -437,10 +438,6 @@ export const observeNotifyPropsByChannels = (database: Database, channels: Chann
);
};
export const queryChannelsByNames = (database: Database, names: string[]) => {
return database.get<ChannelModel>(CHANNEL).query(Q.where('name', Q.oneOf(names)));
};
export const queryMyChannelUnreads = (database: Database, currentTeamId: string) => {
return database.get<MyChannelModel>(MY_CHANNEL).query(
Q.on(
@@ -453,40 +450,42 @@ export const queryMyChannelUnreads = (database: Database, currentTeamId: string)
Q.where('delete_at', Q.eq(0)),
),
),
Q.where('is_unread', Q.eq(true)),
Q.or(
Q.where('is_unread', Q.eq(true)),
Q.where('mentions_count', Q.gte(0)),
),
Q.sortBy('last_post_at', Q.desc),
);
};
export const queryEmptyDirectAndGroupChannels = (database: Database) => {
return database.get<MyChannelModel>(MY_CHANNEL).query(
Q.on(
CHANNEL,
Q.where('team_id', Q.eq('')),
),
Q.where('last_post_at', Q.eq(0)),
);
};
export const observeArchivedDirectChannels = (database: Database, currentUserId: string) => {
const deactivatedIds = database.get<UserModel>(USER).query(
const deactivated = database.get<UserModel>(USER).query(
Q.where('delete_at', Q.gt(0)),
).observe().pipe(
switchMap((users) => of$(users.map((u) => u.id))),
);
).observe();
return deactivatedIds.pipe(
switchMap((dIds) => {
return deactivated.pipe(
switchMap((users) => {
const usersMap = new Map(users.map((u) => [u.id, u]));
return database.get<ChannelModel>(CHANNEL).query(
Q.on(
CHANNEL_MEMBERSHIP,
Q.and(
Q.where('user_id', Q.notEq(currentUserId)),
Q.where('user_id', Q.oneOf(dIds)),
Q.where('user_id', Q.oneOf(Array.from(usersMap.keys()))),
),
),
Q.where('type', 'D'),
).observe();
).observe().pipe(
switchMap((channels) => {
// eslint-disable-next-line max-nested-callbacks
return of$(new Map(channels.map((c) => {
const teammateId = getUserIdFromChannelName(currentUserId, c.name);
const user = usersMap.get(teammateId);
return [c.id, user];
})));
}),
);
}),
);
};
@@ -639,13 +638,13 @@ export const observeIsMutedSetting = (database: Database, channelId: string) =>
return observeChannelSettings(database, channelId).pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === General.MENTION)));
};
export const observeChannelsByLastPostAt = (database: Database, myChannels: MyChannelModel[], excludeIds?: string[]) => {
export const observeChannelsByLastPostAt = (database: Database, myChannels: MyChannelModel[]) => {
const ids = myChannels.map((c) => c.id);
const idsStr = `'${ids.join("','")}'`;
const exclude = excludeIds?.length ? `AND c.id NOT IN ('${excludeIds.join("','")}')` : '';
return database.get<ChannelModel>(CHANNEL).query(
Q.unsafeSqlQuery(`SELECT DISTINCT c.* FROM ${CHANNEL} c INNER JOIN
${MY_CHANNEL} mc ON mc.id=c.id AND c.id IN (${idsStr}) ${exclude}
${MY_CHANNEL} mc ON mc.id=c.id AND c.id IN (${idsStr})
ORDER BY CASE mc.last_post_at WHEN 0 THEN c.create_at ELSE mc.last_post_at END DESC`),
).observe();
};

View File

@@ -197,8 +197,8 @@ export default function CreateDirectMessage({
setSelectedIds((current) => removeProfileFromList(current, id));
}, []);
const createDirectChannel = useCallback(async (id: string): Promise<boolean> => {
const user = selectedIds[id];
const createDirectChannel = useCallback(async (id: string, selectedUser?: UserProfile): Promise<boolean> => {
const user = selectedUser || selectedIds[id];
const displayName = displayUsername(user, intl.locale, teammateNameDisplay);
const result = await makeDirectChannel(serverUrl, id, displayName);
@@ -219,7 +219,7 @@ export default function CreateDirectMessage({
return !result.error;
}, [serverUrl]);
const startConversation = useCallback(async (selectedId?: {[id: string]: boolean}) => {
const startConversation = useCallback(async (selectedId?: {[id: string]: boolean}, selectedUser?: UserProfile) => {
if (startingConversation) {
return;
}
@@ -233,7 +233,7 @@ export default function CreateDirectMessage({
} else if (idsToUse.length > 1) {
success = await createGroupChannel(idsToUse);
} else {
success = await createDirectChannel(idsToUse[0]);
success = await createDirectChannel(idsToUse[0], selectedUser);
}
if (success) {
@@ -249,7 +249,7 @@ export default function CreateDirectMessage({
[currentUserId]: true,
};
startConversation(selectedId);
startConversation(selectedId, user);
} else {
clearSearch();
setSelectedIds((current) => {

View File

@@ -102,6 +102,7 @@ const EditProfile = ({
popTopScreen(componentId);
}
}, []);
const enableSaveButton = useCallback((value: boolean) => {
if (!isTablet) {
const buttons = {
@@ -114,18 +115,19 @@ const EditProfile = ({
}
setCanSave(value);
}, [componentId, rightButton]);
const submitUser = useCallback(preventDoubleTap(async () => {
enableSaveButton(false);
setError(undefined);
setUpdating(true);
try {
const newUserInfo: Partial<UserProfile> = {
email: userInfo.email,
first_name: userInfo.firstName,
last_name: userInfo.lastName,
nickname: userInfo.nickname,
position: userInfo.position,
username: userInfo.username,
email: userInfo.email.trim(),
first_name: userInfo.firstName.trim(),
last_name: userInfo.lastName.trim(),
nickname: userInfo.nickname.trim(),
position: userInfo.position.trim(),
username: userInfo.username.trim(),
};
const localPath = changedProfilePicture.current?.localPath;
const profileImageRemoved = changedProfilePicture.current?.isRemoved;

View File

@@ -12,29 +12,9 @@ import TestHelper from '@test/test_helper';
import CategoryBody from '.';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
const {SERVER: {CATEGORY}} = MM_TABLES;
jest.mock('@queries/servers/categories', () => {
const Queries = jest.requireActual('@queries/servers/categories');
const switchMap = jest.requireActual('rxjs/operators').switchMap;
const mQ = jest.requireActual('@nozbe/watermelondb').Q;
return {
...Queries,
observeChannelsByCategoryChannelSortOrder: (database: Database, category: CategoryModel, excludeIds?: string[]) => {
return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe(
switchMap((categoryChannels: CategoryChannelModel[]) => {
const ids = categoryChannels.filter((cc) => excludeIds?.includes(cc.channelId)).map((cc) => cc.channelId);
return database.get<ChannelModel>('Channel').query(mQ.where('id', mQ.oneOf(ids))).observe();
}),
);
},
};
});
describe('components/channel_list/categories/body', () => {
let database: Database;
let category: CategoryModel;

View File

@@ -7,7 +7,6 @@ import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 're
import {fetchDirectChannelsInfo} from '@actions/remote/channel';
import ChannelItem from '@components/channel_item';
import {DMS_CATEGORY} from '@constants/categories';
import {useServerUrl} from '@context/server';
import {isDMorGM} from '@utils/channel';
@@ -17,7 +16,6 @@ import type ChannelModel from '@typings/database/models/servers/channel';
type Props = {
sortedChannels: ChannelModel[];
category: CategoryModel;
limit: number;
onChannelSwitch: (channelId: string) => void;
unreadIds: Set<string>;
unreadsOnTop: boolean;
@@ -25,16 +23,13 @@ type Props = {
const extractKey = (item: ChannelModel) => item.id;
const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, limit, onChannelSwitch}: Props) => {
const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChannelSwitch}: Props) => {
const serverUrl = useServerUrl();
const ids = useMemo(() => {
const filteredChannels = unreadsOnTop ? sortedChannels.filter((c) => !unreadIds.has(c.id)) : sortedChannels;
if (category.type === DMS_CATEGORY && limit > 0) {
return filteredChannels.slice(0, limit);
}
return filteredChannels;
}, [category.type, limit, sortedChannels, unreadIds, unreadsOnTop]);
}, [category.type, sortedChannels, unreadIds, unreadsOnTop]);
const unreadChannels = useMemo(() => {
return unreadsOnTop ? [] : ids.filter((c) => unreadIds.has(c.id));

View File

@@ -1,20 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Database, Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap, combineLatestWith} from 'rxjs/operators';
import {of as of$, Observable} from 'rxjs';
import {switchMap, combineLatestWith, distinctUntilChanged} from 'rxjs/operators';
import {General, Preferences} from '@constants';
import {Preferences} from '@constants';
import {DMS_CATEGORY} from '@constants/categories';
import {getSidebarPreferenceAsBool} from '@helpers/api/preference';
import {observeChannelsByCategoryChannelSortOrder, observeChannelsByLastPostAtInCategory} from '@queries/servers/categories';
import {observeArchivedDirectChannels, observeNotifyPropsByChannels, queryChannelsByNames, queryEmptyDirectAndGroupChannels} from '@queries/servers/channel';
import {observeArchivedDirectChannels, observeNotifyPropsByChannels} from '@queries/servers/channel';
import {queryPreferencesByCategoryAndName, querySidebarPreferences} from '@queries/servers/preference';
import {observeCurrentChannelId, observeCurrentUserId, observeLastUnreadChannelId} from '@queries/servers/system';
import {getDirectChannelName} from '@utils/channel';
import {ChannelWithMyChannel, filterArchivedChannels, filterAutoclosedDMs, filterManuallyClosedDms, getUnreadIds, sortChannels} from '@utils/categories';
import CategoryBody from './category_body';
@@ -24,10 +22,6 @@ import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type PreferenceModel from '@typings/database/models/servers/preference';
type ChannelData = Pick<ChannelModel, 'id' | 'displayName'> & {
isMuted: boolean;
};
type EnhanceProps = {
category: CategoryModel;
locale: string;
@@ -35,87 +29,45 @@ type EnhanceProps = {
isTablet: boolean;
} & WithDatabaseArgs
const sortAlpha = (locale: string, a: ChannelData, b: ChannelData) => {
if (a.isMuted && !b.isMuted) {
return 1;
} else if (!a.isMuted && b.isMuted) {
return -1;
}
return a.displayName.localeCompare(b.displayName, locale, {numeric: true});
};
const filterArchived = (channels: Array<ChannelModel | null>, currentChannelId: string) => {
return channels.filter((c): c is ChannelModel => c != null && ((c.deleteAt > 0 && c.id === currentChannelId) || !c.deleteAt));
};
const buildAlphaData = (channels: ChannelModel[], notifyProps: Record<string, Partial<ChannelNotifyProps>>, locale: string) => {
const chanelsById = channels.reduce((result: Record<string, ChannelModel>, c) => {
result[c.id] = c;
return result;
}, {});
const combined = channels.map((c) => {
const s = notifyProps[c.id];
return {
id: c.id,
displayName: c.displayName,
isMuted: s?.mark_unread === General.MENTION,
};
});
combined.sort(sortAlpha.bind(null, locale));
return of$(combined.map((cdata) => chanelsById[cdata.id]));
};
const observeSortedChannels = (database: Database, category: CategoryModel, excludeIds: string[], locale: string) => {
switch (category.sorting) {
case 'alpha': {
const channels = category.channels.extend(Q.where('id', Q.notIn(excludeIds))).observeWithColumns(['display_name']);
const notifyProps = channels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs)));
return combineLatest([channels, notifyProps]).pipe(
switchMap(([cs, np]) => buildAlphaData(cs, np, locale)),
);
}
case 'manual': {
return observeChannelsByCategoryChannelSortOrder(database, category, excludeIds);
}
default:
return observeChannelsByLastPostAtInCategory(database, category, excludeIds);
}
};
const mapPrefName = (prefs: PreferenceModel[]) => of$(prefs.map((p) => p.name));
const mapChannelIds = (channels: ChannelModel[] | MyChannelModel[]) => of$(channels.map((c) => c.id));
const withUserId = withObservables([], ({database}: WithDatabaseArgs) => ({currentUserId: observeCurrentUserId(database)}));
const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, locale, isTablet, database, currentUserId}: EnhanceProps) => {
const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId);
const observeCategoryChannels = (category: CategoryModel, myChannels: Observable<MyChannelModel[]>) => {
const channels = category.channels.observeWithColumns(['create_at', 'display_name']);
const manualSort = category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']);
return myChannels.pipe(
combineLatestWith(channels, manualSort),
switchMap(([my, cs, sorted]) => {
const channelMap = new Map<string, ChannelModel>(cs.map((c) => [c.id, c]));
const categoryChannelMap = new Map<string, number>(sorted.map((s) => [s.channelId, s.sortOrder]));
return of$(my.reduce<ChannelWithMyChannel[]>((result, myChannel) => {
const channel = channelMap.get(myChannel.id);
if (channel) {
const channelWithMyChannel: ChannelWithMyChannel = {
channel,
myChannel,
sortOrder: categoryChannelMap.get(myChannel.id) || 0,
};
result.push(channelWithMyChannel);
}
const currentChannelId = observeCurrentChannelId(database);
return result;
}, []));
}),
);
};
const hiddenDmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']).pipe(
switchMap((prefs: PreferenceModel[]) => {
const names = prefs.map(dmMap);
const channels = queryChannelsByNames(database, names).observe();
const enhanced = withObservables([], ({category, currentUserId, database, isTablet, locale}: EnhanceProps) => {
const categoryMyChannels = category.myChannels.observeWithColumns(['last_post_at', 'is_unread']);
const channelsWithMyChannel = observeCategoryChannels(category, categoryMyChannels);
const currentChannelId = isTablet ? observeCurrentChannelId(database) : of$('');
const lastUnreadId = isTablet ? observeLastUnreadChannelId(database) : of$(undefined);
return channels.pipe(
switchMap(mapChannelIds),
);
}),
const unreadsOnTop = querySidebarPreferences(database, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
observeWithColumns(['value']).
pipe(
switchMap((prefs: PreferenceModel[]) => of$(getSidebarPreferenceAsBool(prefs, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS))),
);
const emptyDmIds = queryEmptyDirectAndGroupChannels(database).observeWithColumns(['last_post_at']).pipe(
switchMap(mapChannelIds),
);
const archivedDmIds = observeArchivedDirectChannels(database, currentUserId).pipe(
switchMap(mapChannelIds),
);
let limit = of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);
if (category.type === DMS_CATEGORY) {
limit = querySidebarPreferences(database, Preferences.CHANNEL_SIDEBAR_LIMIT_DMS).
@@ -126,54 +78,61 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category,
);
}
const unreadsOnTop = querySidebarPreferences(database, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
observeWithColumns(['value']).
pipe(
switchMap((prefs: PreferenceModel[]) => of$(getSidebarPreferenceAsBool(prefs, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS))),
);
const lastUnreadId = isTablet ? observeLastUnreadChannelId(database) : of$(undefined);
const hiddenChannelIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.GROUP_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']).pipe(
switchMap(mapPrefName),
combineLatestWith(hiddenDmIds, emptyDmIds, archivedDmIds, lastUnreadId),
switchMap(([hIds, hDmIds, eDmIds, aDmIds, excludeId]) => {
const hidden = new Set(hIds.concat(hDmIds, eDmIds, aDmIds));
if (excludeId) {
hidden.delete(excludeId);
}
return of$(hidden);
}),
);
const sortedChannels = hiddenChannelIds.pipe(
switchMap((excludeIds) => observeSortedChannels(database, category, Array.from(excludeIds), locale)),
combineLatestWith(currentChannelId),
map(([channels, ccId]) => filterArchived(channels, ccId)),
const notifyPropsPerChannel = categoryMyChannels.pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((mc) => observeNotifyPropsByChannels(database, mc)),
);
const unreadChannels = category.myChannels.observeWithColumns(['mentions_count', 'is_unread']);
const notifyProps = unreadChannels.pipe(switchMap((myChannels) => observeNotifyPropsByChannels(database, myChannels)));
const unreadIds = unreadChannels.pipe(
combineLatestWith(notifyProps, lastUnreadId),
map(([my, settings, lastUnread]) => {
return my.reduce<Set<string>>((set, m) => {
const isMuted = settings[m.id]?.mark_unread === 'mention';
if ((isMuted && m.mentionsCount) || (!isMuted && m.isUnread) || m.id === lastUnread) {
set.add(m.id);
}
return set;
}, new Set());
const hiddenDmPrefs = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']);
const hiddenGmPrefs = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.GROUP_CHANNEL_SHOW, undefined, 'false').
observeWithColumns(['value']);
const manuallyClosedPrefs = hiddenDmPrefs.pipe(
combineLatestWith(hiddenGmPrefs),
switchMap(([dms, gms]) => of$(dms.concat(gms))),
);
const approxViewTimePrefs = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.CHANNEL_APPROXIMATE_VIEW_TIME, undefined).
observeWithColumns(['value']);
const openTimePrefs = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.CHANNEL_OPEN_TIME, undefined).
observeWithColumns(['value']);
const autoclosePrefs = approxViewTimePrefs.pipe(
combineLatestWith(openTimePrefs),
switchMap(([viewTimes, openTimes]) => of$(viewTimes.concat(openTimes))),
);
const categorySorting = category.observe().pipe(
switchMap((c) => of$(c.sorting)),
distinctUntilChanged(),
);
const deactivated = (category.type === DMS_CATEGORY) ? observeArchivedDirectChannels(database, currentUserId) : of$(undefined);
const sortedChannels = channelsWithMyChannel.pipe(
combineLatestWith(categorySorting, currentChannelId, lastUnreadId, notifyPropsPerChannel, manuallyClosedPrefs, autoclosePrefs, deactivated, limit),
switchMap(([cwms, sorting, channelId, unreadId, notifyProps, manuallyClosedDms, autoclose, deactivatedDMS, maxDms]) => {
let channelsW = cwms;
channelsW = filterArchivedChannels(channelsW, channelId);
channelsW = filterManuallyClosedDms(channelsW, notifyProps, manuallyClosedDms, currentUserId, unreadId);
channelsW = filterAutoclosedDMs(category.type, maxDms, channelId, channelsW, autoclose, notifyProps, deactivatedDMS, unreadId);
return of$(sortChannels(sorting, channelsW, notifyProps, locale));
}),
);
const unreadIds = channelsWithMyChannel.pipe(
combineLatestWith(notifyPropsPerChannel, lastUnreadId),
switchMap(([cwms, notifyProps, unreadId]) => {
return of$(getUnreadIds(cwms, notifyProps, unreadId));
}),
);
return {
limit,
sortedChannels,
unreadsOnTop,
unreadIds,
category,
sortedChannels,
unreadIds,
unreadsOnTop,
};
});
export default withDatabase(withUserId(enhance(CategoryBody)));
export default withDatabase(withUserId(enhanced(CategoryBody)));

View File

@@ -54,7 +54,7 @@ const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], (
const channels = myUnreadChannels.pipe(switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels)));
const channelsMap = channels.pipe(switchMap((cs) => of$(makeChannelsMap(cs))));
return queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at', 'is_unread']).pipe(
return myUnreadChannels.pipe(
combineLatestWith(channelsMap, notifyProps),
map(filterAndSortMyChannels),
combineLatestWith(lastUnread),

View File

@@ -4,7 +4,7 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
import {Permissions} from '@constants';
import {observePermissionForTeam} from '@queries/servers/role';
@@ -25,6 +25,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const canJoinChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)),
distinctUntilChanged(),
);
const canCreatePublicChannels = combineLatest([currentUser, team]).pipe(
@@ -37,6 +38,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe(
switchMap(([open, priv]) => of$(open || priv)),
distinctUntilChanged(),
);
const canAddUserToTeam = combineLatest([currentUser, team]).pipe(
@@ -48,9 +50,11 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
canJoinChannels,
canInvitePeople: combineLatest([enableOpenServer, canAddUserToTeam]).pipe(
switchMap(([openServer, addUser]) => of$(openServer && addUser)),
distinctUntilChanged(),
),
displayName: team.pipe(
switchMap((t) => of$(t?.displayName)),
distinctUntilChanged(),
),
pushProxyStatus: observePushVerificationStatus(database),
};

View File

@@ -33,8 +33,8 @@ describe('components/categories_list', () => {
it('should render', () => {
const wrapper = renderWithEverything(
<CategoriesList
teamsCount={1}
channelsCount={1}
moreThanOneTeam={false}
hasChannels={true}
/>,
{database},
);
@@ -46,8 +46,8 @@ describe('components/categories_list', () => {
const wrapper = renderWithEverything(
<CategoriesList
isCRTEnabled={true}
teamsCount={1}
channelsCount={1}
moreThanOneTeam={false}
hasChannels={true}
/>,
{database},
);
@@ -67,8 +67,8 @@ describe('components/categories_list', () => {
jest.useFakeTimers();
const wrapper = renderWithEverything(
<CategoriesList
teamsCount={0}
channelsCount={1}
moreThanOneTeam={false}
hasChannels={true}
/>,
{database},
);
@@ -89,8 +89,8 @@ describe('components/categories_list', () => {
jest.useFakeTimers();
const wrapper = renderWithEverything(
<CategoriesList
teamsCount={1}
channelsCount={0}
moreThanOneTeam={true}
hasChannels={false}
/>,
{database},
);

View File

@@ -27,28 +27,28 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
}));
type ChannelListProps = {
channelsCount: number;
hasChannels: boolean;
iconPad?: boolean;
isCRTEnabled?: boolean;
teamsCount: number;
moreThanOneTeam: boolean;
};
const getTabletWidth = (teamsCount: number) => {
return TABLET_SIDEBAR_WIDTH - (teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0);
const getTabletWidth = (moreThanOneTeam: boolean) => {
return TABLET_SIDEBAR_WIDTH - (moreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0);
};
const CategoriesList = ({channelsCount, iconPad, isCRTEnabled, teamsCount}: ChannelListProps) => {
const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: ChannelListProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const {width} = useWindowDimensions();
const isTablet = useIsTablet();
const tabletWidth = useSharedValue(isTablet ? getTabletWidth(teamsCount) : 0);
const tabletWidth = useSharedValue(isTablet ? getTabletWidth(moreThanOneTeam) : 0);
useEffect(() => {
if (isTablet) {
tabletWidth.value = getTabletWidth(teamsCount);
tabletWidth.value = getTabletWidth(moreThanOneTeam);
}
}, [isTablet && teamsCount]);
}, [isTablet && moreThanOneTeam]);
const tabletStyle = useAnimatedStyle(() => {
if (!isTablet) {
@@ -61,7 +61,7 @@ const CategoriesList = ({channelsCount, iconPad, isCRTEnabled, teamsCount}: Chan
}, [isTablet, width]);
const content = useMemo(() => {
if (channelsCount < 1) {
if (!hasChannels) {
return (<LoadChannelsError/>);
}

View File

@@ -29,9 +29,10 @@ import Servers from './servers';
import type {LaunchType} from '@typings/launch';
type ChannelProps = {
channelsCount: number;
hasChannels: boolean;
isCRTEnabled: boolean;
teamsCount: number;
hasTeams: boolean;
hasMoreThanOneTeam: boolean;
isLicensed: boolean;
showToS: boolean;
launchType: LaunchType;
@@ -126,10 +127,10 @@ const ChannelListScreen = (props: ChannelProps) => {
}, [theme, insets.top]);
useEffect(() => {
if (!props.teamsCount) {
if (!props.hasTeams) {
resetToTeams();
}
}, [Boolean(props.teamsCount)]);
}, [Boolean(props.hasTeams)]);
useEffect(() => {
const back = BackHandler.addEventListener('hardwareBackPress', handleBackPress);
@@ -176,13 +177,13 @@ const ChannelListScreen = (props: ChannelProps) => {
>
<TeamSidebar
iconPad={canAddOtherServers}
teamsCount={props.teamsCount}
hasMoreThanOneTeam={props.hasMoreThanOneTeam}
/>
<CategoriesList
iconPad={canAddOtherServers && props.teamsCount <= 1}
iconPad={canAddOtherServers && !props.hasMoreThanOneTeam}
isCRTEnabled={props.isCRTEnabled}
teamsCount={props.teamsCount}
channelsCount={props.channelsCount}
moreThanOneTeam={props.hasMoreThanOneTeam}
hasChannels={props.hasChannels}
/>
{isTablet &&
<AdditionalTabletView/>

View File

@@ -4,7 +4,7 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
import {queryAllMyChannelsForTeam} from '@queries/servers/channel';
import {observeCurrentTeamId, observeLicense} from '@queries/servers/system';
@@ -21,11 +21,22 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
switchMap((lcs) => (lcs ? of$(lcs.IsLicensed === 'true') : of$(false))),
);
const teamsCount = queryMyTeams(database).observeCount(false);
return {
isCRTEnabled: observeIsCRTEnabled(database),
teamsCount: queryMyTeams(database).observeCount(false),
channelsCount: observeCurrentTeamId(database).pipe(
hasTeams: teamsCount.pipe(
switchMap((v) => of$(v > 0)),
distinctUntilChanged(),
),
hasMoreThanOneTeam: teamsCount.pipe(
switchMap((v) => of$(v > 1)),
distinctUntilChanged(),
),
hasChannels: observeCurrentTeamId(database).pipe(
switchMap((id) => (id ? queryAllMyChannelsForTeam(database, id).observeCount(false) : of$(0))),
switchMap((v) => of$(v > 0)),
distinctUntilChanged(),
),
isLicensed,
showToS: observeShowToS(database),

View File

@@ -5,6 +5,7 @@ import {useIsFocused, useNavigation} from '@react-navigation/native';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {FlatList, LayoutChangeEvent, Platform, StyleSheet, ViewStyle} from 'react-native';
import HWKeyboardEvent from 'react-native-hw-keyboard-event';
import Animated, {useAnimatedStyle, useDerivedValue, withTiming} from 'react-native-reanimated';
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
@@ -16,12 +17,14 @@ import FreezeScreen from '@components/freeze_screen';
import Loading from '@components/loading';
import NavigationHeader from '@components/navigation_header';
import RoundedHeaderContext from '@components/rounded_header_context';
import {Screens} from '@constants';
import {BOTTOM_TAB_HEIGHT} from '@constants/view';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {useKeyboardHeight} from '@hooks/device';
import useDidUpdate from '@hooks/did_update';
import {useCollapsibleHeader} from '@hooks/header';
import NavigationStore from '@store/navigation_store';
import {FileFilter, FileFilters, filterFileExtensions} from '@utils/file';
import {TabTypes, TabType} from '@utils/search';
@@ -317,6 +320,19 @@ const SearchScreen = ({teamId, teams}: Props) => {
}
}, [isFocused]);
useEffect(() => {
const listener = HWKeyboardEvent.onHWKeyPressed((keyEvent: {pressedKey: string}) => {
const topScreen = NavigationStore.getVisibleScreen();
if (topScreen === Screens.HOME && isFocused && keyEvent.pressedKey === 'enter') {
searchRef.current?.blur();
onSubmit();
}
});
return () => {
listener.remove();
};
}, [onSubmit]);
return (
<FreezeScreen freeze={!isFocused}>
<NavigationHeader

View File

@@ -31,12 +31,13 @@ import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type ReactionModel from '@typings/database/models/servers/reaction';
import type UserModel from '@typings/database/models/servers/user';
import type {AvailableScreens} from '@typings/screens/navigation';
type EnhancedProps = WithDatabaseArgs & {
combinedPost?: Post | PostModel;
post: PostModel;
showAddReaction: boolean;
location: string;
sourceScreen: AvailableScreens;
serverUrl: string;
}
@@ -75,7 +76,7 @@ const withPost = withObservables([], ({post, database}: {post: Post | PostModel}
};
});
const enhanced = withObservables([], ({combinedPost, post, showAddReaction, location, database, serverUrl}: EnhancedProps) => {
const enhanced = withObservables([], ({combinedPost, post, showAddReaction, sourceScreen, database, serverUrl}: EnhancedProps) => {
const channel = observeChannel(database, post.channelId);
const channelIsArchived = channel.pipe(switchMap((ch: ChannelModel) => of$(ch.deleteAt !== 0)));
const currentUser = observeCurrentUser(database);
@@ -112,7 +113,7 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca
);
const canReply = combineLatest([channelIsArchived, channelIsReadOnly, canPostPermission]).pipe(switchMap(([isArchived, isReadOnly, canPost]) => {
return of$(!isArchived && !isReadOnly && location !== Screens.THREAD && !isSystemMessage(post) && canPost);
return of$(!isArchived && !isReadOnly && sourceScreen !== Screens.THREAD && !isSystemMessage(post) && canPost);
}));
const canPin = combineLatest([channelIsArchived, channelIsReadOnly]).pipe(switchMap(([isArchived, isReadOnly]) => {

View File

@@ -1,6 +1,200 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {General, Preferences} from '@constants';
import {DMS_CATEGORY} from '@constants/categories';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {isDMorGM} from '@utils/channel';
import {getUserIdFromChannelName} from '@utils/user';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type UserModel from '@typings/database/models/servers/user';
export type ChannelWithMyChannel = {
channel: ChannelModel;
myChannel: MyChannelModel;
sortOrder: number;
}
export function makeCategoryChannelId(teamId: string, channelId: string) {
return `${teamId}_${channelId}`;
}
export const isUnreadChannel = (myChannel: MyChannelModel, notifyProps?: Partial<ChannelNotifyProps>, lastUnreadChannelId?: string) => {
const isMuted = notifyProps?.mark_unread === General.MENTION;
return (isMuted && myChannel.mentionsCount) || (!isMuted && myChannel.isUnread) || (myChannel.id === lastUnreadChannelId);
};
export const filterArchivedChannels = (channelsWithMyChannel: ChannelWithMyChannel[], currentChannelId: string) => {
return channelsWithMyChannel.filter((cwm) => cwm.channel.deleteAt === 0 || cwm.channel.id === currentChannelId);
};
export const filterAutoclosedDMs = (
categoryType: CategoryType, limit: number, currentChannelId: string,
channelsWithMyChannel: ChannelWithMyChannel[], preferences: PreferenceModel[],
notifyPropsPerChannel: Record<string, Partial<ChannelNotifyProps>>,
deactivatedDMs?: Map<string, UserModel | undefined >,
lastUnreadChannelId?: string,
) => {
if (categoryType !== DMS_CATEGORY) {
// Only autoclose DMs that haven't been assigned to a category
return channelsWithMyChannel;
}
const prefMap = preferences.reduce((acc, v) => {
const existing = acc.get(v.name);
acc.set(v.name, Math.max((v.value as unknown as number) || 0, existing || 0));
return acc;
}, new Map<string, number>());
const getLastViewedAt = (cwm: ChannelWithMyChannel) => {
// The server only ever sets the last_viewed_at to the time of the last post in channel, so we may need
// to use the preferences added for the previous version of autoclosing DMs.
const id = cwm.channel.id;
return Math.max(
cwm.myChannel.lastViewedAt,
prefMap.get(id) || 0,
);
};
let unreadCount = 0;
let visibleChannels = channelsWithMyChannel.filter((cwm) => {
const {channel, myChannel} = cwm;
if (myChannel.isUnread) {
unreadCount++;
// Unread DMs/GMs are always visible
return true;
}
if (channel.id === currentChannelId) {
return true;
}
// DMs with deactivated users will be visible if you're currently viewing them and they were opened
// since the user was deactivated
if (channel.type === General.DM_CHANNEL) {
const lastViewedAt = getLastViewedAt(cwm);
const teammate = deactivatedDMs?.get(channel.id);
if (teammate && teammate.deleteAt > lastViewedAt) {
return false;
}
}
return true;
});
visibleChannels.sort((cwmA, cwmB) => {
const channelA = cwmA.channel;
const channelB = cwmB.channel;
const myChannelA = cwmA.myChannel;
const myChannelB = cwmB.myChannel;
// Should always prioritise the current channel
if (channelA.id === currentChannelId) {
return -1;
} else if (channelB.id === currentChannelId) {
return 1;
}
// Second priority is for unread channels
const isUnreadA = isUnreadChannel(myChannelA, notifyPropsPerChannel[myChannelA.id], lastUnreadChannelId);
const isUnreadB = isUnreadChannel(myChannelB, notifyPropsPerChannel[myChannelB.id], lastUnreadChannelId);
if (isUnreadA && !isUnreadB) {
return -1;
} else if (isUnreadB && !isUnreadA) {
return 1;
}
// Third priority is last_viewed_at
const channelAlastViewed = getLastViewedAt(cwmA) || 0;
const channelBlastViewed = getLastViewedAt(cwmB) || 0;
if (channelAlastViewed > channelBlastViewed) {
return -1;
} else if (channelBlastViewed > channelAlastViewed) {
return 1;
}
return 0;
});
// The limit of DMs user specifies to be rendered in the sidebar
const remaining = Math.max(limit, unreadCount);
visibleChannels = visibleChannels.slice(0, remaining);
return visibleChannels;
};
export const filterManuallyClosedDms = (
channelsWithMyChannel: ChannelWithMyChannel[],
notifyPropsPerChannel: Record<string, Partial<ChannelNotifyProps>>,
preferences: PreferenceModel[],
currentUserId: string,
lastUnreadChannelId?: string,
) => {
return channelsWithMyChannel.filter((cwm) => {
const {channel, myChannel} = cwm;
if (!isDMorGM(channel)) {
return true;
} else if (!myChannel.lastPostAt) {
// If the direct channel does not have posts we hide it
return false;
}
if (isUnreadChannel(myChannel, notifyPropsPerChannel[myChannel.id], lastUnreadChannelId)) {
// Unread DMs/GMs are always visible
return true;
}
if (channel.type === General.DM_CHANNEL) {
const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
return getPreferenceAsBool(preferences, Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW, teammateId, true);
}
return getPreferenceAsBool(preferences, Preferences.CATEGORIES.GROUP_CHANNEL_SHOW, channel.id, true);
});
};
const sortChannelsByName = (notifyPropsPerChannel: Record<string, Partial<ChannelNotifyProps>>, locale: string) => {
return (a: ChannelWithMyChannel, b: ChannelWithMyChannel) => {
// Sort muted channels last
const aMuted = notifyPropsPerChannel[a.channel.id]?.mark_unread === General.MENTION;
const bMuted = notifyPropsPerChannel[b.channel.id]?.mark_unread === General.MENTION;
if (aMuted && !bMuted) {
return 1;
} else if (!aMuted && bMuted) {
return -1;
}
// And then sort alphabetically
return a.channel.displayName.localeCompare(b.channel.displayName, locale, {numeric: true});
};
};
export const sortChannels = (sorting: CategorySorting, channelsWithMyChannel: ChannelWithMyChannel[], notifyPropsPerChannel: Record<string, Partial<ChannelNotifyProps>>, locale: string) => {
if (sorting === 'recent') {
return channelsWithMyChannel.sort((cwmA, cwmB) => {
return cwmB.myChannel.lastPostAt - cwmA.myChannel.lastPostAt;
}).map((cwm) => cwm.channel);
} else if (sorting === 'manual') {
return channelsWithMyChannel.sort((cwmA, cwmB) => {
return cwmA.sortOrder - cwmB.sortOrder;
}).map((cwm) => cwm.channel);
}
const sortByName = sortChannelsByName(notifyPropsPerChannel, locale);
return channelsWithMyChannel.sort(sortByName).map((cwm) => cwm.channel);
};
export const getUnreadIds = (cwms: ChannelWithMyChannel[], notifyPropsPerChannel: Record<string, Partial<ChannelNotifyProps>>, lastUnreadId?: string) => {
return cwms.reduce<Set<string>>((result, cwm) => {
if (isUnreadChannel(cwm.myChannel, notifyPropsPerChannel, lastUnreadId)) {
result.add(cwm.channel.id);
}
return result;
}, new Set());
};

View File

@@ -3,10 +3,10 @@
import {isMinimumServerVersion} from './helpers';
export function hasReliableWebsocket(config: ClientConfig) {
if (isMinimumServerVersion(config.Version, 6, 5)) {
export function hasReliableWebsocket(version?: string, reliableWebsocketsConfig?: string) {
if (version && isMinimumServerVersion(version, 6, 5)) {
return true;
}
return config.EnableReliableWebSockets === 'true';
return reliableWebsocketsConfig === 'true';
}

View File

@@ -200,7 +200,7 @@ export function doesMatchNamedEmoji(emojiName: string) {
return false;
}
export const getEmojiFirstAlias = (emoji: string) => {
export const getEmojiFirstAlias = (emoji: string): string => {
return getEmojiByName(emoji, [])?.short_names?.[0] || emoji;
};

1
assets/base/i18n/ar.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -872,7 +872,6 @@
"mobile.post_pre_header.saved": "",
"mobile.participants.header": "",
"mobile.oauth.switch_to_browser": "",
"mobile.login_options.sso_continue": "",
"mobile.login_options.separator_text": "",
"mobile.login_options.select_option": "",
"mobile.login_options.gitlab": "",

View File

@@ -545,7 +545,7 @@
"mobile.create_direct_message.start": "Starte Unterhaltung",
"mobile.create_channel.title": "Neuer Kanal",
"mobile.components.select_server_view.msg_welcome": "Willkommen",
"mobile.components.select_server_view.msg_description": "Ein Server ist das Kommunikationszentrum deines Teams, auf den über eine eindeutige URL zugegriffen wird",
"mobile.components.select_server_view.msg_description": "Ein Server ist das Kommunikationszentrum deines Teams, erreichbar über eine eindeutige URL",
"mobile.components.select_server_view.msg_connect": "Verbinden wir uns mit einem Server",
"mobile.components.select_server_view.displayName": "Anzeigename",
"mobile.components.select_server_view.displayHelp": "Wähle einen Anzeigenamen für deinen Server",
@@ -593,7 +593,6 @@
"mobile.no_results_with_term.messages": "Keine Treffer für \"{term}\" gefunden",
"mobile.no_results_with_term.files": "Keine Dateien zum Thema \"{term}\"",
"mobile.no_results_with_term": "Keine Ergebnisse für \"{term}\"",
"mobile.login_options.sso_continue": "Weiter mit",
"mobile.login_options.separator_text": "oder melde dich an mit",
"mobile.login_options.select_option": "Wähle unten eine Anmeldeoption.",
"mobile.login_options.saml": "SAML",
@@ -993,5 +992,19 @@
"mobile.calls_recording_start_no_permissions": "Du hast nicht die Berechtigung, eine Aufzeichnung zu starten. Bitte frage den Gesprächsleiter, ob er eine Aufzeichnung starten darf.",
"mobile.calls_recording_start_in_progress": "Eine Aufzeichnung ist bereits im Gange.",
"mobile.calls_host_rec_error_title": "Bei der Aufnahme ist etwas schief gelaufen",
"mobile.calls_host_rec_error": "Bitte versuche, die Aufzeichnung zu wiederholen. Du kannst dich auch an deinen Systemadministrator wenden, um Hilfe bei der Fehlerbehebung zu erhalten."
"mobile.calls_host_rec_error": "Bitte versuche, die Aufzeichnung zu wiederholen. Du kannst dich auch an deinen Systemadministrator wenden, um Hilfe bei der Fehlerbehebung zu erhalten.",
"notification.no_post": "Die Nachricht wurde nicht gefunden.",
"notification.no_connection": "Der Server ist nicht erreichbar und wir waren nicht in der Lage, den Benachrichtigungskanal / das Team abzurufen.",
"invite.summary.back": "Zurück",
"channel_notification_preferences.unmute_content": "Stummschaltung aufheben",
"channel_notification_preferences.thread_replies": "Antworten zu Unterhaltungen",
"channel_notification_preferences.reset_default": "Auf Standardwerte zurücksetzen",
"channel_notification_preferences.notify_about": "Benachrichtige mich über...",
"channel_notification_preferences.notification.thread_replies": "Benachrichtige mich über Antworten auf Unterhaltungen, denen ich in diesem Kanal folge",
"channel_notification_preferences.notification.none": "Keine",
"channel_notification_preferences.notification.mention": "Erwähnungen, nur Direktnachrichten",
"channel_notification_preferences.notification.all": "Alle neuen Nachrichten",
"channel_notification_preferences.muted_title": "Dieser Kanal ist stummgeschaltet",
"channel_notification_preferences.muted_content": "Du kannst die Benachrichtigungseinstellungen ändern, erhältst aber keine Benachrichtigungen, bis der Kanal wieder freigeschaltet wird.",
"channel_notification_preferences.default": "(Standard)"
}

View File

@@ -579,7 +579,6 @@
"mobile.no_results_with_term.messages": "No matches found for '\\{term}'\\",
"mobile.no_results_with_term.files": "No files matching '\\{term}'\\",
"mobile.no_results_with_term": "No results for '\\{term}'\\",
"mobile.login_options.sso_continue": "Continue with",
"mobile.login_options.separator_text": "or log in with",
"mobile.login_options.select_option": "Select a login option below.",
"mobile.login_options.saml": "SAML",
@@ -607,7 +606,7 @@
"mobile.create_direct_message.start": "Start Conversation",
"mobile.create_channel.title": "New channel",
"mobile.components.select_server_view.msg_welcome": "Welcome",
"mobile.components.select_server_view.msg_description": "A Server is your team's communication hub which is accessed through a unique URL.",
"mobile.components.select_server_view.msg_description": "A server is your team's communication hub accessed using a unique URL",
"mobile.components.select_server_view.msg_connect": "Lets connect to a Server",
"mobile.components.select_server_view.displayName": "Display Name",
"mobile.components.select_server_view.displayHelp": "Choose a display name for your server",
@@ -993,5 +992,19 @@
"invite.summary.member_invite": "Invited as a member of {teamDisplayName}",
"invite.summary.email_invite": "An invitation email has been sent",
"invite.summary.done": "Done",
"invite.shareLink": "Share link"
"invite.shareLink": "Share link",
"notification.no_post": "The message has not been found.",
"notification.no_connection": "The server is unreachable and we were not able to retrieve the notification channel / team.",
"invite.summary.back": "Go back",
"channel_notification_preferences.unmute_content": "Unmute channel",
"channel_notification_preferences.thread_replies": "Thread replies",
"channel_notification_preferences.reset_default": "Reset to default",
"channel_notification_preferences.notify_about": "Notify me about:",
"channel_notification_preferences.notification.thread_replies": "Notify me about replies to threads Im following in this channel",
"channel_notification_preferences.notification.none": "Nothing",
"channel_notification_preferences.notification.mention": "Mentions, direct messages only",
"channel_notification_preferences.notification.all": "All new messages",
"channel_notification_preferences.muted_title": "This channel is muted",
"channel_notification_preferences.muted_content": "You can change the notification settings, but you will not receive notifications until the channel is unmuted.",
"channel_notification_preferences.default": "(default)"
}

View File

@@ -647,7 +647,6 @@
"mobile.no_results_with_term.messages": "No se encontraron coincidencias para “{term}”",
"mobile.no_results_with_term": "No hay resultados para “{term}”",
"mobile.no_results_with_term.files": "No hay archivos que coincidan con “{term}”",
"mobile.login_options.sso_continue": "Continua con",
"mobile.login_options.separator_text": "o inicia sesión con",
"mobile.login_options.select_option": "Selecciona a continuación una opción de inicio de sesión.",
"mobile.login_options.saml": "SAML",

View File

@@ -603,7 +603,6 @@
"mobile.no_results_with_term": "نتیجه‌ای برای «{term}» نیست",
"mobile.managed.jailbreak_no_reason": "موجود نیست",
"mobile.managed.jailbreak_no_debug_info": "موجود نیست",
"mobile.login_options.sso_continue": "ادامه دهید با",
"mobile.login_options.separator_text": "یا وارد شوید با",
"mobile.login_options.select_option": "یکی از گزینه‌های ورود را انتخاب کنید.",
"mobile.login_options.saml": "SAML",

View File

@@ -703,7 +703,6 @@
"mobile.no_results_with_term": "Aucun résultat pour \"{term}\"",
"mobile.no_results_with_term.messages": "Aucun résultat trouvé pour \"{term}\"",
"mobile.no_results_with_term.files": "Aucun fichier correspondant à \"{term}\"",
"mobile.login_options.sso_continue": "Poursuivre avec",
"mobile.login_options.separator_text": "ou connectez-vous avec",
"mobile.login_options.select_option": "Sélectionnez une option de connexion ci-dessous.",
"mobile.login_options.saml": "SAML",

View File

@@ -640,7 +640,6 @@
"screen.mentions.subtitle": "",
"rate.button.yes": "",
"rate.button.needs_work": "",
"mobile.login_options.sso_continue": "",
"mobile.integration_selector.loading_options": "",
"mobile.integration_selector.loading_channels": "",
"unsupported_server.message": "",

View File

@@ -252,7 +252,7 @@
"user.edit_profile.email.web_client": "L'e-mail deve essere aggiornata utilizzando un client web o un'applicazione desktop.",
"user.edit_profile.email.auth_service": "L'accesso avviene tramite {service}. L'e-mail non può essere aggiornata. L'indirizzo e-mail utilizzato per le notifiche è {email}.",
"user_status.title": "Stato",
"user_status.online": "In linea",
"user_status.online": "Online",
"user_status.offline": "Non in linea",
"user_status.dnd": "Non disturbare",
"user_status.away": "Via",
@@ -282,7 +282,7 @@
"thread.header.thread": "Il filo",
"terms_of_service.title": "Termini di servizio",
"terms_of_service.terms_declined.title": "È necessario accettare i termini di servizio",
"terms_of_service.terms_declined.text": "Per accedere a questo server è necessario accettare i termini di servizio. Per maggiori dettagli, contattare l'amministratore di sistema. A questo punto sarete disconnessi. Effettuare nuovamente il login per accettare i termini di servizio.",
"terms_of_service.terms_declined.text": "Per accedere a questo server è necessario accettare i termini di servizio. Per maggiori dettagli, contattare l'amministratore di sistema. L'utente sarà disconnesso. Effettuare nuovamente il login per accettare i termini di servizio.",
"terms_of_service.terms_declined.ok": "OK",
"terms_of_service.error.title": "Impossibile ottenere il ToS.",
"terms_of_service.error.retry": "Riprova",
@@ -357,7 +357,7 @@
"settings_display.clock.standard": "Orologio a 12 ore",
"settings_display.clock.normal.desc": "Esempio: 16:00",
"settings_display.clock.mz.desc": "Esempio: 16:00",
"settings_display.clock.mz": "Orologio 24 ore su 24",
"settings_display.clock.mz": "Orologio 24 ore",
"servers.remove": "Rimuovi",
"servers.logout": "Disconnettersi",
"servers.login": "Accedi",
@@ -401,7 +401,7 @@
"plus_menu.create_new_channel.title": "Creare un nuovo canale",
"plus_menu.browse_channels.title": "Sfoglia i canali",
"pinned_messages.empty.title": "Non ci sono ancora messaggi appuntati",
"pinned_messages.empty.paragraph": "Per appuntare i messaggi importanti, premere a lungo su un messaggio e scegliere Appunta al canale. I messaggi appuntati saranno visibili a tutti gli utenti del canale.",
"pinned_messages.empty.paragraph": "Per bloccare i messaggi importanti, premere a lungo su un messaggio e scegliere Blocca al canale. I messaggi bloccati saranno visibili a tutti gli utenti del canale.",
"permalink.show_dialog_warn.join": "Unirsi",
"permalink.show_dialog_warn.description": "Stai per unirti a {channel} senza essere stato aggiunto esplicitamente dall'amministratore del canale. Vuoi iscriverti a questo canale privato?",
"permalink.show_dialog_warn.cancel": "Annullamento",
@@ -416,7 +416,7 @@
"permalink.error.private_channel.button": "Iscriviti al canale",
"permalink.error.private_channel_and_team.title": "Unirsi al canale privato e al team",
"permalink.error.private_channel_and_team.text": "Il messaggio che si sta cercando di visualizzare si trova in un canale privato di un team di cui non si è membri. Hai accesso come amministratore. Vuoi unirti a **{channelName}** e alla squadra **{teamName}** per vederlo?",
"permalink.error.private_channel_and_team.button": "Unirsi al canale e al team",
"permalink.error.private_channel_and_team.button": "Partecipa al canale e al team",
"permalink.error.okay": "Ok",
"permalink.error.cancel": "Annullamento",
"permalink.error.access.title": "Messaggio non visualizzabile",
@@ -431,7 +431,7 @@
"onboarding.realtime_collaboration": "Collaborare in tempo reale",
"onboarding.integrations_description": "Vai oltre la chat con soluzioni di prodotto strettamente integrate e abbinate a processi di sviluppo comuni.",
"onboarding.integrations": "Integratevi con gli strumenti che amate",
"onboarding.calls_description": "Quando la digitazione non è abbastanza veloce, passate dalla chat basata sui canali alle chiamate audio sicure con un solo tocco.",
"onboarding.calls_description": "Quando la digitazione non è abbastanza veloce, passa dalla chat basata sui canali alle chiamate audio protette con un solo tocco.",
"onboarding.calls": "Start secure audio calls instantly",
"notification.not_team_member": "Questo messaggio appartiene a un team di cui non si fa parte.",
"notification.not_channel_member": "Questo messaggio appartiene a un canale di cui non si è membri.",
@@ -449,8 +449,8 @@
"notification_settings.push_notification": "Notifiche push",
"notification_settings.ooo_auto_responder": "Risposte automatiche",
"notification_settings.mobile.trigger_push": "Attivare notifiche push quando...",
"notification_settings.mobile.online": "Online, in trasferta o offline",
"notification_settings.mobile.offline": "Non in linea",
"notification_settings.mobile.online": "Online, non al computer o offline",
"notification_settings.mobile.offline": "Offline",
"notification_settings.mobile.away": "In trasferta o offline",
"notification_settings.mobile": "Notifiche push",
"notification_settings.mentions.sensitiveUsername": "Il vostro nome utente non sensibile alle maiuscole",
@@ -470,7 +470,7 @@
"notification_settings.email.everyHour": "Ogni ora",
"notification_settings.email.emailInfo": "Le notifiche via e-mail vengono inviate per le menzioni e i messaggi diretti quando si è offline o lontani per più di 5 minuti.",
"notification_settings.email.emailHelp2": "L'amministratore di sistema ha disabilitato l'invio di e-mail. Non verranno inviate e-mail di notifica finché non sarà abilitata.",
"notification_settings.email.crt.send": "Notifiche di risposta alle discussioni",
"notification_settings.email.crt.send": "Notifiche di risposta ai thread",
"notification_settings.email.crt.emailInfo": "Se abilitato, le risposte a un thread che si sta seguendo invieranno una notifica via e-mail",
"notification_settings.email": "Notifiche via e-mail",
"notification_settings.auto_responder.to.enable": "Abilita le risposte automatiche",
@@ -543,8 +543,7 @@
"mobile.manage_members.change_role.error": "Si è verificato un errore durante il tentativo di aggiornamento del ruolo. Controllare la connessione e riprovare.",
"mobile.manage_members.cancel": "Annullamento",
"mobile.manage_members.admin": "Admin",
"mobile.login_options.sso_continue": "Continua con",
"mobile.login_options.separator_text": "o accedere con",
"mobile.login_options.separator_text": "o accedi con",
"mobile.login_options.select_option": "Seleziona un'opzione di accesso qui sotto.",
"mobile.login_options.saml": "SAML",
"mobile.login_options.openid": "ID aperto",
@@ -557,7 +556,7 @@
"mobile.login_options.cant_heading": "Impossibile accedere",
"mobile.link.error.title": "Errore",
"mobile.link.error.text": "Impossibile aprire il link.",
"mobile.leave_and_join_title": "È sicuro di voler passare a un'altra chiamata?",
"mobile.leave_and_join_title": "Passare a un'altra chiamata?",
"mobile.leave_and_join_message": "Siete già impegnati in una chiamata di canale in ~{leaveChannelName}. Volete lasciare la vostra chiamata attuale e unirvi alla chiamata in ~{joinChannelName}?",
"mobile.leave_and_join_confirmation": "Partire e unirsi",
"mobile.join_channel.error": "Non è stato possibile unirsi al canale {displayName}.",
@@ -637,9 +636,9 @@
"mobile.calls_join_call": "Partecipa alla chiamata",
"mobile.calls_host_rec_title": "Stai registrando",
"mobile.calls_host_rec_stopped_title": "La registrazione è stata interrotta. Elaborazione...",
"mobile.calls_host_rec_stopped": "Potete trovare la registrazione nel thread della chat di questa chiamata una volta terminata l'elaborazione.",
"mobile.calls_host_rec_stopped": "Puoi trovare la registrazione nel thread della chat di questa chiamata al termine dell'elaborazione.",
"mobile.calls_host_rec_error_title": "Si è verificato un problema nella registrazione",
"mobile.calls_host_rec_error": "Provare a registrare di nuovo. È inoltrProvare a registrare di nuovo. È inoltre possibile contattare l'amministratore del sistema per ottenere assistenza nella risoluzione dei problemi.e possibile contattare l'amministratore del sistema per ottenere assistenza nella risoluzione dei problemi.",
"mobile.calls_host_rec_error": "Provare a registrare di nuovo. È inoltre possibile contattare l'amministratore del sistema per ottenere assistenza nella risoluzione dei problemi.",
"mobile.calls_host_rec": "Stai registrando la riunione. Informa tutti i partecipanti che questa riunione viene registrata.",
"mobile.calls_host": "ospite",
"mobile.calls_error_title": "Errore",
@@ -668,7 +667,7 @@
"login.signingIn": "Accesso",
"login.signIn": "Accedi",
"login.invalid_credentials": "La combinazione di e-mail e password non è corretta",
"login.forgot": "Avete dimenticato la password?",
"login.forgot": "Password dimenticata?",
"login_mfa.token": "Inserire il token MFA",
"login_mfa.enterToken": "Per completare il processo di accesso, inserire il codice dall'app di autenticazione del dispositivo mobile.",
"load_teams_error.title": "Impossibile caricare {serverName}",
@@ -740,7 +739,7 @@
"gallery.video_saved": "Video salvato",
"gallery.unsupported": "L'anteprima non è supportata per questo tipo di file. Provate a scaricarlo o a condividerlo per aprirlo in un'altra applicazione.",
"gallery.save_failed": "Impossibile salvare il file",
"gallery.preparing": "Preparare...",
"gallery.preparing": "Preparazione in corso...",
"gallery.opening": "Apertura...",
"gallery.open_file": "Aprire il file",
"gallery.image_saved": "Immagine salvata",
@@ -826,7 +825,7 @@
"display_settings.crt.off": "Spento",
"display_settings.crt": "Thread di risposta compressi",
"display_settings.clockDisplay": "Display dell'orologio",
"display_settings.clock.standard": "12 ore",
"display_settings.clock.standard": "Orologio 12 ore",
"display_settings.clock.military": "24 ore su 24",
"default_skin_tone": "Tonalità della pelle predefinita",
"custom_status.suggestions.working_from_home": "Lavorare da casa",
@@ -891,7 +890,7 @@
"channel_info.leave_channel": "Lasciare il canale",
"channel_info.leave": "Lasciare",
"channel_info.ignore_mentions": "Ignorare @canale, @qui, @tutti",
"channel_info.favorited": "Favorito",
"channel_info.favorited": "Preferito",
"channel_info.favorite": "Preferito",
"channel_info.error_close": "Chiudere",
"channel_info.edit_header": "Modifica intestazione",
@@ -932,7 +931,7 @@
"announcment_banner.okay": "Ok",
"alert.push_proxy.button": "Ok",
"alert.push_proxy_unknown.title": "Non è stato possibile ricevere notifiche da questo server",
"alert.push_proxy_unknown.description": "Questo server non è stato in grado di ricevere le notifiche push per un motivo sconosciuto. La prossima volta che ci si connetterà si tenterà di farlo di nuovo.",
"alert.push_proxy_unknown.description": "Questo server non è stato in grado di ricevere le notifiche push per un motivo sconosciuto. Verrà effettuato un nuovo tentativo alla prossima connessione.",
"alert.push_proxy_error.title": "Notifications cannot be received from this server",
"alert.push_proxy_error.description": "A causa della configurazione di questo server, le notifiche non possono essere ricevute nell'app mobile. Per ulteriori informazioni, contattare l'amministratore del sistema.",
"alert.channel_deleted.title": "Canale archiviato",

View File

@@ -5,7 +5,7 @@
"about.enterpriseEditionSt": "ファイアウォールの内側で、現代的なコミュニケーションを実現します。",
"about.hash": "ビルドハッシュ値:",
"about.hashee": "EEビルドハッシュ値:",
"about.teamEditionLearn": "Mattermostコミュニティーに参加する: ",
"about.teamEditionLearn": "Mattermostコミュニティーに参加する",
"about.teamEditionSt": "あなたのチームの全てのコミュニケーションを一箇所で、すぐに検索可能で、どこからでもアクセスできるものにします。",
"about.teamEditiont0": "Team Edition",
"about.teamEditiont1": "Enterprise Edition",
@@ -508,7 +508,6 @@
"mobile.no_results_with_term.messages": "\"{term}\"に該当する情報がありませんでした",
"mobile.no_results_with_term.files": "\"{term}\" に一致するファイルがありませんでした",
"mobile.no_results_with_term": "\"{term}\"の結果はありません",
"mobile.login_options.sso_continue": "続ける",
"mobile.login_options.separator_text": "または 次の方法でログインする",
"mobile.login_options.select_option": "以下からログイン方法を選択してください。",
"mobile.login_options.saml": "SAML",
@@ -973,25 +972,39 @@
"invite.summary.email_invite": "招待メールが送信されました",
"invite.summary.done": "完了",
"invite.sendInvitationsTo": "招待を送る…",
"mobile.calls_recording_start_in_progress": "",
"mobile.calls_host_rec_error": "",
"mobile.manage_members.section_title_members": "",
"mobile.manage_members.member": "",
"mobile.manage_members.manage_member": "",
"mobile.manage_members.manage": "",
"mobile.calls_recording_stop_none_in_progress": "",
"mobile.calls_recording_stop_no_permissions": "",
"mobile.calls_recording_start_no_permissions": "",
"mobile.calls_host_rec_error_title": "",
"snack.bar.remove.user": "",
"mobile.manage_members.section_title_admins": "",
"mobile.manage_members.remove_member": "",
"mobile.manage_members.remove": "",
"mobile.manage_members.message": "",
"mobile.manage_members.admin": "",
"mobile.manage_members.make_channel_member": "",
"mobile.manage_members.make_channel_admin": "",
"mobile.manage_members.done": "",
"mobile.manage_members.change_role.error": "",
"mobile.manage_members.cancel": ""
"mobile.calls_recording_start_in_progress": "既にレコーディング中です。",
"mobile.calls_host_rec_error": "もう一度レコーディングを試してみてください。もしくは、この問題についてシステム管理者に問い合わせてください。",
"mobile.manage_members.section_title_members": "メンバー",
"mobile.manage_members.member": "メンバー",
"mobile.manage_members.manage_member": "メンバー管理",
"mobile.manage_members.manage": "管理",
"mobile.calls_recording_stop_none_in_progress": "レコーディング中ではありません。",
"mobile.calls_recording_stop_no_permissions": "レコーディングを停止する権限がありません。通話のホストにレコーディングを停止するよう依頼してください。",
"mobile.calls_recording_start_no_permissions": "レコーディングを開始する権限がありません。通話のホストにレコーディングを開始するよう依頼してください。",
"mobile.calls_host_rec_error_title": "レコーディングに問題が発生しました",
"snack.bar.remove.user": "1名のメンバーがチャンネルから削除されました",
"mobile.manage_members.section_title_admins": "チャンネル管理者",
"mobile.manage_members.remove_member": "チャンネルから削除する",
"mobile.manage_members.remove": "削除",
"mobile.manage_members.message": "本当に選択したメンバーをチャンネルから削除しますか?",
"mobile.manage_members.admin": "管理者",
"mobile.manage_members.make_channel_member": "チャンネルメンバーにする",
"mobile.manage_members.make_channel_admin": "チャンネル管理者にする",
"mobile.manage_members.done": "完了",
"mobile.manage_members.change_role.error": "ロールを更新する際にエラーが発生しました。接続を確認し、もう一度やり直してください。",
"mobile.manage_members.cancel": "キャンセル",
"notification.no_post": "メッセージが見つかりませんでした。",
"notification.no_connection": "サーバーに到達できないため、通知のチャンネル/チームを取得できませんでした。",
"invite.summary.back": "戻る",
"channel_notification_preferences.unmute_content": "チャンネルのミュートを解除",
"channel_notification_preferences.thread_replies": "スレッドへの返信",
"channel_notification_preferences.reset_default": "デフォルト設定に戻す",
"channel_notification_preferences.notify_about": "通知の対象...",
"channel_notification_preferences.notification.thread_replies": "このチャンネルでフォロー中のスレッドへの返信を通知する",
"channel_notification_preferences.notification.none": "なし",
"channel_notification_preferences.notification.mention": "メンションとダイレクトメッセージのみ",
"channel_notification_preferences.notification.all": "すべての新しいメッセージ",
"channel_notification_preferences.muted_title": "このチャンネルはミュートされています",
"channel_notification_preferences.muted_content": "通知設定を変更することはできますが、チャンネルのミュートが解除されるまで、通知を受け取ることはできません。",
"channel_notification_preferences.default": "(デフォルト)"
}

View File

@@ -896,7 +896,6 @@
"mobile.oauth.failed_to_open_link_no_browser": "",
"mobile.no_results.spelling": "",
"mobile.no_results_with_term": "",
"mobile.login_options.sso_continue": "",
"mobile.login_options.cant_heading": "",
"mobile.leave_and_join_title": "",
"mobile.leave_and_join_message": "",

View File

@@ -392,7 +392,7 @@
"mobile.create_direct_message.start": "Gesprek beginnen",
"mobile.create_channel.title": "Nieuw kanaal",
"mobile.components.select_server_view.msg_welcome": "Welkom",
"mobile.components.select_server_view.msg_description": "Een server is het communicatiecentrum van je team, die bereikbaar is via een unieke URL",
"mobile.components.select_server_view.msg_description": "Een server is het communicatiecentrum van je team dat toegankelijk is via een unieke URL.",
"mobile.components.select_server_view.msg_connect": "Laten we verbinding maken met een server",
"mobile.components.select_server_view.displayName": "Weergavenaam",
"mobile.components.select_server_view.displayHelp": "Kies een weergavenaam voor jouw server",
@@ -603,7 +603,6 @@
"mobile.no_results_with_term.messages": "Geen resultaten gevonden voor \"{term}\"",
"mobile.no_results_with_term.files": "Geen bestanden die overeenkomen met \"{term}\"",
"mobile.no_results_with_term": "Geen resultaten voor \"{term}\"",
"mobile.login_options.sso_continue": "Doorgaan met",
"mobile.login_options.separator_text": "of meldt je aan met",
"mobile.login_options.select_option": "Selecteer hieronder een aanmeldingsoptie.",
"mobile.login_options.saml": "SAML",
@@ -993,5 +992,19 @@
"mobile.calls_recording_start_no_permissions": "Je hebt geen rechten om een opname te starten. Vraag de gesprekshost om een opname te starten.",
"mobile.calls_recording_start_in_progress": "Er is al een opname bezig.",
"mobile.calls_host_rec_error_title": "Er ging iets mis met de opname",
"mobile.calls_host_rec_error": "Probeer opnieuw op te nemen. Je kan ook contact opnemen met jouuw systeembeheerder voor hulp bij het oplossen van problemen."
"mobile.calls_host_rec_error": "Probeer opnieuw op te nemen. Je kan ook contact opnemen met jouuw systeembeheerder voor hulp bij het oplossen van problemen.",
"notification.no_post": "Het bericht werd niet gevonden.",
"notification.no_connection": "De server is onbereikbaar en we konden het meldingskanaal / team niet ophalen.",
"invite.summary.back": "Terug",
"channel_notification_preferences.unmute_content": "Kanaal niet langer dempen",
"channel_notification_preferences.thread_replies": "Reacties op draadjes",
"channel_notification_preferences.reset_default": "Terugzetten naar de standaardinstellingen",
"channel_notification_preferences.notify_about": "Breng me op de hoogte van...",
"channel_notification_preferences.notification.thread_replies": "Hou me op de hoogte van antwoorden op draadjes die ik volg in dit kanaal",
"channel_notification_preferences.notification.none": "Geen",
"channel_notification_preferences.notification.mention": "Vermeldingen, alleen directe berichten",
"channel_notification_preferences.notification.all": "Alle nieuwe berichten",
"channel_notification_preferences.muted_title": "Dit kanaal is gedempt",
"channel_notification_preferences.muted_content": "Je kan de meldingsinstellingen wijzigen, maar je ontvangt geen meldingen totdat het kanaal niet langer gedempt is.",
"channel_notification_preferences.default": "(standaard)"
}

View File

@@ -321,7 +321,7 @@
"post_body.check_for_out_of_channel_groups_mentions.message": "nie zostali powiadomieni poprzez tą wzmiankę, ponieważ nie są na kanale. Nie można ich dodać do kanału, ponieważ nie są członkami grup połączonych. Aby dodać je do tego kanału, muszą zostać dodane do połączonych grup.",
"post_body.check_for_out_of_channel_mentions.link.and": " i ",
"post_body.check_for_out_of_channel_mentions.link.private": "dodaj je do tego prywatnego kanału",
"post_body.check_for_out_of_channel_mentions.link.public": "dodaj je do kanału",
"post_body.check_for_out_of_channel_mentions.link.public": "dodać ich do kanału",
"post_body.check_for_out_of_channel_mentions.message_last": "? Będą mieć dostęp do całej historii wiadomości.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "zostały wspomniane, ale nie są w kanale. Czy chciałbyś ",
"post_body.check_for_out_of_channel_mentions.message.one": "został wspomniany, ale nie ma go w kanale. Czy chciałbyś ",
@@ -547,7 +547,7 @@
"mobile.create_direct_message.start": "Rozpocznij Rozmowę",
"mobile.create_channel.title": "Nowy kanał",
"mobile.components.select_server_view.msg_welcome": "Witamy",
"mobile.components.select_server_view.msg_description": "Serwer jest węzłem komunikacyjnym Twojego zespołu, do którego dostęp uzyskuje się poprzez unikalny adres URL",
"mobile.components.select_server_view.msg_description": "Serwer jest węzłem komunikacyjnym Twojego zespołu, do którego dostęp uzyskuje się za pomocą unikalnego adresu URL",
"mobile.components.select_server_view.msg_connect": "Połączmy się z serwerem",
"mobile.components.select_server_view.displayName": "Nazwa Wyświetlana",
"mobile.components.select_server_view.displayHelp": "Wybierz nazwę wyświetlaną dla swojego serwera",
@@ -605,7 +605,6 @@
"mobile.no_results_with_term.messages": "Nie znaleziono dopasowań dla \"{term}\"",
"mobile.no_results_with_term.files": "Brak plików pasujących do \"{term}\"",
"mobile.no_results_with_term": "Brak wyników dla \"{term}\"",
"mobile.login_options.sso_continue": "Kontynuuj z",
"mobile.login_options.separator_text": "lub zaloguj się za pomocą",
"mobile.login_options.select_option": "Wybierz poniżej opcję logowania.",
"mobile.login_options.saml": "SAML",
@@ -993,5 +992,19 @@
"mobile.calls_recording_start_no_permissions": "Nie masz uprawnień do rozpoczęcia nagrywania. Poproś prowadzącego rozmowę o uruchomienie nagrania.",
"mobile.calls_recording_start_in_progress": "Nagranie jest już w trakcie realizacji.",
"mobile.calls_host_rec_error_title": "Coś poszło nie tak z nagraniem",
"mobile.calls_host_rec_error": "Proszę spróbować nagrać ponownie. Możesz również skontaktować się z administratorem systemu, aby uzyskać pomoc w rozwiązywaniu problemów."
"mobile.calls_host_rec_error": "Proszę spróbować nagrać ponownie. Możesz również skontaktować się z administratorem systemu, aby uzyskać pomoc w rozwiązywaniu problemów.",
"notification.no_post": "Wiadomość nie została odnaleziona.",
"notification.no_connection": "Serwer jest nieosiągalny i nie byliśmy w stanie pobrać powiadomienia dla kanału / zespołu.",
"invite.summary.back": "Wróć",
"channel_notification_preferences.unmute_content": "Wyłącz wyciszenie kanału",
"channel_notification_preferences.thread_replies": "Odpowiedzi na wątki",
"channel_notification_preferences.reset_default": "Przywróć domyślne",
"channel_notification_preferences.notify_about": "Powiadom mnie o...",
"channel_notification_preferences.notification.thread_replies": "Powiadom mnie o odpowiedziach na wątki, które śledzę na tym kanale",
"channel_notification_preferences.notification.none": "Nic",
"channel_notification_preferences.notification.mention": "Wzmianki, tylko bezpośrednie wiadomości",
"channel_notification_preferences.notification.all": "Wszystkie nowe wiadomości",
"channel_notification_preferences.muted_title": "Ten kanał jest wyciszony",
"channel_notification_preferences.muted_content": "Możesz zmienić ustawienia powiadomień, ale nie będziesz otrzymywać powiadomień, dopóki na kanale nie zostanie wyłączone wyciszenie.",
"channel_notification_preferences.default": "(domyślne)"
}

View File

@@ -941,7 +941,6 @@
"mobile.oauth.switch_to_browser.title": "",
"mobile.no_results_with_term.messages": "",
"mobile.no_results_with_term.files": "",
"mobile.login_options.sso_continue": "",
"mobile.login_options.separator_text": "",
"mobile.login_options.select_option": "",
"mobile.login_options.saml": "",

View File

@@ -362,7 +362,6 @@
"custom_status.expiry_time.today": "",
"mobile.channel_list.recent": "",
"channel_info.position": "",
"mobile.login_options.sso_continue": "",
"mobile.calls_lower_hand": "",
"global_threads.emptyThreads.message": "",
"load_teams_error.title": "",

View File

@@ -415,7 +415,6 @@
"login.signIn": "Войти",
"login.invalid_credentials": "Неверная комбинация электронной почты и пароля",
"login.forgot": "Забыли пароль?",
"mobile.login_options.sso_continue": "Продолжить",
"mobile.login_options.separator_text": "или войти в систему с помощью",
"mobile.login_options.select_option": "Выберите вариант входа в систему ниже.",
"mobile.login_options.saml": "SAML",
@@ -434,7 +433,7 @@
"mobile.create_direct_message.you": "@{username} - вы",
"mobile.create_direct_message.start": "Начать разговор",
"mobile.create_channel.title": "Новый канал",
"mobile.components.select_server_view.msg_description": "Сервер - это коммуникационный узел вашей команды, доступ к которому осуществляется через уникальный URL-адрес",
"mobile.components.select_server_view.msg_description": "Сервер - это коммуникационный узел вашей команды, доступ к которому осуществляется по уникальному URL-адресу",
"mobile.components.select_server_view.msg_connect": "Давайте подключимся к серверу",
"mobile.components.select_server_view.displayName": "Отображаемое имя",
"mobile.components.select_server_view.displayHelp": "Выберите отображаемое имя для вашего сервера",
@@ -993,5 +992,19 @@
"mobile.calls_recording_start_no_permissions": "У вас нет прав для начала записи. Пожалуйста, попросите ведущего звонка начать запись.",
"mobile.calls_recording_start_in_progress": "Запись уже ведется.",
"mobile.calls_host_rec_error_title": "Что-то пошло не так с записью",
"mobile.calls_host_rec_error": "Пожалуйста, попробуйте записать еще раз. Вы также можете обратиться к системному администратору за помощью в устранении неполадок."
"mobile.calls_host_rec_error": "Пожалуйста, попробуйте записать еще раз. Вы также можете обратиться к системному администратору за помощью в устранении неполадок.",
"notification.no_post": "Сообщение не найдено.",
"notification.no_connection": "Сервер недоступен, и мы не смогли получить уведомления канала / команды.",
"invite.summary.back": "Вернуться назад",
"channel_notification_preferences.unmute_content": "Включить уведомления",
"channel_notification_preferences.thread_replies": "Ответы на обсуждения",
"channel_notification_preferences.reset_default": "Сбросить до значений по умолчанию",
"channel_notification_preferences.notify_about": "Уведомить меня о...",
"channel_notification_preferences.notification.thread_replies": "Уведомлять меня об ответах на обсуждения, за которыми я слежу в этом канале",
"channel_notification_preferences.notification.none": "Ничего",
"channel_notification_preferences.notification.mention": "Только упоминания, личные сообщения",
"channel_notification_preferences.notification.all": "Все новые сообщения",
"channel_notification_preferences.muted_title": "На этом канале уведомления отключены",
"channel_notification_preferences.muted_content": "Вы можете изменить настройки уведомлений, но вы не будете получать уведомления, пока вы не включите их для канала обратно.",
"channel_notification_preferences.default": "(по умолчанию)"
}

View File

@@ -409,7 +409,6 @@
"mobile.no_results_with_term.messages": "Inga träffar hittades för “{term}”",
"mobile.no_results_with_term.files": "Inga filer som matchar “{term}”",
"mobile.no_results_with_term": "Inga resultat för “{term}”",
"mobile.login_options.sso_continue": "Fortsätt med",
"mobile.login_options.separator_text": "eller logga in med",
"mobile.login_options.select_option": "Välj ett inloggningsalternativ nedan.",
"mobile.login_options.saml": "SAML",
@@ -438,7 +437,7 @@
"mobile.create_direct_message.start": "Starta en konversation",
"mobile.create_channel.title": "Ny kanal",
"mobile.components.select_server_view.msg_welcome": "Välkommen",
"mobile.components.select_server_view.msg_description": "En server är teamets kommunikationscentral som nås via en unik URL-adress",
"mobile.components.select_server_view.msg_description": "En server är teamets kommunikationscentral och nås via en unik URL-adress",
"mobile.components.select_server_view.msg_connect": "Låt oss ansluta till en server",
"mobile.components.select_server_view.displayName": "Visningsnamn",
"mobile.components.select_server_view.displayHelp": "Välj ett visningsnamn för din server",
@@ -993,5 +992,18 @@
"mobile.calls_recording_start_no_permissions": "Du har inte behörighet att starta en inspelning. Be samtalsvärden att starta en inspelning.",
"mobile.calls_recording_start_in_progress": "En inspelning pågår redan.",
"mobile.calls_host_rec_error_title": "Något gick fel med inspelningen",
"mobile.calls_host_rec_error": "Försök att spela in igen. Du kan också kontakta din systemadministratör för att få hjälp med felsökning."
"mobile.calls_host_rec_error": "Försök att spela in igen. Du kan också kontakta din systemadministratör för att få hjälp med felsökning.",
"notification.no_post": "Meddelandet hittades inte.",
"invite.summary.back": "Gå tillbaka",
"channel_notification_preferences.unmute_content": "Stäng av mjutad kanal",
"channel_notification_preferences.thread_replies": "Svar i trådar",
"channel_notification_preferences.reset_default": "Återställ till standardinställningen",
"channel_notification_preferences.notify_about": "Notifiera mig om...",
"channel_notification_preferences.notification.thread_replies": "Notifiera mig om svar i trådar som jag följer i denna kanal",
"channel_notification_preferences.notification.none": "Inget",
"channel_notification_preferences.notification.mention": "Endast omnämnanden och direktmeddelanden",
"channel_notification_preferences.notification.all": "Alla nya meddelanden",
"channel_notification_preferences.muted_title": "Kanalen är mjutad",
"channel_notification_preferences.muted_content": "Du kan ändra notifieringsinställningarna, men eftersom kanalen är mjutad kommer du inte få notifieringar.",
"channel_notification_preferences.default": "(standard)"
}

View File

@@ -619,7 +619,6 @@
"mobile.no_results_with_term.messages": "\"{term}\" için bir eşleşme bulunamadı",
"mobile.no_results_with_term.files": "\"{term}\" ile eşleşen bir dosya bulunamadı",
"mobile.no_results_with_term": "\"{term}\" için sonuç bulunamadı",
"mobile.login_options.sso_continue": "Şununla ilerleyin",
"mobile.login_options.separator_text": "ya da şununla oturum açın",
"mobile.login_options.select_option": "Aşağıdan bir oturum açma seçeneği seçin.",
"mobile.login_options.saml": "SAML",

View File

@@ -706,7 +706,6 @@
"notification_settings.mentions_replies": "",
"plus_menu.invite_people_to_team.title": "",
"mobile.write_storage_permission_denied_description": "",
"mobile.login_options.sso_continue": "",
"mobile.custom_status.clear_after.title": "",
"mobile.camera_photo_permission_denied_description": "",
"mobile.calls_unmute": "",

View File

@@ -706,7 +706,6 @@
"mobile.post_info.save": "",
"mobile.oauth.switch_to_browser.error_title": "",
"mobile.oauth.something_wrong.okButton": "",
"mobile.login_options.sso_continue": "",
"mobile.login_options.separator_text": "",
"mobile.login_options.select_option": "",
"mobile.login_options.openid": "",

View File

@@ -937,7 +937,6 @@
"mobile.no_results_with_term.messages": "",
"mobile.no_results_with_term.files": "",
"mobile.no_results_with_term": "",
"mobile.login_options.sso_continue": "",
"mobile.login_options.separator_text": "",
"mobile.login_options.select_option": "",
"mobile.login_options.saml": "",

View File

@@ -1,305 +0,0 @@
{
"ServiceSettings": {
"SiteURL": "http://localhost:8065",
"WebsocketURL": "",
"LicenseFileLocation": "",
"ListenAddress": ":8065",
"ConnectionSecurity": "",
"TLSCertFile": "",
"TLSKeyFile": "",
"TLSMinVer": "1.2",
"TLSStrictTransport": false,
"TLSStrictTransportMaxAge": 63072000,
"TLSOverwriteCiphers": [],
"UseLetsEncrypt": false,
"Forward80To443": false,
"TrustedProxyIPHeader": [],
"ReadTimeout": 300,
"WriteTimeout": 300,
"IdleTimeout": 300,
"MaximumLoginAttempts": 10,
"GoroutineHealthThreshold": -1,
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
"EnableIncomingWebhooks": true,
"EnableOutgoingWebhooks": true,
"EnableCommands": true,
"EnableOnlyAdminIntegrations": true,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
"EnableLinkPreviews": false,
"EnableTesting": false,
"EnableDeveloper": false,
"EnableOpenTracing": false,
"EnableSecurityFixAlert": true,
"EnableInsecureOutgoingConnections": false,
"AllowedUntrustedInternalConnections": "localhost",
"EnableMultifactorAuthentication": false,
"EnforceMultifactorAuthentication": false,
"EnableUserAccessTokens": false,
"AllowCorsFrom": "",
"CorsExposedHeaders": "",
"CorsAllowCredentials": false,
"CorsDebug": false,
"AllowCookiesForSubdomains": false,
"ExtendSessionLengthWithActivity": true,
"SessionLengthWebInDays": 30,
"SessionLengthMobileInDays": 30,
"SessionLengthSSOInDays": 30,
"SessionCacheInMinutes": 10,
"SessionIdleTimeoutInMinutes": 43200,
"WebsocketSecurePort": 443,
"WebsocketPort": 80,
"WebserverMode": "gzip",
"EnableCustomEmoji": false,
"EnableEmojiPicker": true,
"EnableGifPicker": false,
"GfycatApiKey": "2_KtH_W5",
"GfycatApiSecret": "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof",
"RestrictCustomEmojiCreation": "all",
"RestrictPostDelete": "all",
"AllowEditPost": "always",
"PostEditTimeLimit": -1,
"TimeBetweenUserTypingUpdatesMilliseconds": 5000,
"EnablePostSearch": true,
"MinimumHashtagLength": 3,
"EnableUserTypingMessages": true,
"EnableChannelViewedMessages": true,
"EnableUserStatuses": true,
"ExperimentalEnableAuthenticationTransfer": true,
"CloseUnusedDirectMessages": false,
"EnablePreviewFeatures": true,
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
"ExperimentalGroupUnreadChannels": "disabled",
"ExperimentalChannelOrganization": false,
"ExperimentalChannelSidebarOrganization": "disabled",
"EnableAPIChannelDeletion": true,
"EnableAPITeamDeletion": true,
"ExperimentalEnableHardenedMode": false,
"DisableLegacyMFA": true,
"ExperimentalStrictCSRFEnforcement": false,
"EnableEmailInvitations": true,
"DisableBotsWhenOwnerIsDeactivated": true,
"EnableBotAccountCreation": true,
"EnableSVGs": true,
"EnableLatex": true,
"EnableInlineLatex": true,
"CollapsedThreads": "always_on"
},
"TeamSettings": {
"SiteName": "Mattermost",
"MaxUsersPerTeam": 2000,
"EnableTeamCreation": true,
"EnableUserCreation": true,
"EnableOpenServer": true,
"EnableUserDeactivation": false,
"EnableCustomUserStatuses": true,
"RestrictCreationToDomains": "",
"EnableCustomBrand": false,
"CustomBrandText": "",
"CustomDescriptionText": "",
"RestrictDirectMessage": "any",
"RestrictTeamInvite": "all",
"RestrictPublicChannelManagement": "all",
"RestrictPrivateChannelManagement": "all",
"RestrictPublicChannelCreation": "all",
"RestrictPrivateChannelCreation": "all",
"RestrictPublicChannelDeletion": "all",
"RestrictPrivateChannelDeletion": "all",
"RestrictPrivateChannelManageMembers": "all",
"EnableXToLeaveChannelsFromLHS": false,
"UserStatusAwayTimeout": 300,
"MaxChannelsPerTeam": 2000,
"MaxNotificationsPerChannel": 1000,
"EnableConfirmNotificationsToChannel": true,
"TeammateNameDisplay": "username",
"ExperimentalViewArchivedChannels": true,
"ExperimentalEnableAutomaticReplies": true,
"ExperimentalHideTownSquareinLHS": false,
"ExperimentalTownSquareIsReadOnly": false,
"LockTeammateNameDisplay": false,
"ExperimentalPrimaryTeam": "",
"ExperimentalDefaultChannels": []
},
"ClientRequirements": {
"AndroidLatestVersion": "",
"AndroidMinVersion": "",
"DesktopLatestVersion": "",
"DesktopMinVersion": "",
"IosLatestVersion": "",
"IosMinVersion": ""
},
"LogSettings": {
"EnableConsole": true
},
"NotificationLogSettings": {
"EnableConsole": true
},
"PasswordSettings": {
"MinimumLength": 5,
"Lowercase": false,
"Number": false,
"Uppercase": false,
"Symbol": false,
"Enable": false
},
"FileSettings": {
"EnableFileAttachments": true,
"EnableMobileUpload": true,
"EnableMobileDownload": true,
"MaxFileSize": 52428800,
"DriverName": "local",
"Directory": "./data/",
"EnablePublicLink": false,
"PublicLinkSalt": ""
},
"EmailSettings": {
"EnableSignUpWithEmail": true,
"EnableSignInWithEmail": true,
"EnableSignInWithUsername": true,
"SendEmailNotifications": true,
"UseChannelInEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
"FeedbackEmail": "test@example.com",
"ReplyToAddress": "test@example.com",
"FeedbackOrganization": "",
"EnableSMTPAuth": false,
"SMTPUsername": "",
"SMTPPassword": "",
"SMTPServer": "localhost",
"SMTPPort": "10025",
"SMTPServerTimeout": 10,
"ConnectionSecurity": "",
"SendPushNotifications": true,
"PushNotificationServer": "https://push-test.mattermost.com",
"PushNotificationContents": "generic",
"EnableEmailBatching": false,
"EmailBatchingBufferSize": 256,
"EmailBatchingInterval": 30,
"EnablePreviewModeBanner": true,
"SkipServerCertificateVerification": false,
"EmailNotificationContentsType": "full",
"LoginButtonColor": "#0000",
"LoginButtonBorderColor": "#2389D7",
"LoginButtonTextColor": "#2389D7"
},
"RateLimitSettings": {
"Enable": false
},
"PrivacySettings": {
"ShowEmailAddress": true,
"ShowFullName": true
},
"SupportSettings": {
"TermsOfServiceLink": "https://mattermost.com/terms-of-use/",
"PrivacyPolicyLink": "https://mattermost.com/privacy-policy/",
"AboutLink": "https://mattermost.com/default-about/",
"HelpLink": "https://mattermost.com/default-help/",
"ReportAProblemLink": "https://mattermost.com/default-report-a-problem/",
"SupportEmail": "feedback@mattermost.com",
"CustomTermsOfServiceEnabled": false,
"CustomTermsOfServiceReAcceptancePeriod": 365,
"EnableAskCommunityLink": true
},
"AnnouncementSettings": {
"EnableBanner": false
},
"ThemeSettings": {
"EnableThemeSelection": true,
"DefaultTheme": "default",
"AllowCustomThemes": true,
"AllowedThemes": []
},
"GitLabSettings": {
"Enable": false
},
"GoogleSettings": {
"Enable": false
},
"Office365Settings": {
"Enable": false
},
"LdapSettings": {
"Enable": false
},
"ComplianceSettings": {
"Enable": false
},
"LocalizationSettings": {
"DefaultServerLocale": "en",
"DefaultClientLocale": "en",
"AvailableLocales": ""
},
"SamlSettings": {
"Enable": false
},
"NativeAppSettings": {
"AppDownloadLink": "https://mattermost.com/download/#mattermostApps",
"AndroidAppDownloadLink": "https://mattermost.com/mattermost-android-app/",
"IosAppDownloadLink": "https://mattermost.com/mattermost-ios-app/"
},
"ClusterSettings": {
"Enable": false
},
"MetricsSettings": {
"Enable": false
},
"ExperimentalSettings": {
"ClientSideCertEnable": false,
"ClientSideCertCheck": "secondary",
"LinkMetadataTimeoutMilliseconds": 5000,
"RestrictSystemAdmin": false,
"UseNewSAMLLibrary": false
},
"AnalyticsSettings": {
"MaxUsersForStatistics": 2500
},
"ElasticsearchSettings": {
"ConnectionUrl": "http://localhost:9200",
"Username": "elastic",
"Password": "changeme",
"EnableIndexing": false,
"EnableSearching": false,
"EnableAutocomplete": false,
"Sniff": true
},
"DataRetentionSettings": {
"EnableMessageDeletion": false,
"EnableFileDeletion": false,
"MessageRetentionDays": 365,
"FileRetentionDays": 365,
"DeletionJobStartTime": "02:00"
},
"MessageExportSettings": {
"EnableExport": false
},
"JobSettings": {
"RunJobs": true,
"RunScheduler": true
},
"PluginSettings": {
"Enable": false,
"PluginStates": {
"com.mattermost.calls": {
"Enable": true
}
}
},
"DisplaySettings": {
"CustomUrlSchemes": [],
"ExperimentalTimezone": true
},
"GuestAccountsSettings": {
"Enable": true,
"AllowEmailAccounts": true,
"EnforceMultifactorAuthentication": false,
"RestrictCreationToDomains": ""
},
"ImageProxySettings": {
"Enable": true,
"ImageProxyType": "local",
"RemoteImageProxyURL": "",
"RemoteImageProxyOptions": ""
}
}

View File

@@ -3,13 +3,10 @@
import path from 'path';
import {ldapPort, ldapServer} from '@support/test_config';
import merge from 'deepmerge';
import jestExpect from 'expect';
import client from './client';
import {apiUploadFile, getResponseFromError} from './common';
import defaultServerConfig from './default_config.json';
// ****************************************************************
// System
@@ -152,29 +149,6 @@ export const apiRequireSMTPServer = async (baseUrl: string) => {
jestExpect(status).toEqual(200);
};
/**
* Update configuration.
* See https://api.mattermost.com/#operation/UpdateConfig
* @param {string} baseUrl - the base server URL
* @param {Object} newConfig - specific config to update
* @return {Object} returns {config} on success or {error, status} on error
*/
export const apiUpdateConfig = async (baseUrl: string, newConfig: any = {}): Promise<any> => {
try {
const {config: currentConfig} = await apiGetConfig(baseUrl);
const config = merge.all([currentConfig, getDefaultConfig(baseUrl), newConfig]);
const response = await client.put(
`${baseUrl}/api/v4/config`,
config,
);
return {config: response.data};
} catch (err) {
return getResponseFromError(err);
}
};
/**
* Upload server license with file expected at "/detox/e2e/support/fixtures/mattermost-license.txt"
* See https://api.mattermost.com/#operation/UploadLicenseFile
@@ -209,18 +183,6 @@ export const getClientLicense = async (baseUrl: string): Promise<any> => {
return {license: out.license};
};
export const getDefaultConfig = (siteUrl: string) => {
const fromEnv = {
LdapSettings: {
LdapServer: ldapServer,
LdapPort: ldapPort,
},
ServiceSettings: {SiteURL: siteUrl},
};
return merge(defaultServerConfig, fromEnv);
};
export const System = {
apiCheckSystemHealth,
apiEmailTest,
@@ -230,10 +192,8 @@ export const System = {
apiRequireLicense,
apiRequireLicenseForFeature,
apiRequireSMTPServer,
apiUpdateConfig,
apiUploadLicense,
getClientLicense,
getDefaultConfig,
};
export default System;

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {isAndroid, timeouts, wait} from '@support/utils';
import {isAndroid, isIos, timeouts, wait} from '@support/utils';
import {expect} from 'detox';
class ServerScreen {
@@ -47,7 +47,12 @@ class ServerScreen {
await this.serverUrlInput.replaceText(serverUrl);
await this.serverUrlInput.tapReturnKey();
await this.serverDisplayNameInput.replaceText(serverDisplayName);
await this.tapConnectButton();
if (isAndroid()) {
await this.serverDisplayNameInput.tapReturnKey();
}
if (isIos()) {
await this.tapConnectButton();
}
};
close = async () => {
@@ -56,10 +61,6 @@ class ServerScreen {
};
tapConnectButton = async () => {
if (isAndroid()) {
await device.pressBack();
await wait(timeouts.ONE_SEC);
}
await this.connectButton.tap();
await wait(timeouts.ONE_SEC);
};

View File

@@ -8,7 +8,6 @@ beforeAll(async () => {
// Login as sysadmin and reset server configuration
await System.apiCheckSystemHealth(siteOneUrl);
await User.apiAdminLogin(siteOneUrl);
await System.apiUpdateConfig(siteOneUrl);
await Plugin.apiDisableNonPrepackagedPlugins(siteOneUrl);
await device.launchApp({

View File

@@ -25,7 +25,7 @@ import {
ServerListScreen,
ServerScreen,
} from '@support/ui/screen';
import {timeouts} from '@support/utils';
import {isAndroid, isIos, timeouts} from '@support/utils';
import {expect} from 'detox';
describe('Smoke Test - Server Login', () => {
@@ -79,11 +79,19 @@ describe('Smoke Test - Server Login', () => {
// # Go back to first server, open server list screen, swipe left on second server and tap on logout option
await ServerListScreen.open();
await ServerListScreen.serverListScreen.swipe('up');
if (isIos()) {
await ServerListScreen.serverListTitle.swipe('up');
} else if (isAndroid()) {
await ServerListScreen.serverListTitle.swipe('up', 'fast', 0.1, 0.5, 0.3);
}
await waitFor(ServerListScreen.getServerItemInactive(serverOneDisplayName)).toBeVisible().withTimeout(timeouts.TEN_SEC);
await ServerListScreen.getServerItemInactive(serverOneDisplayName).tap();
await ServerListScreen.open();
await ServerListScreen.serverListScreen.swipe('up');
if (isIos()) {
await ServerListScreen.serverListTitle.swipe('up');
} else if (isAndroid()) {
await ServerListScreen.serverListTitle.swipe('up', 'fast', 0.1, 0.5, 0.3);
}
await waitFor(ServerListScreen.getServerItemInactive(serverTwoDisplayName)).toBeVisible().withTimeout(timeouts.TEN_SEC);
await ServerListScreen.getServerItemInactive(serverTwoDisplayName).swipe('left');
await ServerListScreen.getServerItemLogoutOption(serverTwoDisplayName).tap();

View File

@@ -79,7 +79,7 @@ describe('Teams - Invite', () => {
await expect(Invite.teamIcon).toBeVisible();
// * Verify default Selection
await expect(Invite.screenSelection).toBeVisible();
await waitFor(Invite.screenSelection).toBeVisible().withTimeout(timeouts.TWO_SEC);
// * Verify Server data
await expect(Invite.serverDisplayName).toHaveText(serverOneDisplayName);
@@ -93,10 +93,9 @@ describe('Teams - Invite', () => {
});
it('MM-T5221 - should be able to share a URL invite to the team', async () => {
// # Tap on Share link
await Invite.shareLinkButton.tap();
if (isIos()) {
// # Tap on Share link
await Invite.shareLinkButton.tap();
const dialog = systemDialog(`Join the ${testTeam.display_name} team`);
// * Verify share dialog is open
@@ -104,28 +103,28 @@ describe('Teams - Invite', () => {
// # Close share dialog
await dialog.swipe('down');
}
} // no support for Android system dialogs by detox yet. See https://github.com/wix/Detox/issues/3227
});
it('MM-T5361 - should show no results item in search list', async () => {
const noUser = 'qwertyuiop';
// # Search for a non existent user
// # Search for a non-existent user
await Invite.searchBarInput.replaceText(noUser);
// * Validate no results item in search list
await expect(Invite.getSearchListNoResults(noUser)).toBeVisible();
await waitFor(Invite.getSearchListNoResults(noUser)).toBeVisible().withTimeout(timeouts.TWO_SEC);
await expect(Invite.getSearchListNoResultsText(noUser)).toHaveText(noUser);
});
it('MM-T5362 - should be able to send email invite', async () => {
const noUserEmailFormat = 'qwerty@ui.op';
// # Search for a non existent user with email format
// # Search for a non-existent user with email format
await Invite.searchBarInput.replaceText(noUserEmailFormat);
// * Validate email invite item in search list
await expect(Invite.getSearchListTextItem(noUserEmailFormat)).toBeVisible();
await waitFor(Invite.getSearchListTextItem(noUserEmailFormat)).toBeVisible().withTimeout(timeouts.TWO_SEC);
await expect(Invite.getSearchListTextItemText(noUserEmailFormat)).toHaveText(noUserEmailFormat);
// # Select email invite item
@@ -139,7 +138,7 @@ describe('Teams - Invite', () => {
await Invite.sendButton.tap();
// * Validate summary report sent
await expect(Invite.screenSummary).toBeVisible();
await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC);
await expect(Invite.getSummaryReportSent()).toBeVisible();
await expect(Invite.getSummaryReportNotSent()).not.toExist();
await expect(Invite.getSummaryReportTextItem(noUserEmailFormat)).toBeVisible();
@@ -149,11 +148,11 @@ describe('Teams - Invite', () => {
it('MM-T5363 - should be able to send user invite', async () => {
const username = ` @${testUser1.username}`;
// # Search for a existent user
// # Search for an existent user
await Invite.searchBarInput.replaceText(testUser1.username);
// * Validate user item in search list
await expect(Invite.getSearchListUserItem(testUser1.id)).toBeVisible();
await waitFor(Invite.getSearchListUserItem(testUser1.id)).toBeVisible().withTimeout(timeouts.TWO_SEC);
await expect(Invite.getSearchListUserItemText(testUser1.id)).toHaveText(username);
// # Select user item
@@ -167,7 +166,7 @@ describe('Teams - Invite', () => {
await Invite.sendButton.tap();
// * Validate summary report sent
await expect(Invite.screenSummary).toBeVisible();
await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC);
await expect(Invite.getSummaryReportSent()).toBeVisible();
await expect(Invite.getSummaryReportNotSent()).not.toExist();
await expect(Invite.getSummaryReportUserItem(testUser1.id)).toBeVisible();
@@ -177,11 +176,11 @@ describe('Teams - Invite', () => {
it('MM-T5364 - should not be able to send user invite to user already in team', async () => {
const username = ` @${testUser1.username}`;
// # Search for a existent user already in team
// # Search for an existent user already in team
await Invite.searchBarInput.replaceText(testUser1.username);
// * Validate user item in search list
await expect(Invite.getSearchListUserItem(testUser1.id)).toBeVisible();
await waitFor(Invite.getSearchListUserItem(testUser1.id)).toBeVisible().withTimeout(timeouts.TWO_SEC);
// # Select user item
await Invite.getSearchListUserItem(testUser1.id).tap();
@@ -206,11 +205,11 @@ describe('Teams - Invite', () => {
const username1 = ` @${testUser1.username}`;
const username2 = ` @${testUser2.username}`;
// # Search for a existent user
// # Search for an existent user
await Invite.searchBarInput.replaceText(testUser2.username);
// * Validate user item in search list
await expect(Invite.getSearchListUserItem(testUser2.id)).toBeVisible();
await waitFor(Invite.getSearchListUserItem(testUser2.id)).toBeVisible().withTimeout(timeouts.TEN_SEC);
// # Select user item
await Invite.getSearchListUserItem(testUser2.id).tap();
@@ -234,7 +233,7 @@ describe('Teams - Invite', () => {
await Invite.sendButton.tap();
// * Validate summary
await expect(Invite.screenSummary).toBeVisible();
await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC);
// * Validate summary report not sent
await expect(Invite.getSummaryReportNotSent()).toBeVisible();
@@ -242,7 +241,7 @@ describe('Teams - Invite', () => {
await expect(Invite.getSummaryReportUserItemText(testUser1.id)).toHaveText(username1);
// * Validate summary report sent
await expect(Invite.getSummaryReportSent()).toBeVisible();
await waitFor(Invite.getSummaryReportSent()).toBeVisible().withTimeout(timeouts.TEN_SEC);
await expect(Invite.getSummaryReportUserItem(testUser2.id)).toBeVisible();
await expect(Invite.getSummaryReportUserItemText(testUser2.id)).toHaveText(username2);
});

View File

@@ -1,23 +1,23 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
CFPropertyList (3.0.6)
rexml
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.695.0)
aws-sdk-core (3.169.0)
aws-partitions (1.721.0)
aws-sdk-core (3.170.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.62.0)
aws-sdk-kms (1.63.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.118.0)
aws-sdk-s3 (1.119.1)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
@@ -36,7 +36,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.97.1)
excon (0.99.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.211.0)
fastlane (2.212.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -109,11 +109,11 @@ GEM
fastlane-plugin-android_change_string_app_name (0.1.1)
nokogiri
fastlane-plugin-find_replace_string (0.1.0)
fastlane-plugin-versioning_android (0.1.0)
fastlane-plugin-versioning_android (0.1.1)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.32.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-core (0.9.5)
google-apis-androidpublisher_v3 (0.35.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -122,10 +122,10 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.16.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-playcustomapp_v1 (0.12.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
@@ -133,7 +133,7 @@ GEM
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
@@ -155,7 +155,7 @@ GEM
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.6.0)
jwt (2.7.0)
memoist (0.16.2)
mini_magick (4.12.0)
mini_mime (1.1.2)
@@ -164,12 +164,12 @@ GEM
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
nokogiri (1.14.0)
nokogiri (1.14.2)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
plist (3.7.0)
public_suffix (5.0.1)
racc (1.6.2)
rake (13.0.6)
@@ -188,7 +188,7 @@ GEM
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
simctl (1.6.10)
CFPropertyList
naturally
slack-notifier (2.3.2)
@@ -205,7 +205,7 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)

View File

@@ -1128,7 +1128,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 459;
CURRENT_PROJECT_VERSION = 461;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -1172,7 +1172,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 459;
CURRENT_PROJECT_VERSION = 461;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -1315,7 +1315,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 459;
CURRENT_PROJECT_VERSION = 461;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;
@@ -1366,7 +1366,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 459;
CURRENT_PROJECT_VERSION = 461;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;

View File

@@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>459</string>
<string>461</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -21,7 +21,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>
<key>CFBundleVersion</key>
<string>459</string>
<string>461</string>
<key>UIAppFonts</key>
<array>
<string>OpenSans-Bold.ttf</string>

View File

@@ -21,7 +21,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>
<key>CFBundleVersion</key>
<string>459</string>
<string>461</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

View File

@@ -83,8 +83,9 @@ class NotificationService: UNNotificationServiceExtension {
let isCRTEnabled = notification.userInfo["is_crt_enabled"] as? Bool ?? false
let rootId = notification.userInfo["root_id"] as? String ?? ""
let channelName = notification.userInfo["channel_name"] as? String ?? ""
let message = (notification.userInfo["message"] as? String ?? "")
let senderName = notification.userInfo["sender_name"] as? String
let channelName = notification.userInfo["channel_name"] as? String
var message = (notification.userInfo["message"] as? String ?? "")
let overrideUsername = notification.userInfo["override_username"] as? String
let senderId = notification.userInfo["sender_id"] as? String
let senderIdentifier = overrideUsername ?? senderId
@@ -94,11 +95,18 @@ class NotificationService: UNNotificationServiceExtension {
if isCRTEnabled && !rootId.isEmpty {
conversationId = rootId
}
if channelName == nil && message == "",
let senderName = senderName,
let body = bestAttemptContent?.body {
message = body.replacingOccurrences(of: "\(senderName) ", with: "")
bestAttemptContent?.body = message
}
let handle = INPersonHandle(value: senderIdentifier, type: .unknown)
let sender = INPerson(personHandle: handle,
nameComponents: nil,
displayName: channelName,
displayName: channelName ?? senderName,
image: avatar,
contactIdentifier: nil,
customIdentifier: nil)

View File

@@ -54,7 +54,7 @@ target 'Mattermost' do
pod 'simdjson', path: '../node_modules/@nozbe/simdjson'
# TODO: Remove this once upstream PR https://github.com/daltoniam/Starscream/pull/871 is merged
pod 'Starscream', :git => 'https://github.com/mattermost/Starscream.git', :commit => '2770c931b2758f26e29b937d547a23122e9c6583'
pod 'Starscream', :git => 'https://github.com/mattermost/Starscream.git', :commit => '9575b6781d1262247096af73617ae3acb2b139a0'
end

View File

@@ -709,7 +709,7 @@ DEPENDENCIES:
- RNSVG (from `../node_modules/react-native-svg`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- "simdjson (from `../node_modules/@nozbe/simdjson`)"
- Starscream (from `https://github.com/mattermost/Starscream.git`, commit `2770c931b2758f26e29b937d547a23122e9c6583`)
- Starscream (from `https://github.com/mattermost/Starscream.git`, commit `9575b6781d1262247096af73617ae3acb2b139a0`)
- "WatermelonDB (from `../node_modules/@nozbe/watermelondb`)"
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -908,7 +908,7 @@ EXTERNAL SOURCES:
simdjson:
:path: "../node_modules/@nozbe/simdjson"
Starscream:
:commit: 2770c931b2758f26e29b937d547a23122e9c6583
:commit: 9575b6781d1262247096af73617ae3acb2b139a0
:git: https://github.com/mattermost/Starscream.git
WatermelonDB:
:path: "../node_modules/@nozbe/watermelondb"
@@ -917,7 +917,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
Starscream:
:commit: 2770c931b2758f26e29b937d547a23122e9c6583
:commit: 9575b6781d1262247096af73617ae3acb2b139a0
:git: https://github.com/mattermost/Starscream.git
SPEC CHECKSUMS:
@@ -1033,6 +1033,6 @@ SPEC CHECKSUMS:
Yoga: 5ed1699acbba8863755998a4245daa200ff3817b
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 9f76739ed16bbdc0f4b1049ecb366fb5d23a0f3a
PODFILE CHECKSUM: 831b649321a4d14a86a074af619aa779ebc048c4
COCOAPODS: 1.11.3

14
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@gorhom/bottom-sheet": "4.4.5",
"@mattermost/compass-icons": "0.1.35",
"@mattermost/react-native-emm": "1.3.5",
"@mattermost/react-native-network-client": "1.3.1",
"@mattermost/react-native-network-client": "1.3.2",
"@mattermost/react-native-paste-input": "0.6.2",
"@mattermost/react-native-turbo-log": "0.2.3",
"@mattermost/react-native-turbo-mailer": "0.2.4",
@@ -3241,9 +3241,9 @@
}
},
"node_modules/@mattermost/react-native-network-client": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.1.tgz",
"integrity": "sha512-DtwVLV/NUE6MkXOlVZG+4QJXou6nHMdmsxnP1+RqhOeSw5jJlQvxmQgxzxvxLpaWOag+wgB1zpDulGNbr/Cz6Q==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.2.tgz",
"integrity": "sha512-3GFNzMXZWlIXXDYQLIJlKRf+HUZKP0F7mpZ1rSTgoTmUeFdqde4uRiU/L96COg34rAdeFRFrgpk0DxEnT7NiVg==",
"dependencies": {
"validator": "13.9.0",
"zod": "3.20.6"
@@ -24297,9 +24297,9 @@
"requires": {}
},
"@mattermost/react-native-network-client": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.1.tgz",
"integrity": "sha512-DtwVLV/NUE6MkXOlVZG+4QJXou6nHMdmsxnP1+RqhOeSw5jJlQvxmQgxzxvxLpaWOag+wgB1zpDulGNbr/Cz6Q==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.2.tgz",
"integrity": "sha512-3GFNzMXZWlIXXDYQLIJlKRf+HUZKP0F7mpZ1rSTgoTmUeFdqde4uRiU/L96COg34rAdeFRFrgpk0DxEnT7NiVg==",
"requires": {
"validator": "13.9.0",
"zod": "3.20.6"

View File

@@ -16,7 +16,7 @@
"@gorhom/bottom-sheet": "4.4.5",
"@mattermost/compass-icons": "0.1.35",
"@mattermost/react-native-emm": "1.3.5",
"@mattermost/react-native-network-client": "1.3.1",
"@mattermost/react-native-network-client": "1.3.2",
"@mattermost/react-native-paste-input": "0.6.2",
"@mattermost/react-native-turbo-log": "0.2.3",
"@mattermost/react-native-turbo-mailer": "0.2.4",

View File

@@ -25,7 +25,7 @@ declare class CategoryModel extends Model {
displayName: string;
/** type : The type of category */
type: string;
type: CategoryType;
/** sort_order : The sort order for this category */
sortOrder: number;