Desktop: Sort plugin results according to recommended property, and display Recommended tag

pull/5405/head
Laurent Cozic 2021-09-01 12:17:20 +01:00
parent 9c44133bd0
commit d97ba57dda
5 changed files with 56 additions and 1 deletions

View File

@ -873,6 +873,16 @@ class Application extends BaseApplication {
// });
// }, 2000);
// setTimeout(() => {
// this.dispatch({
// type: 'NAV_GO',
// routeName: 'Config',
// props: {
// defaultSection: 'plugins',
// },
// });
// }, 2000);
return null;
}

View File

@ -133,6 +133,21 @@ const StyledDescription = styled.div`
line-height: 1.6em;
`;
const RecommendedBadge = styled.a`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.colorWarn};
font-size: ${props => props.theme.fontSize}px;
border: 1px solid ${props => props.theme.colorWarn};
padding: 5px;
margin-bottom: 10px;
border-radius: 50px;
opacity: 0.8;
&:hover {
opacity: 1;
}
`;
export default function(props: Props) {
const item = useMemo(() => {
return props.item ? props.item : manifestToItem(props.manifest);
@ -144,6 +159,10 @@ export default function(props: Props) {
bridge().openExternal(manifest.homepage_url);
}, [item]);
const onRecommendedClick = useCallback(() => {
bridge().openExternal('https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins');
}, []);
// For plugins in dev mode things like enabling/disabling or
// uninstalling them doesn't make sense, as that should be done by
// adding/removing them from wherever they were loaded from.
@ -222,11 +241,18 @@ export default function(props: Props) {
);
}
function renderRecommendedBadge() {
if (props.onToggle) return null;
if (!item.manifest._recommended) return null;
return <RecommendedBadge href="#" title={_('The Joplin team has vetted this plugin and it meets our standards for security and performance.')} onClick={onRecommendedClick}><i className="fas fa-crown"></i></RecommendedBadge>;
}
return (
<CellRoot isCompatible={props.isCompatible}>
<CellTop>
<StyledNameAndVersion mb={'5px'}><StyledName onClick={onNameClick} href="#" style={{ marginRight: 5 }}>{item.manifest.name} {item.deleted ? _('(%s)', 'Deleted') : ''}</StyledName><StyledVersion>v{item.manifest.version}</StyledVersion></StyledNameAndVersion>
{renderToggleButton()}
{renderRecommendedBadge()}
</CellTop>
<CellContent>
<StyledDescription>{item.manifest.description}</StyledDescription>

View File

@ -30,6 +30,14 @@ interface Props {
disabled: boolean;
}
function sortManifestResults(results: PluginManifest[]): PluginManifest[] {
return results.sort((m1, m2) => {
if (m1._recommended && !m2._recommended) return -1;
if (!m1._recommended && m2._recommended) return +1;
return m1.name.toLowerCase() < m2.name.toLowerCase() ? -1 : +1;
});
}
export default function(props: Props) {
const [searchStarted, setSearchStarted] = useState(false);
const [manifests, setManifests] = useState<PluginManifest[]>([]);
@ -47,7 +55,7 @@ export default function(props: Props) {
setSearchResultCount(null);
} else {
const r = await props.repoApi().search(props.searchQuery);
setManifests(r);
setManifests(sortManifestResults(r));
setSearchResultCount(r.length);
}
});

View File

@ -24,6 +24,13 @@ export default function manifestFromObject(o: any): PluginManifest {
return o[name];
};
const getBoolean = (name: string, required: boolean = true, defaultValue: boolean = false): boolean => {
if (required && !o[name]) throw new Error(`Missing required field: ${name}`);
if (!o[name]) return defaultValue;
if (typeof o[name] !== 'boolean') throw new Error(`Field must be a boolean: ${name}`);
return o[name];
};
const permissions: PluginPermission[] = [];
const manifest: PluginManifest = {
@ -39,6 +46,8 @@ export default function manifestFromObject(o: any): PluginManifest {
repository_url: getString('repository_url', false),
keywords: getStrings('keywords', false),
permissions: permissions,
_recommended: getBoolean('_recommended', false, false),
};
validatePluginId(manifest.id);

View File

@ -20,4 +20,6 @@ export interface PluginManifest {
_publish_hash?: string;
_publish_commit?: string;
_npm_package_name?: string;
_obsolete?: boolean;
_recommended?: boolean;
}