Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94d4a23147 | ||
|
|
10b2e0f21a | ||
|
|
01e3d62d11 | ||
|
|
1f90540d95 | ||
|
|
9ee97a659d | ||
|
|
424e0e0cd8 | ||
|
|
10be71b536 | ||
|
|
ee33cb414e | ||
|
|
bbe2977fd7 |
20 changed files with 391 additions and 186 deletions
32
NOTICE.txt
32
NOTICE.txt
|
|
@ -667,6 +667,38 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @voximplant/react-native-foreground-service
|
||||
|
||||
This product contains a modified version of '@voximplant/react-native-foreground-service' by Voximplant.
|
||||
|
||||
A foreground service performs some operation that is noticeable to the user.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/voximplant/react-native-foreground-service
|
||||
|
||||
* LICENSE: MIT License
|
||||
|
||||
Copyright (c) 2019 Zingaya, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@ android {
|
|||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 489
|
||||
versionName "2.9.0"
|
||||
versionCode 496
|
||||
versionName "2.10.1"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,14 +94,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
|||
}
|
||||
}
|
||||
|
||||
if (currentTeamId === teamId) {
|
||||
const models = await prepareCommonSystemValues(operator, {
|
||||
currentChannelId: channel.id,
|
||||
});
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models, 'switchToThread');
|
||||
}
|
||||
} else {
|
||||
if (currentTeamId !== teamId) {
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
modelPromises.push(addTeamToTeamHistory(operator, teamId, true));
|
||||
const commonValues: PrepareCommonSystemValuesArgs = {
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export const handleGotoLocation = async (serverUrl: string, intl: IntlShape, loc
|
|||
const match = matchDeepLink(location, serverUrl, config?.SiteURL);
|
||||
|
||||
if (match) {
|
||||
handleDeepLink(match, intl, location);
|
||||
handleDeepLink(match.url, intl, location);
|
||||
} else {
|
||||
const {formatMessage} = intl;
|
||||
const onError = () => Alert.alert(
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
|||
const match = matchDeepLink(url, serverUrl, siteURL);
|
||||
|
||||
if (match) {
|
||||
const {error} = await handleDeepLink(match, intl);
|
||||
const {error} = await handleDeepLink(match.url, intl);
|
||||
if (error) {
|
||||
tryOpenURL(match, onError);
|
||||
tryOpenURL(match.url, onError);
|
||||
}
|
||||
} else {
|
||||
tryOpenURL(url, onError);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {Modal, StyleSheet, useWindowDimensions, View} from 'react-native';
|
||||
import {Modal, useWindowDimensions} from 'react-native';
|
||||
|
||||
import HighlightItem from './item';
|
||||
|
||||
|
|
@ -11,11 +11,10 @@ type Props = {
|
|||
itemBounds: TutorialItemBounds;
|
||||
itemBorderRadius?: number;
|
||||
onDismiss: () => void;
|
||||
onLayout: () => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
|
||||
const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, onLayout, onShow}: Props) => {
|
||||
const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, onShow}: Props) => {
|
||||
const {width, height} = useWindowDimensions();
|
||||
|
||||
const handleShowTutorial = useCallback(() => {
|
||||
|
|
@ -26,19 +25,13 @@ const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, o
|
|||
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
visible={itemBounds.endX > 0}
|
||||
transparent={true}
|
||||
animationType='fade'
|
||||
onDismiss={onDismiss}
|
||||
onRequestClose={onDismiss}
|
||||
testID='tutorial_highlight'
|
||||
>
|
||||
<View
|
||||
style={StyleSheet.absoluteFill}
|
||||
pointerEvents='box-none'
|
||||
onLayout={onLayout}
|
||||
/>
|
||||
{itemBounds.endX > 0 &&
|
||||
<HighlightItem
|
||||
borderRadius={itemBorderRadius}
|
||||
itemBounds={itemBounds}
|
||||
|
|
@ -47,7 +40,6 @@ const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, o
|
|||
width={width}
|
||||
onLayout={handleShowTutorial}
|
||||
/>
|
||||
}
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type Props = {
|
|||
rightDecorator?: React.ReactNode;
|
||||
onUserPress?: (user: UserProfile | UserModel) => void;
|
||||
onUserLongPress?: (user: UserProfile | UserModel) => void;
|
||||
onLayout?: () => void;
|
||||
disabled?: boolean;
|
||||
viewRef?: React.LegacyRef<View>;
|
||||
padding?: number;
|
||||
|
|
@ -102,6 +103,7 @@ const UserItem = ({
|
|||
locale,
|
||||
teammateNameDisplay,
|
||||
rightDecorator,
|
||||
onLayout,
|
||||
onUserPress,
|
||||
onUserLongPress,
|
||||
disabled = false,
|
||||
|
|
@ -159,6 +161,7 @@ const UserItem = ({
|
|||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
disabled={!(onUserPress || onUserLongPress)}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<View
|
||||
ref={viewRef}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ exports[`components/channel_list_row should show no results 1`] = `
|
|||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onLayout={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
|
|
@ -424,6 +425,7 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
|
|||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onLayout={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
|
|
@ -551,79 +553,8 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
|
|||
onRequestClose={[Function]}
|
||||
testID="tutorial_highlight"
|
||||
transparent={true}
|
||||
visible={true}
|
||||
>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
{
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="tutorial_swipe_left"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 8,
|
||||
"height": 161,
|
||||
"padding": 16,
|
||||
"width": 247,
|
||||
},
|
||||
{
|
||||
"top": -74,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<long_press_illustration.svg />
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "#3f4350",
|
||||
"fontFamily": "Metropolis-SemiBold",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 24,
|
||||
"marginTop": 8,
|
||||
"paddingHorizontal": 12,
|
||||
"textAlign": "center",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Long-press on an item to view a user's profile
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
visible={false}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
|
|
@ -790,6 +721,7 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
|
|||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onLayout={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
|
|
@ -1108,6 +1040,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
|
|||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onLayout={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
|
|
@ -1299,6 +1232,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
|
|||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onLayout={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
|
|
|
|||
|
|
@ -155,8 +155,10 @@ function UserListRow({
|
|||
}, [isChannelAdmin, showManageMode, theme]);
|
||||
|
||||
const onLayout = useCallback(() => {
|
||||
startTutorial();
|
||||
}, []);
|
||||
if (showTutorial) {
|
||||
startTutorial();
|
||||
}
|
||||
}, [showTutorial]);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
if (!selectable && !selected) {
|
||||
|
|
@ -190,17 +192,19 @@ function UserListRow({
|
|||
viewRef={viewRef}
|
||||
padding={20}
|
||||
includeMargin={includeMargin}
|
||||
onLayout={onLayout}
|
||||
/>
|
||||
{showTutorial &&
|
||||
<TutorialHighlight
|
||||
itemBounds={itemBounds}
|
||||
onDismiss={handleDismissTutorial}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
{Boolean(itemBounds.endX) &&
|
||||
<TutorialLongPress
|
||||
message={formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}
|
||||
style={isTablet ? style.tutorialTablet : style.tutorial}
|
||||
/>
|
||||
}
|
||||
</TutorialHighlight>
|
||||
}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -213,9 +213,11 @@ const ServerItem = ({
|
|||
};
|
||||
|
||||
const onLayout = useCallback(() => {
|
||||
swipeable.current?.close();
|
||||
startTutorial();
|
||||
}, []);
|
||||
if (showTutorial) {
|
||||
swipeable.current?.close();
|
||||
startTutorial();
|
||||
}
|
||||
}, [showTutorial]);
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
const style: StyleProp<ViewStyle> = [styles.container];
|
||||
|
|
@ -390,6 +392,7 @@ const ServerItem = ({
|
|||
style={containerStyle}
|
||||
ref={viewRef}
|
||||
testID={serverItemTestId}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<RectButton
|
||||
onPress={onServerPressed}
|
||||
|
|
@ -474,13 +477,14 @@ const ServerItem = ({
|
|||
itemBounds={itemBounds}
|
||||
onDismiss={handleDismissTutorial}
|
||||
onShow={handleShowTutorial}
|
||||
onLayout={onLayout}
|
||||
itemBorderRadius={8}
|
||||
>
|
||||
{Boolean(itemBounds.endX) &&
|
||||
<TutorialSwipeLeft
|
||||
message={intl.formatMessage({id: 'server.tutorial.swipe', defaultMessage: 'Swipe left on a server to see more actions'})}
|
||||
style={isTablet ? styles.tutorialTablet : styles.tutorial}
|
||||
/>
|
||||
}
|
||||
</TutorialHighlight>
|
||||
}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {match} from 'path-to-regexp';
|
||||
import {createIntl, type IntlShape} from 'react-intl';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import {makeDirectChannel, switchToChannelByName} from '@actions/remote/channel';
|
||||
import {showPermalink} from '@actions/remote/permalink';
|
||||
import {fetchUsersByUsernames} from '@actions/remote/user';
|
||||
import DeepLinkType from '@app/constants/deep_linking';
|
||||
import {DeepLink, Launch, Screens} from '@constants';
|
||||
import {getDefaultThemeByAppearance} from '@context/theme';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
|
@ -21,7 +23,14 @@ import {alertErrorWithFallback, errorBadChannel, errorUnkownUser} from '@utils/d
|
|||
import {logError} from '@utils/log';
|
||||
import {escapeRegex} from '@utils/markdown';
|
||||
import {addNewServer} from '@utils/server';
|
||||
import {removeProtocol} from '@utils/url';
|
||||
import {
|
||||
TEAM_NAME_PATH_PATTERN,
|
||||
IDENTIFIER_PATH_PATTERN,
|
||||
ID_PATH_PATTERN,
|
||||
PLUGIN_ID_PATH_PATTERN,
|
||||
} from '@utils/url/path';
|
||||
|
||||
import {removeProtocol} from '../url';
|
||||
|
||||
import type {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkWithData, LaunchProps} from '@typings/launch';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
|
@ -86,7 +95,7 @@ export async function handleDeepLink(deepLinkUrl: string, intlShape?: IntlShape,
|
|||
}
|
||||
case DeepLink.GroupMessage: {
|
||||
const deepLinkData = parsed.data as DeepLinkGM;
|
||||
switchToChannelByName(existingServerUrl, deepLinkData.channelId, deepLinkData.teamName, errorBadChannel, intl);
|
||||
switchToChannelByName(existingServerUrl, deepLinkData.channelName, deepLinkData.teamName, errorBadChannel, intl);
|
||||
break;
|
||||
}
|
||||
case DeepLink.Permalink: {
|
||||
|
|
@ -114,80 +123,89 @@ export async function handleDeepLink(deepLinkUrl: string, intlShape?: IntlShape,
|
|||
}
|
||||
}
|
||||
|
||||
type ChannelPathParams = {
|
||||
hostname: string;
|
||||
serverUrl: string;
|
||||
teamName: string;
|
||||
path: 'channels' | 'messages';
|
||||
identifier: string;
|
||||
};
|
||||
|
||||
const CHANNEL_PATH = `:serverUrl(.*)/:teamName(${TEAM_NAME_PATH_PATTERN})/:path(channels|messages)/:identifier(${IDENTIFIER_PATH_PATTERN})`;
|
||||
export const matchChannelDeeplink = match<ChannelPathParams>(CHANNEL_PATH);
|
||||
|
||||
type PermalinkPathParams = {
|
||||
serverUrl: string;
|
||||
teamName: string;
|
||||
postId: string;
|
||||
};
|
||||
const PERMALINK_PATH = `:serverUrl(.*)/:teamName(${TEAM_NAME_PATH_PATTERN})/pl/:postId(${ID_PATH_PATTERN})`;
|
||||
export const matchPermalinkDeeplink = match<PermalinkPathParams>(PERMALINK_PATH);
|
||||
|
||||
export function parseDeepLink(deepLinkUrl: string): DeepLinkWithData {
|
||||
try {
|
||||
const url = removeProtocol(decodeURIComponent(deepLinkUrl));
|
||||
const url = removeProtocol(deepLinkUrl);
|
||||
|
||||
if (url.includes('../') || url.includes('/..')) {
|
||||
return {type: DeepLink.Invalid, url: deepLinkUrl};
|
||||
const channelMatch = matchChannelDeeplink(url);
|
||||
if (channelMatch) {
|
||||
const {params: {serverUrl, teamName, path, identifier}} = channelMatch;
|
||||
|
||||
if (path === 'channels') {
|
||||
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl, teamName, channelName: identifier}};
|
||||
}
|
||||
|
||||
if (path === 'messages') {
|
||||
if (identifier.startsWith('@')) {
|
||||
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl, teamName, userName: identifier.substring(1)}};
|
||||
}
|
||||
|
||||
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl, teamName, channelName: identifier}};
|
||||
}
|
||||
}
|
||||
|
||||
let match = new RegExp('(.*)\\/([^\\/]+)\\/channels\\/(\\S+)').exec(url);
|
||||
if (match) {
|
||||
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], channelName: match[3]}};
|
||||
const permalinkMatch = matchPermalinkDeeplink(url);
|
||||
if (permalinkMatch) {
|
||||
const {params: {serverUrl, teamName, postId}} = permalinkMatch;
|
||||
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl, teamName, postId}};
|
||||
}
|
||||
|
||||
match = new RegExp('(.*)\\/([^\\/]+)\\/pl\\/(\\w+)').exec(url);
|
||||
if (match) {
|
||||
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], postId: match[3]}};
|
||||
const pluginMatch = match<{serverUrl: string; id: string; route?: string}>(`:serverUrl(.*)/plugins/:id(${PLUGIN_ID_PATH_PATTERN})/:route(.*)?`)(url);
|
||||
if (pluginMatch) {
|
||||
const {params: {serverUrl, id, route}} = pluginMatch;
|
||||
return {type: DeepLink.Plugin, url: deepLinkUrl, data: {serverUrl, teamName: '', id, route}};
|
||||
}
|
||||
|
||||
match = new RegExp('(.*)\\/([^\\/]+)\\/messages\\/@(\\S+)').exec(url);
|
||||
if (match) {
|
||||
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], userName: match[3]}};
|
||||
}
|
||||
|
||||
match = new RegExp('(.*)\\/([^\\/]+)\\/messages\\/(\\S+)').exec(url);
|
||||
if (match) {
|
||||
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], channelId: match[3]}};
|
||||
}
|
||||
|
||||
match = new RegExp('(.*)\\/plugins\\/([^\\/]+)\\/(\\S+)').exec(url);
|
||||
if (match) {
|
||||
return {type: DeepLink.Plugin, url: deepLinkUrl, data: {serverUrl: match[1], id: match[2], teamName: ''}};
|
||||
}
|
||||
} catch {
|
||||
} catch (err) {
|
||||
// do nothing just return invalid deeplink
|
||||
}
|
||||
|
||||
return {type: DeepLink.Invalid, url: deepLinkUrl};
|
||||
}
|
||||
|
||||
export function matchDeepLink(url?: string, serverURL?: string, siteURL?: string) {
|
||||
export function matchDeepLink(url: string, serverURL?: string, siteURL?: string) {
|
||||
if (!url || (!serverURL && !siteURL)) {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
let urlToMatch = url;
|
||||
const urlBase = serverURL || siteURL || '';
|
||||
const parsedUrl = urlParse(url);
|
||||
const parsedBase = urlParse(urlBase);
|
||||
|
||||
if (!parsedUrl.protocol) {
|
||||
// If url doesn't contain site or server URL, tack it on.
|
||||
// e.g. <jump to convo> URLs from autolink plugin.
|
||||
const match = new RegExp(escapeRegex(urlBase)).exec(url);
|
||||
if (!match) {
|
||||
const deepLinkMatch = new RegExp(escapeRegex(urlBase)).exec(url);
|
||||
if (!deepLinkMatch) {
|
||||
urlToMatch = urlBase + url;
|
||||
}
|
||||
}
|
||||
|
||||
const finalUrl = urlParse(urlToMatch);
|
||||
const baseSubpath = parsedBase.pathname.replace('/', '');
|
||||
const baseHostname = parsedBase.hostname;
|
||||
const urlSubpath = finalUrl.pathname.split('/')[1];
|
||||
const urlHostname = finalUrl.hostname;
|
||||
const parsed = parseDeepLink(urlToMatch);
|
||||
|
||||
if (baseSubpath) {
|
||||
// if the server is in a subpath
|
||||
if (urlHostname === baseHostname && urlSubpath === baseSubpath) {
|
||||
return urlToMatch;
|
||||
}
|
||||
} else if (urlHostname === baseHostname) {
|
||||
return urlToMatch;
|
||||
if (parsed.type === DeepLinkType.Invalid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '';
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export const getLaunchPropsFromDeepLink = (deepLinkUrl: string, coldStart = false): LaunchProps => {
|
||||
|
|
|
|||
20
app/utils/url/path.ts
Normal file
20
app/utils/url/path.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const ID_PATH_PATTERN = '[a-z0-9]{26}';
|
||||
export const PLUGIN_ID_PATH_PATTERN = '[a-zA-Z0-9-_.]{3,190}';
|
||||
|
||||
// This should cover:
|
||||
// - Team name (lowercase english characters, numbers or -)
|
||||
// - Two ids separated by __ (userID__userID)
|
||||
export const TEAM_NAME_PATH_PATTERN = '[a-z0-9\\-_]+';
|
||||
|
||||
// This should cover:
|
||||
// - Channel name
|
||||
// - Channel ID
|
||||
// - Group Channel Name (40 length UID)
|
||||
// - DM Name (userID__userID)
|
||||
// - Username prefixed by a @
|
||||
// - User ID
|
||||
// - Email
|
||||
export const IDENTIFIER_PATH_PATTERN = '[@a-zA-Z\\-_0-9][@a-zA-Z\\-_0-9.]*';
|
||||
|
|
@ -5,7 +5,7 @@ import {Linking} from 'react-native';
|
|||
|
||||
import DeepLinkType from '@constants/deep_linking';
|
||||
import TestHelper from '@test/test_helper';
|
||||
import {matchDeepLink, parseDeepLink} from '@utils/deep_link';
|
||||
import {matchDeepLink} from '@utils/deep_link';
|
||||
import * as UrlUtils from '@utils/url';
|
||||
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
|
|
@ -129,32 +129,35 @@ describe('UrlUtils', () => {
|
|||
|
||||
describe('matchDeepLink', () => {
|
||||
const URL_NO_PROTOCOL = 'localhost:8065';
|
||||
const URL_PATH_NO_PROTOCOL = 'localhost:8065/subpath';
|
||||
const URL_PATH_NO_PROTOCOL = 'localhost:8065/subpath/deepsubpath';
|
||||
const SITE_URL = `http://${URL_NO_PROTOCOL}`;
|
||||
const SERVER_URL = `http://${URL_NO_PROTOCOL}`;
|
||||
const SERVER_WITH_SUBPATH = `http://${URL_PATH_NO_PROTOCOL}`;
|
||||
const DEEPLINK_URL_ROOT = `mattermost://${URL_NO_PROTOCOL}`;
|
||||
|
||||
const DM_USER = TestHelper.fakeUserWithId();
|
||||
const GM_CHANNEL_NAME = '4862db64e76a321d167fe6677f16e96e9275dabe';
|
||||
|
||||
const tests = [
|
||||
{
|
||||
name: 'should return null if all inputs are empty',
|
||||
input: {url: '', serverURL: '', siteURL: ''},
|
||||
expected: {type: 'invalid'},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null if any of the input is null',
|
||||
input: {url: '', serverURL: '', siteURL: null},
|
||||
expected: {type: 'invalid'},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null if any of the input is null',
|
||||
input: {url: '', serverURL: null, siteURL: ''},
|
||||
expected: {type: 'invalid'},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null if any of the input is null',
|
||||
input: {url: null, serverURL: '', siteURL: ''},
|
||||
expected: {type: 'invalid'},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for not supported link',
|
||||
|
|
@ -163,12 +166,12 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: 'invalid'},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null despite url subset match',
|
||||
input: {url: 'http://myserver.com', serverURL: 'http://myserver.co'},
|
||||
expected: {type: 'invalid'},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should match despite no server URL in input link',
|
||||
|
|
@ -186,6 +189,51 @@ describe('UrlUtils', () => {
|
|||
type: DeepLinkType.Permalink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should return null for invalid deeplink',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/ad-1/channels/../town-square',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for backslash-invalid deeplink',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/ad-1/channels/\\..town-square',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for backslash-invalid-alt deeplink',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/ad-1/channels/t..\\town-square',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for double encoded invalid deeplink',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/ad-1/channels/%252f%252e.town-square',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for double encoded backslash-invalid deeplink',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/ad-1/channels/%255C%252e.town-square',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should match channel link',
|
||||
input: {
|
||||
|
|
@ -234,10 +282,74 @@ describe('UrlUtils', () => {
|
|||
type: DeepLinkType.Channel,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match channel link (channel name: messages) with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/ad-1/channels/messages',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
channelName: 'messages',
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'ad-1',
|
||||
},
|
||||
type: DeepLinkType.Channel,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match DM channel link with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + `/pl/messages/@${DM_USER.username}`,
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
userName: DM_USER.username,
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.DirectMessage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match GM channel link with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + `/pl/messages/${GM_CHANNEL_NAME}`,
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
channelName: GM_CHANNEL_NAME,
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.GroupMessage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match channel link (team name: pl, channel name: messages) with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/pl/channels/messages',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
channelName: 'messages',
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.Channel,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match permalink with deeplink prefix on a Server hosted in a Subpath',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/ad-1/pl/qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/ad-1/pl/qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
|
|
@ -247,7 +359,23 @@ describe('UrlUtils', () => {
|
|||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: 'ad-1',
|
||||
},
|
||||
type: 'permalink',
|
||||
type: DeepLinkType.Permalink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match permalink (team name: pl) with deeplink prefix on a Server hosted in a Subpath',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/pl/pl/qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
postId: 'qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.Permalink,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -263,7 +391,7 @@ describe('UrlUtils', () => {
|
|||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: 'ad-1',
|
||||
},
|
||||
type: 'permalink',
|
||||
type: DeepLinkType.Permalink,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -273,8 +401,75 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should match plugin path on a Server hosted in a Subpath',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/plugins/com.acme.abc-test/api/testroute',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: {
|
||||
type: 'invalid',
|
||||
data: {
|
||||
id: 'com.acme.abc-test',
|
||||
route: 'api/testroute',
|
||||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: '',
|
||||
},
|
||||
type: DeepLinkType.Plugin,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match plugin path with single-level route',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/plugins/abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_ab/testroute',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
id: 'abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_ab',
|
||||
route: 'testroute',
|
||||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: '',
|
||||
},
|
||||
type: DeepLinkType.Plugin,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not match plugin path with invalid plugin id len=2',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/plugins/ab/api/testroute',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should not match plugin path with invalid plugin id len=191',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/plugins/abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc_abc/api/testroute',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should match plugin path without a route',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/plugins/abc',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
id: 'abc',
|
||||
route: undefined,
|
||||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: '',
|
||||
},
|
||||
type: DeepLinkType.Plugin,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -283,10 +478,11 @@ describe('UrlUtils', () => {
|
|||
const {name, input, expected} = test;
|
||||
|
||||
it(name, () => {
|
||||
const match = matchDeepLink(input.url!, input.serverURL!, input.siteURL!);
|
||||
const parsed = parseDeepLink(match);
|
||||
Reflect.deleteProperty(parsed, 'url');
|
||||
expect(parsed).toEqual(expected);
|
||||
const matched = matchDeepLink(input.url!, input.serverURL!, input.siteURL!);
|
||||
if (matched) {
|
||||
Reflect.deleteProperty(matched, 'url');
|
||||
}
|
||||
expect(matched).toEqual(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1929,7 +1929,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 489;
|
||||
CURRENT_PROJECT_VERSION = 496;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
|
|
@ -1973,7 +1973,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 489;
|
||||
CURRENT_PROJECT_VERSION = 496;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
|
|
@ -2116,7 +2116,7 @@
|
|||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 489;
|
||||
CURRENT_PROJECT_VERSION = 496;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
@ -2165,7 +2165,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 489;
|
||||
CURRENT_PROJECT_VERSION = 496;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.9.0</string>
|
||||
<string>2.10.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>489</string>
|
||||
<string>496</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.9.0</string>
|
||||
<string>2.10.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>489</string>
|
||||
<string>496</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.9.0</string>
|
||||
<string>2.10.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>489</string>
|
||||
<string>496</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
23
package-lock.json
generated
23
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "2.9.0",
|
||||
"version": "2.10.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"@voximplant/react-native-foreground-service": "3.0.2",
|
||||
"base-64": "1.0.0",
|
||||
"commonmark": "npm:@mattermost/commonmark@0.30.1-1",
|
||||
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#235bc817bcade503fb81fa51bbbe3c84f958ed12",
|
||||
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#81b5d27509652bae50b4b510ede777dd3bd923cf",
|
||||
"deep-equal": "2.2.2",
|
||||
"deepmerge": "4.3.1",
|
||||
"emoji-regex": "10.2.1",
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
"mime-db": "1.52.0",
|
||||
"moment-timezone": "0.5.43",
|
||||
"pako": "2.1.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"react": "18.2.0",
|
||||
"react-freeze": "1.0.3",
|
||||
"react-intl": "6.4.4",
|
||||
|
|
@ -9679,8 +9680,8 @@
|
|||
},
|
||||
"node_modules/commonmark-react-renderer": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "git+ssh://git@github.com/mattermost/commonmark-react-renderer.git#235bc817bcade503fb81fa51bbbe3c84f958ed12",
|
||||
"integrity": "sha512-GP3+17loU8tNal91FAlqDcDyUHdhahMPpV/XBm++VAyoyvsGKx3wGQZY/cVKXNx+SV2UANXVf4VFx8GpAkqCBQ==",
|
||||
"resolved": "git+ssh://git@github.com/mattermost/commonmark-react-renderer.git#81b5d27509652bae50b4b510ede777dd3bd923cf",
|
||||
"integrity": "sha512-2UYjN/Fix93L6udBsmZqCUggE7e5NA8rXHO2R3Dxs3lwHac3MsOMdEYZPST3Z/CCFc6gyKqWqDtCQXofbHffPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.assign": "^4.2.0",
|
||||
|
|
@ -18577,6 +18578,11 @@
|
|||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
|
||||
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
|
|
@ -28521,7 +28527,8 @@
|
|||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@voximplant/react-native-foreground-service/-/react-native-foreground-service-3.0.2.tgz",
|
||||
"integrity": "sha512-ZIyccOAXPqznA1PAVAYlKZ+GI7kXYCuYgH+gmAkhPouyJbkgrSXaCJJzQ+uBkPr4FBa/PuC/yjzK8vf6tJREQA==",
|
||||
"requires": {}
|
||||
"requires": {
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
|
|
@ -30055,9 +30062,9 @@
|
|||
}
|
||||
},
|
||||
"commonmark-react-renderer": {
|
||||
"version": "git+ssh://git@github.com/mattermost/commonmark-react-renderer.git#235bc817bcade503fb81fa51bbbe3c84f958ed12",
|
||||
"integrity": "sha512-GP3+17loU8tNal91FAlqDcDyUHdhahMPpV/XBm++VAyoyvsGKx3wGQZY/cVKXNx+SV2UANXVf4VFx8GpAkqCBQ==",
|
||||
"from": "commonmark-react-renderer@github:mattermost/commonmark-react-renderer#235bc817bcade503fb81fa51bbbe3c84f958ed12",
|
||||
"version": "git+ssh://git@github.com/mattermost/commonmark-react-renderer.git#81b5d27509652bae50b4b510ede777dd3bd923cf",
|
||||
"integrity": "sha512-2UYjN/Fix93L6udBsmZqCUggE7e5NA8rXHO2R3Dxs3lwHac3MsOMdEYZPST3Z/CCFc6gyKqWqDtCQXofbHffPA==",
|
||||
"from": "commonmark-react-renderer@github:mattermost/commonmark-react-renderer#81b5d27509652bae50b4b510ede777dd3bd923cf",
|
||||
"requires": {
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "2.9.0",
|
||||
"version": "2.10.1",
|
||||
"description": "Mattermost Mobile with React Native",
|
||||
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
||||
"author": "Mattermost, Inc.",
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
"@voximplant/react-native-foreground-service": "3.0.2",
|
||||
"base-64": "1.0.0",
|
||||
"commonmark": "npm:@mattermost/commonmark@0.30.1-1",
|
||||
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#235bc817bcade503fb81fa51bbbe3c84f958ed12",
|
||||
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#81b5d27509652bae50b4b510ede777dd3bd923cf",
|
||||
"deep-equal": "2.2.2",
|
||||
"deepmerge": "4.3.1",
|
||||
"emoji-regex": "10.2.1",
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
"mime-db": "1.52.0",
|
||||
"moment-timezone": "0.5.43",
|
||||
"pako": "2.1.0",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"react": "18.2.0",
|
||||
"react-freeze": "1.0.3",
|
||||
"react-intl": "6.4.4",
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ export interface DeepLinkPermalink extends DeepLink {
|
|||
}
|
||||
|
||||
export interface DeepLinkGM extends DeepLink {
|
||||
channelId: string;
|
||||
channelName: string;
|
||||
}
|
||||
|
||||
export interface DeepLinkPlugin extends DeepLink {
|
||||
id: string;
|
||||
route?: string;
|
||||
}
|
||||
|
||||
export type DeepLinkType = typeof DeepLink[keyof typeof DeepLink];
|
||||
|
|
|
|||
Loading…
Reference in a new issue