diff --git a/app/components/markdown/markdown_link/markdown_link.js b/app/components/markdown/markdown_link/markdown_link.js index 0d21eaa31..274a855a6 100644 --- a/app/components/markdown/markdown_link/markdown_link.js +++ b/app/components/markdown/markdown_link/markdown_link.js @@ -9,7 +9,7 @@ import {intlShape} from 'react-intl'; import {Alert, Text} from 'react-native'; import urlParse from 'url-parse'; -import {dismissAllModals, popToRoot} from '@actions/navigation'; +import {dismissAllModals, popToRoot, showModal} from '@actions/navigation'; import Config from '@assets/config'; import {DeepLinkTypes} from '@constants'; import {getCurrentServerUrl} from '@init/credentials'; @@ -59,13 +59,20 @@ export default class MarkdownLink extends PureComponent { const match = matchDeepLink(url, serverURL, siteURL); if (match) { - if (match.type === DeepLinkTypes.CHANNEL) { + switch (match.type) { + case DeepLinkTypes.CHANNEL: await handleSelectChannelByName(match.channelName, match.teamName, errorBadChannel, intl); await dismissAllModals(); await popToRoot(); - } else if (match.type === DeepLinkTypes.PERMALINK) { + break; + case DeepLinkTypes.PERMALINK: { const teamName = match.teamName === PERMALINK_GENERIC_TEAM_NAME_REDIRECT ? currentTeamName : match.teamName; showPermalink(intl, teamName, match.postId); + break; + } + case DeepLinkTypes.PLUGIN: + showModal('PluginInternal', match.id, {link: url}); + break; } } else { const onError = () => { diff --git a/app/components/post_list/post_list.tsx b/app/components/post_list/post_list.tsx index f46f4d67a..3e953cb92 100644 --- a/app/components/post_list/post_list.tsx +++ b/app/components/post_list/post_list.tsx @@ -297,10 +297,10 @@ const PostList = ({ if (match) { if (match.type === DeepLinkTypes.CHANNEL) { - handleSelectChannelByName(match.channelName!, match.teamName, errorBadChannel, intl); + handleSelectChannelByName(match.channelName!, match.teamName!, errorBadChannel, intl); } else if (match.type === DeepLinkTypes.PERMALINK) { const teamName = match.teamName === PERMALINK_GENERIC_TEAM_NAME_REDIRECT ? currentTeamName : match.teamName; - onPermalinkPress(match.postId!, teamName); + onPermalinkPress(match.postId!, teamName!); } } else { badDeepLink(intl); diff --git a/app/constants/deep_linking.js b/app/constants/deep_linking.js index 31988cb4d..34a468219 100644 --- a/app/constants/deep_linking.js +++ b/app/constants/deep_linking.js @@ -7,4 +7,5 @@ export default { GROUPCHANNEL: 'groupchannel', PERMALINK: 'permalink', OTHER: 'other', + PLUGIN: 'plugin', }; diff --git a/app/mm-redux/actions/integrations.ts b/app/mm-redux/actions/integrations.ts index 161d8b36e..fdc8b3eaf 100644 --- a/app/mm-redux/actions/integrations.ts +++ b/app/mm-redux/actions/integrations.ts @@ -4,6 +4,7 @@ import {intlShape} from 'react-intl'; import {Alert} from 'react-native'; +import {showModal} from '@actions/navigation'; import {handleSelectChannel, handleSelectChannelByName, loadChannelsByTeamName} from '@actions/views/channel'; import {makeDirectChannel} from '@actions/views/more_dms'; import {showPermalink} from '@actions/views/permalink'; @@ -146,6 +147,9 @@ export function handleGotoLocation(href: string, intl: typeof intlShape): Action dispatch(makeGroupMessageVisibleIfNecessary(match.id)); dispatch(handleSelectChannel(match.id)); break; + case DeepLinkTypes.PLUGIN: + showModal('PluginInternal', match.id, {link: url}); + break; } } else { const {formatMessage} = intl; diff --git a/app/screens/index.js b/app/screens/index.js index 6fe99be03..1c87c7501 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -218,6 +218,9 @@ Navigation.setLazyComponentRegistrator((screenName) => { case 'UserProfile': screen = require('@screens/user_profile').default; break; + case 'PluginInternal': + screen = require('@screens/plugin').default; + break; case 'SlideUp': screen = require('@screens/slide_up').default; break; diff --git a/app/screens/plugin/index.tsx b/app/screens/plugin/index.tsx new file mode 100644 index 000000000..183bf0295 --- /dev/null +++ b/app/screens/plugin/index.tsx @@ -0,0 +1,79 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import React from 'react'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import {WebView} from 'react-native-webview'; +import {shallowEqual, useSelector} from 'react-redux'; + +import StatusBar from '@components/status_bar'; +import {getTheme} from '@mm-redux/selectors/entities/preferences'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; + +import type {GlobalState} from '@mm-redux/types/store'; +import type {Theme} from '@mm-redux/types/theme'; + +const HEADERS = { + 'X-Mobile-App': 'mattermost', +}; + +interface PluginProps { + link: string; +} + +function Plugin({link}: PluginProps) { + const [theme] = useSelector((state: GlobalState) => ([ + getTheme(state), + ]), shallowEqual); + + const style = getStyleSheet(theme); + + const renderWebView = () => { + const userAgent = 'Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/LMY48X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/81.0.4044.117 Mobile Safari/608.2.11'; + return ( + true} + source={{uri: link, headers: HEADERS}} + startInLoadingState={true} + userAgent={userAgent} + useSharedProcessPool={false} + /> + ); + }; + + return ( + + + {renderWebView()} + + ); +} + +export default Plugin; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + container: { + flex: 1, + }, + errorContainer: { + alignItems: 'center', + flex: 1, + marginTop: 40, + }, + errorText: { + color: changeOpacity(theme.centerChannelColor, 0.4), + fontSize: 16, + fontWeight: '400', + lineHeight: 23, + paddingHorizontal: 30, + }, + }; +}); + diff --git a/app/utils/url.js b/app/utils/url.js index f331420eb..5a39c96ce 100644 --- a/app/utils/url.js +++ b/app/utils/url.js @@ -143,6 +143,11 @@ export function matchDeepLink(url, serverURL, siteURL) { return {type: DeepLinkTypes.GROUPCHANNEL, teamName: match[1], id: match[2]}; } + match = new RegExp(linkRoot + '\\/plugins\\/([^\\/]+)\\/(\\S+)').exec(urlToMatch); + if (match) { + return {type: DeepLinkTypes.PLUGIN, id: match[1]}; + } + return null; }