mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Desktop: Resolves #4727: Add support for safe mode, which temporarily disables note rendering and plugins
This commit is contained in:
parent
920f54f5d3
commit
3235f58f5a
@ -211,6 +211,9 @@ packages/app-desktop/commands/stopExternalEditing.js.map
|
||||
packages/app-desktop/commands/toggleExternalEditing.d.ts
|
||||
packages/app-desktop/commands/toggleExternalEditing.js
|
||||
packages/app-desktop/commands/toggleExternalEditing.js.map
|
||||
packages/app-desktop/commands/toggleSafeMode.d.ts
|
||||
packages/app-desktop/commands/toggleSafeMode.js
|
||||
packages/app-desktop/commands/toggleSafeMode.js.map
|
||||
packages/app-desktop/gui/Button/Button.d.ts
|
||||
packages/app-desktop/gui/Button/Button.js
|
||||
packages/app-desktop/gui/Button/Button.js.map
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -198,6 +198,9 @@ packages/app-desktop/commands/stopExternalEditing.js.map
|
||||
packages/app-desktop/commands/toggleExternalEditing.d.ts
|
||||
packages/app-desktop/commands/toggleExternalEditing.js
|
||||
packages/app-desktop/commands/toggleExternalEditing.js.map
|
||||
packages/app-desktop/commands/toggleSafeMode.d.ts
|
||||
packages/app-desktop/commands/toggleSafeMode.js
|
||||
packages/app-desktop/commands/toggleSafeMode.js.map
|
||||
packages/app-desktop/gui/Button/Button.d.ts
|
||||
packages/app-desktop/gui/Button/Button.js
|
||||
packages/app-desktop/gui/Button/Button.js.map
|
||||
|
@ -89,10 +89,11 @@ const globalCommands = [
|
||||
require('./commands/exportNotes'),
|
||||
require('./commands/focusElement'),
|
||||
require('./commands/openProfileDirectory'),
|
||||
require('./commands/replaceMisspelling'),
|
||||
require('./commands/startExternalEditing'),
|
||||
require('./commands/stopExternalEditing'),
|
||||
require('./commands/toggleExternalEditing'),
|
||||
require('./commands/replaceMisspelling'),
|
||||
require('./commands/toggleSafeMode'),
|
||||
require('@joplin/lib/commands/historyBackward'),
|
||||
require('@joplin/lib/commands/historyForward'),
|
||||
require('@joplin/lib/commands/synchronize'),
|
||||
@ -539,6 +540,7 @@ class Application extends BaseApplication {
|
||||
|
||||
const pluginRunner = new PluginRunner();
|
||||
service.initialize(packageInfo.version, PlatformImplementation.instance(), pluginRunner, this.store());
|
||||
service.isSafeMode = Setting.value('isSafeMode');
|
||||
|
||||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
|
||||
|
||||
|
20
packages/app-desktop/commands/toggleSafeMode.ts
Normal file
20
packages/app-desktop/commands/toggleSafeMode.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleSafeMode',
|
||||
label: () => _('Toggle safe mode'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (_context: CommandContext, enabled: boolean = null) => {
|
||||
enabled = enabled !== null ? enabled : !Setting.value('isSafeMode');
|
||||
Setting.setValue('isSafeMode', enabled);
|
||||
await Setting.saveAll();
|
||||
bridge().restart();
|
||||
},
|
||||
};
|
||||
};
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import versionInfo from '@joplin/lib/versionInfo';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import bridge from '../services/bridge';
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
@ -62,6 +63,12 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
const safeMode_click = async () => {
|
||||
Setting.setValue('isSafeMode', true);
|
||||
await Setting.saveAll();
|
||||
bridge().restart();
|
||||
};
|
||||
|
||||
try {
|
||||
const output = [];
|
||||
|
||||
@ -112,6 +119,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
<div style={{ overflow: 'auto', fontFamily: 'sans-serif', padding: '5px 20px' }}>
|
||||
<h1>Error</h1>
|
||||
<p>Joplin encountered a fatal error and could not continue. To report the error, please copy the *entire content* of this page and post it on Joplin forum or GitHub.</p>
|
||||
<p>To continue you may close the app. Alternatively, if the error persists you may try to <a href="#" onClick={safeMode_click}>restart in safe mode</a>, which will temporarily disable all plugins.</p>
|
||||
{output}
|
||||
</div>
|
||||
);
|
||||
|
@ -63,6 +63,7 @@ interface Props {
|
||||
settingEditorCodeView: boolean;
|
||||
pluginsLegacy: any;
|
||||
startupPluginsLoaded: boolean;
|
||||
isSafeMode: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -237,7 +238,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
// For example, it cannot be closed right away if a note is being saved.
|
||||
// If a note is being saved, we wait till it is saved and then call
|
||||
// "appCloseReply" again.
|
||||
ipcRenderer.on('appClose', () => {
|
||||
ipcRenderer.on('appClose', async () => {
|
||||
if (this.waitForNotesSavedIID_) shim.clearInterval(this.waitForNotesSavedIID_);
|
||||
this.waitForNotesSavedIID_ = null;
|
||||
|
||||
@ -489,8 +490,24 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
bridge().restart();
|
||||
};
|
||||
|
||||
const onDisableSafeModeAndRestart = async () => {
|
||||
Setting.setValue('isSafeMode', false);
|
||||
await Setting.saveAll();
|
||||
bridge().restart();
|
||||
};
|
||||
|
||||
let msg = null;
|
||||
if (this.props.shouldUpgradeSyncTarget) {
|
||||
|
||||
if (this.props.isSafeMode) {
|
||||
msg = (
|
||||
<span>
|
||||
{_('Safe mode is currently active. Note rendering and all plugins are temporarily disabled.')}{' '}
|
||||
<a href="#" onClick={() => onDisableSafeModeAndRestart()}>
|
||||
{_('Disable safe mode and restart')}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
} else if (this.props.shouldUpgradeSyncTarget) {
|
||||
msg = (
|
||||
<span>
|
||||
{_('The sync target needs to be upgraded before Joplin can sync. The operation may take a few minutes to complete and the app needs to be restarted. To proceed please click on the link.')}{' '}
|
||||
@ -553,9 +570,9 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
messageBoxVisible(props: any = null) {
|
||||
messageBoxVisible(props: Props = null) {
|
||||
if (!props) props = this.props;
|
||||
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget;
|
||||
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode;
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
@ -777,6 +794,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
layoutMoveMode: state.layoutMoveMode,
|
||||
mainLayout: state.mainLayout,
|
||||
startupPluginsLoaded: state.startupPluginsLoaded,
|
||||
isSafeMode: state.settings.isSafeMode,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -737,6 +737,7 @@ function useMenu(props: Props) {
|
||||
},
|
||||
},
|
||||
|
||||
menuItemDic.toggleSafeMode,
|
||||
menuItemDic.openProfileDirectory,
|
||||
menuItemDic.copyDevCommand,
|
||||
|
||||
|
@ -43,5 +43,6 @@ export default function() {
|
||||
'editor.sortSelectedLines',
|
||||
'editor.swapLineUp',
|
||||
'editor.swapLineDown',
|
||||
'toggleSafeMode',
|
||||
];
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import htmlUtils from './htmlUtils';
|
||||
import Resource from './models/Resource';
|
||||
|
||||
export class MarkupLanguageUtils {
|
||||
|
||||
private lib_(language: MarkupLanguage) {
|
||||
if (language === MarkupLanguage.Html) return htmlUtils;
|
||||
if (language === MarkupLanguage.Markdown) return markdownUtils;
|
||||
@ -31,6 +32,7 @@ export class MarkupLanguageUtils {
|
||||
pluginOptions: pluginOptions,
|
||||
tempDir: Setting.value('tempDir'),
|
||||
fsDriver: shim.fsDriver(),
|
||||
isSafeMode: Setting.value('isSafeMode'),
|
||||
}, options);
|
||||
|
||||
return new MarkupToHtml(options);
|
||||
|
@ -1133,6 +1133,14 @@ class Setting extends BaseModel {
|
||||
'Restart app to see changes.'),
|
||||
storage: SettingStorage.File,
|
||||
},
|
||||
|
||||
isSafeMode: {
|
||||
value: false,
|
||||
type: SettingItemType.Bool,
|
||||
public: false,
|
||||
appTypes: ['desktop'],
|
||||
storage: SettingStorage.Database,
|
||||
},
|
||||
};
|
||||
|
||||
this.metadata_ = Object.assign(this.metadata_, this.customMetadata_);
|
||||
|
@ -74,6 +74,7 @@ export default class PluginService extends BaseService {
|
||||
private plugins_: Plugins = {};
|
||||
private runner_: BasePluginRunner = null;
|
||||
private startedPlugins_: Record<string, boolean> = {};
|
||||
private isSafeMode_: boolean = false;
|
||||
|
||||
public initialize(appVersion: string, platformImplementation: any, runner: BasePluginRunner, store: any) {
|
||||
this.appVersion_ = appVersion;
|
||||
@ -86,6 +87,14 @@ export default class PluginService extends BaseService {
|
||||
return this.plugins_;
|
||||
}
|
||||
|
||||
public get isSafeMode(): boolean {
|
||||
return this.isSafeMode_;
|
||||
}
|
||||
|
||||
public set isSafeMode(v: boolean) {
|
||||
this.isSafeMode_ = v;
|
||||
}
|
||||
|
||||
private setPluginAt(pluginId: string, plugin: Plugin) {
|
||||
this.plugins_ = {
|
||||
...this.plugins_,
|
||||
@ -346,6 +355,8 @@ export default class PluginService extends BaseService {
|
||||
}
|
||||
|
||||
public async runPlugin(plugin: Plugin) {
|
||||
if (this.isSafeMode) throw new Error(`Plugin was not started due to safe mode: ${plugin.manifest.id}`);
|
||||
|
||||
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 {
|
||||
|
@ -27,24 +27,35 @@ export interface RenderResult {
|
||||
cssStrings: string[];
|
||||
}
|
||||
|
||||
export interface OptionsResourceModel {
|
||||
isResourceUrl: (url: string)=> boolean;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
isSafeMode?: boolean;
|
||||
ResourceModel: OptionsResourceModel;
|
||||
}
|
||||
|
||||
export default class MarkupToHtml {
|
||||
|
||||
static MARKUP_LANGUAGE_MARKDOWN: number = MarkupLanguage.Markdown;
|
||||
static MARKUP_LANGUAGE_HTML: number = MarkupLanguage.Html;
|
||||
|
||||
private renderers_: any = {};
|
||||
private options_: any;
|
||||
private options_: Options;
|
||||
private rawMarkdownIt_: any;
|
||||
|
||||
constructor(options: any) {
|
||||
this.options_ = Object.assign({}, {
|
||||
public constructor(options: Options) {
|
||||
this.options_ = {
|
||||
ResourceModel: {
|
||||
isResourceUrl: () => false,
|
||||
},
|
||||
}, options);
|
||||
isSafeMode: false,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
renderer(markupLanguage: MarkupLanguage) {
|
||||
private renderer(markupLanguage: MarkupLanguage) {
|
||||
if (this.renderers_[markupLanguage]) return this.renderers_[markupLanguage];
|
||||
|
||||
let RendererClass = null;
|
||||
@ -61,7 +72,7 @@ export default class MarkupToHtml {
|
||||
return this.renderers_[markupLanguage];
|
||||
}
|
||||
|
||||
stripMarkup(markupLanguage: MarkupLanguage, markup: string, options: any = null) {
|
||||
public stripMarkup(markupLanguage: MarkupLanguage, markup: string, options: any = null) {
|
||||
if (!markup) return '';
|
||||
|
||||
options = Object.assign({}, {
|
||||
@ -89,16 +100,23 @@ export default class MarkupToHtml {
|
||||
return output;
|
||||
}
|
||||
|
||||
clearCache(markupLanguage: MarkupLanguage) {
|
||||
public clearCache(markupLanguage: MarkupLanguage) {
|
||||
const r = this.renderer(markupLanguage);
|
||||
if (r.clearCache) r.clearCache();
|
||||
}
|
||||
|
||||
async render(markupLanguage: MarkupLanguage, markup: string, theme: any, options: any): Promise<RenderResult> {
|
||||
public async render(markupLanguage: MarkupLanguage, markup: string, theme: any, options: any): Promise<RenderResult> {
|
||||
if (this.options_.isSafeMode) {
|
||||
return {
|
||||
html: `<pre>${markup}</pre>`,
|
||||
cssStrings: [],
|
||||
pluginAssets: [],
|
||||
};
|
||||
}
|
||||
return this.renderer(markupLanguage).render(markup, theme, options);
|
||||
}
|
||||
|
||||
async allAssets(markupLanguage: MarkupLanguage, theme: any) {
|
||||
public async allAssets(markupLanguage: MarkupLanguage, theme: any) {
|
||||
return this.renderer(markupLanguage).allAssets(theme);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import FileModel from '../../models/FileModel';
|
||||
import { ErrorNotFound } from '../../utils/errors';
|
||||
import BaseApplication from '../../services/BaseApplication';
|
||||
import { formatDateTime } from '../../utils/time';
|
||||
import { OptionsResourceModel } from '@joplin/renderer/MarkupToHtml';
|
||||
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
|
||||
@ -163,7 +164,7 @@ export default class Application extends BaseApplication {
|
||||
|
||||
private async renderNote(share: Share, note: NoteEntity, resourceInfos: ResourceInfos, linkedItemInfos: LinkedItemInfos): Promise<FileViewerResponse> {
|
||||
const markupToHtml = new MarkupToHtml({
|
||||
ResourceModel: Resource,
|
||||
ResourceModel: Resource as OptionsResourceModel,
|
||||
});
|
||||
|
||||
const renderOptions: any = {
|
||||
|
Loading…
Reference in New Issue
Block a user