1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Do not allow installing plugins incompatible with current app version

This commit is contained in:
Laurent Cozic 2021-01-24 18:45:42 +00:00
parent 351a05fb0d
commit 774be9cc0d
4 changed files with 44 additions and 31 deletions

View File

@ -26,6 +26,7 @@ interface Props {
installState?: InstallState;
updateState?: UpdateState;
themeId: number;
isCompatible: boolean;
onToggle?: Function;
onDelete?: Function;
onInstall?: Function;
@ -34,28 +35,20 @@ interface Props {
function manifestToItem(manifest: PluginManifest): PluginItem {
return {
id: manifest.id,
name: manifest.name,
version: manifest.version,
description: manifest.description,
manifest: manifest,
enabled: true,
deleted: false,
devMode: false,
hasBeenUpdated: false,
homepage_url: manifest.homepage_url,
};
}
export interface PluginItem {
id: string;
name: string;
version: string;
description: string;
manifest: PluginManifest;
enabled: boolean;
deleted: boolean;
devMode: boolean;
hasBeenUpdated: boolean;
homepage_url: string;
}
const CellRoot = styled.div`
@ -71,6 +64,8 @@ const CellRoot = styled.div`
margin-right: 20px;
margin-bottom: 20px;
box-shadow: 1px 1px 3px rgba(0,0,0,0.2);
opacity: ${props => props.isCompatible ? '1' : '0.6'};
`;
const CellTop = styled.div`
@ -91,6 +86,12 @@ const CellFooter = styled.div`
flex-direction: row;
`;
const NeedUpgradeMessage = styled.span`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.colorWarn};
font-size: ${props => props.theme.fontSize}px;
`;
const DevModeLabel = styled.div`
border: 1px solid ${props => props.theme.color};
border-radius: 4px;
@ -132,8 +133,8 @@ export default function(props: Props) {
const item = props.item ? props.item : manifestToItem(props.manifest);
const onNameClick = useCallback(() => {
if (!props.item.homepage_url) return;
bridge().openExternal(props.item.homepage_url);
if (!props.item.manifest.homepage_url) return;
bridge().openExternal(props.item.manifest.homepage_url);
}, [props.item]);
// For plugins in dev mode things like enabling/disabling or
@ -194,6 +195,16 @@ export default function(props: Props) {
function renderFooter() {
if (item.devMode) return null;
if (!props.isCompatible) {
return (
<CellFooter>
<NeedUpgradeMessage>
{_('Please upgrade Joplin to use this plugin')}
</NeedUpgradeMessage>
</CellFooter>
);
}
return (
<CellFooter>
{renderDeleteButton()}
@ -205,13 +216,13 @@ export default function(props: Props) {
}
return (
<CellRoot>
<CellRoot isCompatible={props.isCompatible}>
<CellTop>
<StyledNameAndVersion mb={'5px'}><StyledName onClick={onNameClick} href="#" style={{ marginRight: 5 }}>{item.name} {item.deleted ? _('(%s)', 'Deleted') : ''}</StyledName><StyledVersion>v{item.version}</StyledVersion></StyledNameAndVersion>
<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()}
</CellTop>
<CellContent>
<StyledDescription>{item.description}</StyledDescription>
<StyledDescription>{item.manifest.description}</StyledDescription>
</CellContent>
{renderFooter()}
</CellRoot>

View File

@ -63,20 +63,16 @@ function usePluginItems(plugins: Plugins, settings: PluginSettings): PluginItem[
};
output.push({
id: pluginId,
name: plugin.manifest.name,
version: plugin.manifest.version,
description: plugin.manifest.description,
manifest: plugin.manifest,
enabled: setting.enabled,
deleted: setting.deleted,
devMode: plugin.devMode,
hasBeenUpdated: setting.hasBeenUpdated,
homepage_url: plugin.manifest.homepage_url,
});
}
output.sort((a: PluginItem, b: PluginItem) => {
return a.name < b.name ? -1 : +1;
return a.manifest.name < b.manifest.name ? -1 : +1;
});
return output;
@ -134,12 +130,12 @@ export default function(props: Props) {
const onDelete = useCallback(async (event: any) => {
const item: PluginItem = event.item;
const confirm = await bridge().showConfirmMessageBox(_('Delete plugin "%s"?', item.name));
const confirm = await bridge().showConfirmMessageBox(_('Delete plugin "%s"?', item.manifest.name));
if (!confirm) return;
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
if (!draft[item.id]) draft[item.id] = defaultPluginSetting();
draft[item.id].deleted = true;
if (!draft[item.manifest.id]) draft[item.manifest.id] = defaultPluginSetting();
draft[item.manifest.id].deleted = true;
});
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
@ -149,8 +145,8 @@ export default function(props: Props) {
const item: PluginItem = event.item;
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
if (!draft[item.id]) draft[item.id] = defaultPluginSetting();
draft[item.id].enabled = !draft[item.id].enabled;
if (!draft[item.manifest.id]) draft[item.manifest.id] = defaultPluginSetting();
draft[item.manifest.id].enabled = !draft[item.manifest.id].enabled;
});
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
@ -213,8 +209,8 @@ export default function(props: Props) {
for (const item of items) {
if (item.deleted) continue;
const isUpdating = updatingPluginsIds[item.id];
const onUpdateHandler = canBeUpdatedPluginIds[item.id] ? onUpdate : null;
const isUpdating = updatingPluginsIds[item.manifest.id];
const onUpdateHandler = canBeUpdatedPluginIds[item.manifest.id] ? onUpdate : null;
let updateState = UpdateState.Idle;
if (onUpdateHandler) updateState = UpdateState.CanUpdate;
@ -222,10 +218,11 @@ export default function(props: Props) {
if (item.hasBeenUpdated) updateState = UpdateState.HasBeenUpdated;
output.push(<PluginBox
key={item.id}
key={item.manifest.id}
item={item}
themeId={props.themeId}
updateState={updateState}
isCompatible={PluginService.instance().isCompatible(item.manifest.app_min_version)}
onDelete={onDelete}
onToggle={onToggle}
onUpdate={onUpdateHandler}

View File

@ -6,7 +6,7 @@ import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
import PluginBox, { InstallState } from './PluginBox';
import { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { _ } from '@joplin/lib/locale';
import useOnInstallHandler from './useOnInstallHandler';
@ -82,6 +82,7 @@ export default function(props: Props) {
key={manifest.id}
manifest={manifest}
themeId={props.themeId}
isCompatible={PluginService.instance().isCompatible(manifest.app_min_version)}
onInstall={onInstall}
installState={installState(manifest.id)}
/>);

View File

@ -334,8 +334,12 @@ export default class PluginService extends BaseService {
}
}
public isCompatible(pluginVersion: string): boolean {
return compareVersions(this.appVersion_, pluginVersion) >= 0;
}
public async runPlugin(plugin: Plugin) {
if (compareVersions(this.appVersion_, plugin.manifest.app_min_version) < 0) {
if (!this.isCompatible(plugin.manifest.app_min_version)) {
throw new Error(`Plugin "${plugin.id}" was disabled because it requires Joplin version ${plugin.manifest.app_min_version} and current version is ${this.appVersion_}.`);
} else {
this.store_.dispatch({