You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-29 22:48:10 +02:00
Chore: Apply changes from mobile plugins to lib/ and app-desktop/ (#10079)
This commit is contained in:
@@ -22,35 +22,35 @@ export interface Joplin {
|
||||
export default class BasePlatformImplementation {
|
||||
|
||||
public get versionInfo(): VersionInfo {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: versionInfo');
|
||||
}
|
||||
|
||||
public get clipboard(): any {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: clipboard');
|
||||
}
|
||||
|
||||
public get nativeImage(): any {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: nativeImage');
|
||||
}
|
||||
|
||||
public get window(): WindowImplementation {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: window');
|
||||
}
|
||||
|
||||
public registerComponent(_name: string, _component: any) {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: registerComponent');
|
||||
}
|
||||
|
||||
public unregisterComponent(_name: string) {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: unregisterComponent');
|
||||
}
|
||||
|
||||
public get joplin(): Joplin {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: joplin');
|
||||
}
|
||||
|
||||
public get imaging(): ImagingImplementation {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error('Not implemented: imaging');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ export default abstract class BasePluginRunner extends BaseService {
|
||||
throw new Error(`Not implemented: ${plugin} / ${sandbox}`);
|
||||
}
|
||||
|
||||
public async stop(plugin: Plugin): Promise<void> {
|
||||
throw new Error(`Not implemented ${plugin} stop`);
|
||||
}
|
||||
|
||||
public async waitForSandboxCalls(): Promise<void> {
|
||||
throw new Error('Not implemented: waitForSandboxCalls');
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export default class Plugin {
|
||||
private contentScriptMessageListeners_: Record<string, Function> = {};
|
||||
private dataDir_: string;
|
||||
private dataDirCreated_ = false;
|
||||
private hasErrors_ = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
public constructor(baseDir: string, manifest: PluginManifest, scriptText: string, dispatch: Function, dataDir: string) {
|
||||
@@ -97,6 +98,14 @@ export default class Plugin {
|
||||
return Object.keys(this.viewControllers_).length;
|
||||
}
|
||||
|
||||
public get hasErrors(): boolean {
|
||||
return this.hasErrors_;
|
||||
}
|
||||
|
||||
public set hasErrors(hasErrors: boolean) {
|
||||
this.hasErrors_ = hasErrors;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
public on(eventName: string, callback: Function) {
|
||||
return this.eventEmitter_.on(eventName, callback);
|
||||
@@ -190,4 +199,11 @@ export default class Plugin {
|
||||
return this.contentScriptMessageListeners_[id](message);
|
||||
}
|
||||
|
||||
public onUnload() {
|
||||
this.dispatch_({
|
||||
type: 'PLUGIN_UNLOAD',
|
||||
pluginId: this.id,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -144,6 +144,22 @@ export default class PluginService extends BaseService {
|
||||
delete this.plugins_[pluginId];
|
||||
}
|
||||
|
||||
public async unloadPlugin(pluginId: string) {
|
||||
const plugin = this.plugins_[pluginId];
|
||||
if (plugin) {
|
||||
this.logger().info(`Unloading plugin ${pluginId}`);
|
||||
|
||||
plugin.onUnload();
|
||||
await this.runner_.stop(plugin);
|
||||
|
||||
this.deletePluginAt(pluginId);
|
||||
this.startedPlugins_ = { ...this.startedPlugins_ };
|
||||
delete this.startedPlugins_[pluginId];
|
||||
} else {
|
||||
this.logger().info(`Unable to unload plugin ${pluginId} -- already unloaded`);
|
||||
}
|
||||
}
|
||||
|
||||
private async deletePluginFiles(plugin: Plugin) {
|
||||
await shim.fsDriver().remove(plugin.baseDir);
|
||||
}
|
||||
@@ -167,7 +183,7 @@ export default class PluginService extends BaseService {
|
||||
return output;
|
||||
}
|
||||
|
||||
public serializePluginSettings(settings: PluginSettings): any {
|
||||
public serializePluginSettings(settings: PluginSettings): string {
|
||||
return JSON.stringify(settings);
|
||||
}
|
||||
|
||||
@@ -343,7 +359,7 @@ export default class PluginService extends BaseService {
|
||||
|
||||
private pluginEnabled(settings: PluginSettings, pluginId: string): boolean {
|
||||
if (!settings[pluginId]) return true;
|
||||
return settings[pluginId].enabled !== false;
|
||||
return settings[pluginId].enabled !== false && settings[pluginId].deleted !== true;
|
||||
}
|
||||
|
||||
public callStatsSummary(pluginId: string, duration: number) {
|
||||
@@ -407,6 +423,20 @@ export default class PluginService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
public async loadAndRunDevPlugins(settings: PluginSettings) {
|
||||
const devPluginOptions = { devMode: true, builtIn: false };
|
||||
|
||||
if (Setting.value('plugins.devPluginPaths')) {
|
||||
const paths = Setting.value('plugins.devPluginPaths').split(',').map((p: string) => p.trim());
|
||||
await this.loadAndRunPlugins(paths, settings, devPluginOptions);
|
||||
}
|
||||
|
||||
// Also load dev plugins that have passed via command line arguments
|
||||
if (Setting.value('startupDevPlugins')) {
|
||||
await this.loadAndRunPlugins(Setting.value('startupDevPlugins'), settings, devPluginOptions);
|
||||
}
|
||||
}
|
||||
|
||||
public isCompatible(pluginVersion: string): boolean {
|
||||
return compareVersions(this.appVersion_, pluginVersion) >= 0;
|
||||
}
|
||||
@@ -450,6 +480,7 @@ export default class PluginService extends BaseService {
|
||||
public async installPluginFromRepo(repoApi: RepositoryApi, pluginId: string): Promise<Plugin> {
|
||||
const pluginPath = await repoApi.downloadPlugin(pluginId);
|
||||
const plugin = await this.installPlugin(pluginPath);
|
||||
|
||||
await shim.fsDriver().remove(pluginPath);
|
||||
return plugin;
|
||||
}
|
||||
@@ -467,6 +498,13 @@ export default class PluginService extends BaseService {
|
||||
const preloadedPlugin = await this.loadPluginFromPath(jplPath);
|
||||
await this.deletePluginFiles(preloadedPlugin);
|
||||
|
||||
// On mobile, it's necessary to create the plugin directory before we can copy
|
||||
// into it.
|
||||
if (!(await shim.fsDriver().exists(Setting.value('pluginDir')))) {
|
||||
logger.info(`Creating plugin directory: ${Setting.value('pluginDir')}`);
|
||||
await shim.fsDriver().mkdir(Setting.value('pluginDir'));
|
||||
}
|
||||
|
||||
const destPath = `${Setting.value('pluginDir')}/${preloadedPlugin.id}.jpl`;
|
||||
await shim.fsDriver().copy(jplPath, destPath);
|
||||
|
||||
|
||||
@@ -68,6 +68,10 @@ export default class RepositoryApi {
|
||||
this.tempDir_ = tempDir;
|
||||
}
|
||||
|
||||
public static ofDefaultJoplinRepo(tempDirPath: string) {
|
||||
return new RepositoryApi('https://github.com/joplin/plugins', tempDirPath);
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
// https://github.com/joplin/plugins
|
||||
// https://api.github.com/repos/joplin/plugins/releases
|
||||
@@ -183,6 +187,12 @@ export default class RepositoryApi {
|
||||
}
|
||||
}
|
||||
|
||||
output.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;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ export default class WebviewController extends ViewController {
|
||||
private messageListener_: Function = null;
|
||||
private closeResponse_: CloseResponse = null;
|
||||
|
||||
// True if a **panel** is shown in a modal window.
|
||||
private panelInModalMode_ = false;
|
||||
|
||||
public constructor(handle: ViewHandle, pluginId: string, store: any, baseDir: string, containerType: ContainerType) {
|
||||
super(handle, pluginId, store);
|
||||
this.baseDir_ = toSystemSlashes(baseDir, 'linux');
|
||||
@@ -150,8 +153,26 @@ export default class WebviewController extends ViewController {
|
||||
return this.show(false);
|
||||
}
|
||||
|
||||
// This method allows us to determine whether a panel is shown in dialog mode,
|
||||
// which is used on mobile.
|
||||
public setIsShownInModal(shown: boolean) {
|
||||
this.panelInModalMode_ = shown;
|
||||
}
|
||||
|
||||
public get visible(): boolean {
|
||||
const mainLayout = this.store.getState().mainLayout;
|
||||
const appState = this.store.getState();
|
||||
|
||||
if (this.panelInModalMode_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const mainLayout = appState.mainLayout;
|
||||
|
||||
// Mobile: There is no appState.mainLayout
|
||||
if (!mainLayout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const item = findItemByKey(mainLayout, this.handle);
|
||||
return item ? item.visible : false;
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ import { Command } from './types';
|
||||
export default class JoplinCommands {
|
||||
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Executes the given
|
||||
* command.
|
||||
* Executes the given command.
|
||||
*
|
||||
* The command can take any number of arguments, and the supported
|
||||
* arguments will vary based on the command. For custom commands, this
|
||||
@@ -78,7 +77,7 @@ export default class JoplinCommands {
|
||||
}
|
||||
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Registers a new command.
|
||||
* Registers a new command.
|
||||
*
|
||||
* ```typescript
|
||||
* // Register a new commmand called "testCommand1"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable multiline-comment-style */
|
||||
|
||||
import shim from '../../../shim';
|
||||
import Plugin from '../Plugin';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
export interface Implementation {
|
||||
injectCustomStyles(elementId: string, cssFilePath: string): Promise<void>;
|
||||
@@ -36,7 +36,7 @@ export default class JoplinWindow {
|
||||
* for an example.
|
||||
*/
|
||||
public async loadNoteCssFile(filePath: string) {
|
||||
const cssString = await fs.readFile(filePath, 'utf8');
|
||||
const cssString = await shim.fsDriver().readFile(filePath, 'utf8');
|
||||
|
||||
this.store_.dispatch({
|
||||
type: 'CUSTOM_CSS_APPEND',
|
||||
|
||||
@@ -227,6 +227,8 @@ export interface VersionInfo {
|
||||
version: string;
|
||||
profileVersion: number;
|
||||
syncVersion: number;
|
||||
|
||||
platform: 'desktop'|'mobile';
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { Draft } from 'immer';
|
||||
import { ContainerType } from './WebviewController';
|
||||
import { ButtonSpec } from './api/types';
|
||||
|
||||
export interface ViewInfo {
|
||||
view: any;
|
||||
plugin: any;
|
||||
}
|
||||
|
||||
interface PluginViewState {
|
||||
export interface PluginViewState {
|
||||
id: string;
|
||||
type: string;
|
||||
opened: boolean;
|
||||
buttons: ButtonSpec[];
|
||||
fitToContent?: boolean;
|
||||
scripts?: string[];
|
||||
html?: string;
|
||||
commandName?: string;
|
||||
location?: string;
|
||||
containerType: ContainerType;
|
||||
}
|
||||
|
||||
interface PluginViewStates {
|
||||
@@ -29,6 +34,11 @@ interface PluginState {
|
||||
views: PluginViewStates;
|
||||
}
|
||||
|
||||
export interface ViewInfo {
|
||||
view: PluginViewState;
|
||||
plugin: PluginState;
|
||||
}
|
||||
|
||||
export interface PluginStates {
|
||||
[key: string]: PluginState;
|
||||
}
|
||||
@@ -181,6 +191,10 @@ const reducer = (draftRoot: Draft<any>, action: any) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PLUGIN_UNLOAD':
|
||||
delete draft.plugins[action.pluginId];
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `In plugin reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||
|
||||
Reference in New Issue
Block a user