Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@

import { PluginHtmlContents, PluginStates, ViewInfo } from '@joplin/lib/services/plugins/reducer';
import * as React from 'react';
import { Button, IconButton, Portal, SegmentedButtons, Text } from 'react-native-paper';
import { Button, Portal, SegmentedButtons, Text } from 'react-native-paper';
import useViewInfos from './hooks/useViewInfos';
import WebviewController, { ContainerType } from '@joplin/lib/services/plugins/WebviewController';
import { useCallback, useEffect, useMemo, useState } from 'react';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import { connect } from 'react-redux';
import { AppState } from '../../../utils/types';
import PluginUserWebView from './PluginUserWebView';
import { View, useWindowDimensions, StyleSheet, AccessibilityInfo } from 'react-native';
import { View, StyleSheet, AccessibilityInfo } from 'react-native';
import { _ } from '@joplin/lib/locale';
import { Theme } from '@joplin/lib/themes/type';
import { themeStyle } from '@joplin/lib/theme';
import Setting from '@joplin/lib/models/Setting';
import { Dispatch } from 'redux';
import Modal from '../../../components/Modal';
import DismissibleDialog from '../../../components/DismissibleDialog';

interface Props {
themeId: number;
Expand All @@ -27,40 +25,16 @@ interface Props {
}


const useStyles = (themeId: number) => {
const windowSize = useWindowDimensions();

return useMemo(() => {
const theme: Theme = themeStyle(themeId);

return StyleSheet.create({
webView: {
backgroundColor: 'transparent',
display: 'flex',
},
webViewContainer: {
flexGrow: 1,
flexShrink: 1,
},
closeButtonContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
dialog: {
backgroundColor: theme.backgroundColor,
borderRadius: 12,
padding: 10,

height: windowSize.height * 0.9,
width: windowSize.width * 0.97,

// Center
marginLeft: 'auto',
marginRight: 'auto',
},
});
}, [themeId, windowSize.width, windowSize.height]);
};
const styles = StyleSheet.create({
webView: {
backgroundColor: 'transparent',
display: 'flex',
},
webViewContainer: {
flexGrow: 1,
flexShrink: 1,
},
});

type ButtonInfo = {
value: string;
Expand Down Expand Up @@ -141,7 +115,6 @@ const PluginPanelViewer: React.FC<Props> = props => {
});
}, [viewInfoById]);

const styles = useStyles(props.themeId);
const { selectedTabId, setSelectedTabId } = useSelectedTabId(buttonInfos, viewInfoById);

const viewInfo = viewInfoById[selectedTabId];
Expand Down Expand Up @@ -194,30 +167,16 @@ const PluginPanelViewer: React.FC<Props> = props => {
});
}, [props.dispatch]);

const closeButton = (
<View style={styles.closeButtonContainer}>
<IconButton
icon='close'
accessibilityLabel={_('Close')}
onPress={onClose}
/>
</View>
);

return (
<Portal>
<Modal
<DismissibleDialog
themeId={props.themeId}
visible={props.visible}
onDismiss={onClose}
onRequestClose={onClose}
animationType='fade'
transparent={true}
containerStyle={styles.dialog}
>
{closeButton}
{renderTabContent()}
{renderTabSelector()}
</Modal>
</DismissibleDialog>
</Portal>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import getPluginIssueReportUrl from './getPluginIssueReportUrl';

describe('getPluginIssueReportUrl', () => {
test.each([
[{ repository_url: 'http://github.com/laurent22/joplin' }, 'https://github.com/laurent22/joplin/issues'],
[{ repository_url: 'https://www.github.com/laurent22/joplin' }, 'https://github.com/laurent22/joplin/issues'],
[{ repository_url: 'https://www.github.com/laurent22/joplin.git' }, 'https://github.com/laurent22/joplin/issues'],
[{ homepage_url: 'https://www.github.com/laurent22/joplin' }, 'https://github.com/laurent22/joplin/issues'],

[{ homepage_url: 'https://gitlab.com/laurent22/joplin' }, 'https://gitlab.com/laurent22/joplin/-/issues'],
[{ homepage_url: 'https://www.gitlab.com/laurent22/joplin' }, 'https://gitlab.com/laurent22/joplin/-/issues'],

[{ homepage_url: 'https://example.com/laurent22/joplin' }, null],
])('should return the issue URL (case %#)', async (manifest, expectedUrl) => {
expect(getPluginIssueReportUrl(manifest)).toBe(expectedUrl);
});
});
31 changes: 31 additions & 0 deletions packages/lib/services/plugins/utils/getPluginIssueReportUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PluginManifest } from './types';

type ManifestSlice = Pick<PluginManifest, 'repository_url'|'homepage_url'>;
const getPluginIssueReportUrl = (pluginManifest: ManifestSlice): string|null => {
const githubUrlExp = /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/?]+)/;
const gitlabUrlExp = /^https?:\/\/(?:www\.)?gitlab\.com\/([^/]+)\/([^/]+)/;

let githubUrlMatch = null;
let gitlabUrlMatch = null;
const urls = [pluginManifest.repository_url, pluginManifest.homepage_url].filter(url => !!url);

for (const url of urls) {
githubUrlMatch ??= githubUrlExp.exec(url);
gitlabUrlMatch ??= gitlabUrlExp.exec(url);
}

if (githubUrlMatch) {
const organization = githubUrlMatch[1];
// Some plugins include a trailing .git after the repository name
const project = githubUrlMatch[2].replace(/\.git$/, '');
return `https://github.com/${organization}/${project}/issues`;
} else if (gitlabUrlMatch) {
const organization = gitlabUrlMatch[1];
const project = gitlabUrlMatch[2];
return `https://gitlab.com/${organization}/${project}/-/issues`;
}

return null;
};

export default getPluginIssueReportUrl;