forked from Ivasoft/mattermost-mobile
Compare commits
31 Commits
release-2.
...
test1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| b960c2f036 | |||
| 2aa1265762 | |||
| 565c19ca64 | |||
| b4acbef5f5 | |||
| 0a766bfb65 | |||
| b7cc411c3b | |||
| 04b35959a3 | |||
| e639e69d46 | |||
|
|
07aa748cd7 | ||
|
|
01b5fc0f9e | ||
|
|
9b932c401a | ||
|
|
5ccf45622d | ||
|
|
279b601b2a | ||
|
|
049e763204 | ||
|
|
d6e7889afc | ||
|
|
46df588a6a | ||
|
|
a92574cdf3 | ||
|
|
fdd0bed294 | ||
|
|
72c5271598 | ||
|
|
6454a19a37 | ||
|
|
71805ed79d | ||
|
|
fe916ec740 | ||
|
|
3c046ae39c | ||
|
|
a804a7331f | ||
|
|
903aaf62b5 | ||
|
|
ef4fb9c8e0 | ||
|
|
af07f511f7 | ||
|
|
abd388986f | ||
|
|
0938045b7d | ||
|
|
9347e736e5 | ||
|
|
276bcba956 |
31
.drone.yml
Normal file
31
.drone.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: permissions
|
||||
image: alpine/git
|
||||
commands:
|
||||
- chmod -R 777 .
|
||||
|
||||
#- name: build
|
||||
# image: cimg/android:2022.09.2-node
|
||||
# environment:
|
||||
# CIRCLECI: true
|
||||
# NODE_OPTIONS: --max_old_space_size=12000
|
||||
# NODE_ENV: production
|
||||
# BABEL_ENV: production
|
||||
# MATTERMOST_RELEASE_STORE_FILE: /root/mattermost.keystore
|
||||
# MATTERMOST_RELEASE_KEY_ALIAS: mattermost-google-key
|
||||
# MATTERMOST_RELEASE_PASSWORD: 123456
|
||||
# commands:
|
||||
# - 'npm run build:android'
|
||||
|
||||
- name: gitea_release
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: drone_release
|
||||
base_url: https://git.ivasoft.cz
|
||||
files: package.json
|
||||
when:
|
||||
event: tag
|
||||
@@ -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'
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}),
|
||||
|
||||
@@ -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]}>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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},
|
||||
);
|
||||
|
||||
@@ -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/>);
|
||||
}
|
||||
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]) => {
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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
1
assets/base/i18n/ar.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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ś ",
|
||||
|
||||
@@ -433,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": "Выберите отображаемое имя для вашего сервера",
|
||||
@@ -992,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": "(по умолчанию)"
|
||||
}
|
||||
|
||||
@@ -992,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)"
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -82,7 +82,6 @@ describe('Channels - Channel List', () => {
|
||||
// * Verify on first channel
|
||||
await ChannelScreen.toBeVisible();
|
||||
await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name);
|
||||
await expect(ChannelScreen.introDisplayName).toHaveText(testChannel.display_name);
|
||||
|
||||
// # Go back to channel list screen and tap on a second channel
|
||||
await ChannelScreen.back();
|
||||
@@ -92,7 +91,15 @@ describe('Channels - Channel List', () => {
|
||||
// * Verify on second channel
|
||||
await ChannelScreen.toBeVisible();
|
||||
await expect(ChannelScreen.headerTitle).toHaveText('Off-Topic');
|
||||
await expect(ChannelScreen.introDisplayName).toHaveText('Off-Topic');
|
||||
|
||||
// # Go back to channel list screen and tap on a third channel
|
||||
await ChannelScreen.back();
|
||||
await ChannelListScreen.toBeVisible();
|
||||
await ChannelListScreen.getChannelItemDisplayName(channelsCategory, townSquareChannelName).tap();
|
||||
|
||||
// * Verify on third channel
|
||||
await ChannelScreen.toBeVisible();
|
||||
await expect(ChannelScreen.headerTitle).toHaveText('Town Square');
|
||||
|
||||
// # Go back to channel list screen
|
||||
await ChannelScreen.back();
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>459</string>
|
||||
<string>461</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
14
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user