mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Plugins: Improved note change event handling. Also added tests and improved debugging plugins.
This commit is contained in:
parent
eed3dc8617
commit
05e9000087
@ -86,6 +86,9 @@ packages/app-cli/tests/models_Note.js.map
|
||||
packages/app-cli/tests/models_Setting.d.ts
|
||||
packages/app-cli/tests/models_Setting.js
|
||||
packages/app-cli/tests/models_Setting.js.map
|
||||
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.d.ts
|
||||
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js
|
||||
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js.map
|
||||
@ -1124,6 +1127,9 @@ packages/lib/services/plugins/utils/executeSandboxCall.js.map
|
||||
packages/lib/services/plugins/utils/loadContentScripts.d.ts
|
||||
packages/lib/services/plugins/utils/loadContentScripts.js
|
||||
packages/lib/services/plugins/utils/loadContentScripts.js.map
|
||||
packages/lib/services/plugins/utils/makeListener.d.ts
|
||||
packages/lib/services/plugins/utils/makeListener.js
|
||||
packages/lib/services/plugins/utils/makeListener.js.map
|
||||
packages/lib/services/plugins/utils/manifestFromObject.d.ts
|
||||
packages/lib/services/plugins/utils/manifestFromObject.js
|
||||
packages/lib/services/plugins/utils/manifestFromObject.js.map
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -77,6 +77,9 @@ packages/app-cli/tests/models_Note.js.map
|
||||
packages/app-cli/tests/models_Setting.d.ts
|
||||
packages/app-cli/tests/models_Setting.js
|
||||
packages/app-cli/tests/models_Setting.js.map
|
||||
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.d.ts
|
||||
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js
|
||||
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js.map
|
||||
@ -1115,6 +1118,9 @@ packages/lib/services/plugins/utils/executeSandboxCall.js.map
|
||||
packages/lib/services/plugins/utils/loadContentScripts.d.ts
|
||||
packages/lib/services/plugins/utils/loadContentScripts.js
|
||||
packages/lib/services/plugins/utils/loadContentScripts.js.map
|
||||
packages/lib/services/plugins/utils/makeListener.d.ts
|
||||
packages/lib/services/plugins/utils/makeListener.js
|
||||
packages/lib/services/plugins/utils/makeListener.js.map
|
||||
packages/lib/services/plugins/utils/manifestFromObject.d.ts
|
||||
packages/lib/services/plugins/utils/manifestFromObject.js
|
||||
packages/lib/services/plugins/utils/manifestFromObject.js.map
|
||||
|
@ -37,7 +37,8 @@
|
||||
"tsc": "lerna run tsc --stream --parallel",
|
||||
"updateIgnored": "gulp updateIgnoredTypeScriptBuild",
|
||||
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
|
||||
"watch": "lerna run watch --stream --parallel"
|
||||
"watch": "lerna run watch --stream --parallel",
|
||||
"i": "lerna add --no-bootstrap --scope"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
@ -5,6 +5,7 @@ import BasePluginRunner from '@joplin/lib/services/plugins/BasePluginRunner';
|
||||
import executeSandboxCall from '@joplin/lib/services/plugins/utils/executeSandboxCall';
|
||||
import Global from '@joplin/lib/services/plugins/api/Global';
|
||||
import mapEventHandlersToIds, { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandlersToIds';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
|
||||
function createConsoleWrapper(pluginId: string) {
|
||||
const wrapper: any = {};
|
||||
@ -31,6 +32,7 @@ function createConsoleWrapper(pluginId: string) {
|
||||
export default class PluginRunner extends BasePluginRunner {
|
||||
|
||||
private eventHandlers_: EventHandlers = {};
|
||||
private activeSandboxCalls_: any = {};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -45,7 +47,13 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
|
||||
private newSandboxProxy(pluginId: string, sandbox: Global) {
|
||||
const target = async (path: string, args: any[]) => {
|
||||
return executeSandboxCall(pluginId, sandbox, `joplin.${path}`, mapEventHandlersToIds(args, this.eventHandlers_), this.eventHandler);
|
||||
const callId = `${pluginId}::${path}::${uuid.createNano()}`;
|
||||
this.activeSandboxCalls_[callId] = true;
|
||||
const promise = executeSandboxCall(pluginId, sandbox, `joplin.${path}`, mapEventHandlersToIds(args, this.eventHandlers_), this.eventHandler);
|
||||
promise.finally(() => {
|
||||
delete this.activeSandboxCalls_[callId];
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
return {
|
||||
@ -69,10 +77,25 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
vm.runInContext(plugin.scriptText, vmSandbox);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
// this.logger().error(`In plugin ${plugin.id}:`, error);
|
||||
// return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async waitForSandboxCalls(): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
return new Promise((resolve: Function, reject: Function) => {
|
||||
const iid = setInterval(() => {
|
||||
if (!Object.keys(this.activeSandboxCalls_).length) {
|
||||
clearInterval(iid);
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > 4000) {
|
||||
clearInterval(iid);
|
||||
reject(new Error(`Timeout while waiting for sandbox calls to complete: ${JSON.stringify(this.activeSandboxCalls_)}`));
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
|
||||
const { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('../../../test-utils');
|
||||
const Note = require('@joplin/lib/models/Note');
|
||||
const Folder = require('@joplin/lib/models/Folder');
|
||||
const ItemChange = require('@joplin/lib/models/ItemChange');
|
||||
|
||||
describe('JoplinWorkspace', () => {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await afterEachCleanUp();
|
||||
});
|
||||
|
||||
test('should listen to noteChange events', async () => {
|
||||
const service = new newPluginService() as PluginService;
|
||||
|
||||
const pluginScript = newPluginScript(`
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.workspace.onNoteChange(async (event) => {
|
||||
await joplin.data.post(['folders'], null, { title: JSON.stringify(event) });
|
||||
});
|
||||
},
|
||||
});
|
||||
`);
|
||||
|
||||
const note = await Note.save({});
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
|
||||
await service.runPlugin(plugin);
|
||||
|
||||
await Note.save({ id: note.id, body: 'testing' });
|
||||
await ItemChange.waitForAllSaved();
|
||||
|
||||
const folder = (await Folder.all())[0];
|
||||
|
||||
const result: any = JSON.parse(folder.title);
|
||||
|
||||
expect(result.id).toBe(note.id);
|
||||
expect(result.event).toBe(ItemChange.TYPE_UPDATE);
|
||||
|
||||
await service.destroy();
|
||||
});
|
||||
|
||||
});
|
@ -22,9 +22,7 @@ function newPluginService(appVersion: string = '1.4') {
|
||||
service.initialize(
|
||||
appVersion,
|
||||
{
|
||||
joplin: {
|
||||
workspace: {},
|
||||
},
|
||||
joplin: {},
|
||||
},
|
||||
runner,
|
||||
{
|
||||
|
@ -20,4 +20,4 @@
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ const KeychainServiceDriver = require('@joplin/lib/services/keychain/KeychainSer
|
||||
const KeychainServiceDriverDummy = require('@joplin/lib/services/keychain/KeychainServiceDriver.dummy').default;
|
||||
const md5 = require('md5');
|
||||
const S3 = require('aws-sdk/clients/s3');
|
||||
const PluginRunner = require('../app/services/plugins/PluginRunner').default;
|
||||
const PluginService = require('@joplin/lib/services/plugins/PluginService').default;
|
||||
const { Dirnames } = require('@joplin/lib/services/synchronizer/utils/types');
|
||||
const sharp = require('sharp');
|
||||
|
||||
@ -677,6 +679,39 @@ async function createTempDir() {
|
||||
return tempDirPath;
|
||||
}
|
||||
|
||||
function newPluginService(appVersion = '1.4') {
|
||||
const runner = new PluginRunner();
|
||||
const service = new PluginService();
|
||||
service.initialize(
|
||||
appVersion,
|
||||
{
|
||||
joplin: {},
|
||||
},
|
||||
runner,
|
||||
{
|
||||
dispatch: () => {},
|
||||
getState: () => {},
|
||||
}
|
||||
);
|
||||
return service;
|
||||
}
|
||||
|
||||
function newPluginScript(script) {
|
||||
return `
|
||||
/* joplin-manifest:
|
||||
{
|
||||
"id": "org.joplinapp.plugins.PluginTest",
|
||||
"manifest_version": 1,
|
||||
"app_min_version": "1.4",
|
||||
"name": "JS Bundle test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
|
||||
${script}
|
||||
`;
|
||||
}
|
||||
|
||||
// TODO: Update for Jest
|
||||
|
||||
// function mockDate(year, month, day, tick) {
|
||||
@ -757,4 +792,4 @@ class TestApp extends BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
|
||||
module.exports = { newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
|
||||
|
@ -1,11 +1,5 @@
|
||||
// import { EditorCommand } from '@joplin/lib/services/plugins/api/types';
|
||||
|
||||
import bridge from '../bridge';
|
||||
|
||||
// interface JoplinWorkspace {
|
||||
// execEditorCommand(command:EditorCommand):Promise<string>
|
||||
// }
|
||||
|
||||
interface JoplinViewsDialogs {
|
||||
showMessageBox(message: string): Promise<number>;
|
||||
}
|
||||
@ -15,7 +9,6 @@ interface JoplinViews {
|
||||
}
|
||||
|
||||
interface Joplin {
|
||||
// workspace: JoplinWorkspace;
|
||||
views: JoplinViews;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,17 @@ const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
const logger = Logger.create('PluginRunner');
|
||||
|
||||
// Electron error messages are useless so wrap the renderer call and print
|
||||
// additional information when an error occurs.
|
||||
function ipcRendererSend(message: string, args: any) {
|
||||
try {
|
||||
return ipcRenderer.send(message, args);
|
||||
} catch (error) {
|
||||
logger.error('Could not send IPC message:', message, ': ', args, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
enum PluginMessageTarget {
|
||||
MainWindow = 'mainWindow',
|
||||
Plugin = 'plugin',
|
||||
@ -46,7 +57,7 @@ function mapEventIdsToHandlers(pluginId: string, arg: any) {
|
||||
callbackPromises[callbackId] = { resolve, reject };
|
||||
});
|
||||
|
||||
ipcRenderer.send('pluginMessage', {
|
||||
ipcRendererSend('pluginMessage', {
|
||||
callbackId: callbackId,
|
||||
target: PluginMessageTarget.Plugin,
|
||||
pluginId: pluginId,
|
||||
@ -132,7 +143,7 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
|
||||
// Don't log complete HTML code, which can be long, for setHtml calls
|
||||
const debugMappedArgs = fullPath.includes('setHtml') ? '<hidden>' : mappedArgs;
|
||||
logger.debug(`Got message (3): ${fullPath}: ${debugMappedArgs}`);
|
||||
logger.debug(`Got message (3): ${fullPath}`, debugMappedArgs);
|
||||
|
||||
let result: any = null;
|
||||
let error: any = null;
|
||||
@ -142,7 +153,7 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
error = e ? e : new Error('Unknown error');
|
||||
}
|
||||
|
||||
ipcRenderer.send('pluginMessage', {
|
||||
ipcRendererSend('pluginMessage', {
|
||||
target: PluginMessageTarget.Plugin,
|
||||
pluginId: plugin.id,
|
||||
pluginCallbackId: message.callbackId,
|
||||
|
@ -1,13 +1,5 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="./plugin_index.js"></script>
|
||||
<script>
|
||||
// joplin.plugins.register({
|
||||
// onStart: async function() {
|
||||
// alert('PLUGIN STARTED');
|
||||
// },
|
||||
// });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
</head>
|
||||
</html>
|
@ -3,6 +3,15 @@
|
||||
const sandboxProxy = require('../../node_modules/@joplin/lib/services/plugins/sandboxProxy.js').default;
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
const ipcRendererSend = (message, args) => {
|
||||
try {
|
||||
return ipcRenderer.send(message, args);
|
||||
} catch (error) {
|
||||
console.error('Could not send IPC message:', message, ': ', args, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const pluginId = urlParams.get('pluginId');
|
||||
|
||||
@ -42,7 +51,7 @@
|
||||
callbackPromises[callbackId] = { resolve, reject };
|
||||
});
|
||||
|
||||
ipcRenderer.send('pluginMessage', {
|
||||
ipcRendererSend('pluginMessage', {
|
||||
target: 'mainWindow',
|
||||
pluginId: pluginId,
|
||||
callbackId: callbackId,
|
||||
@ -71,7 +80,7 @@
|
||||
}
|
||||
|
||||
if (message.callbackId) {
|
||||
ipcRenderer.send('pluginMessage', {
|
||||
ipcRendererSend('pluginMessage', {
|
||||
target: 'mainWindow',
|
||||
pluginId: pluginId,
|
||||
mainWindowCallbackId: message.callbackId,
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"generator-node": {
|
||||
"promptValues": {
|
||||
"authorName": "Laurent Cozic",
|
||||
"authorEmail": "laurent@cozic.net",
|
||||
"authorUrl": ""
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Laurent Cozic <laurent@cozic.net>
|
||||
Copyright (c) 2020 Laurent Cozic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,6 +1,6 @@
|
||||
const events = require('events');
|
||||
|
||||
class EventManager {
|
||||
export class EventManager {
|
||||
|
||||
private emitter_: any;
|
||||
private appStatePrevious_: any;
|
||||
|
13
packages/lib/jest.config.js
Normal file
13
packages/lib/jest.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
testMatch: [
|
||||
'**/*.test.js',
|
||||
],
|
||||
|
||||
testPathIgnorePatterns: [
|
||||
'<rootDir>/node_modules/',
|
||||
'<rootDir>/rnInjectedJs/',
|
||||
'<rootDir>/vendor/',
|
||||
],
|
||||
|
||||
testEnvironment: 'node',
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
const BaseModel = require('../BaseModel').default;
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
const shim = require('../shim').default;
|
||||
const eventManager = require('../eventManager').default;
|
||||
|
||||
class ItemChange extends BaseModel {
|
||||
static tableName() {
|
||||
@ -22,10 +23,25 @@ class ItemChange extends BaseModel {
|
||||
const release = await ItemChange.addChangeMutex_.acquire();
|
||||
|
||||
try {
|
||||
await this.db().transactionExecBatch([{ sql: 'DELETE FROM item_changes WHERE item_id = ?', params: [itemId] }, { sql: 'INSERT INTO item_changes (item_type, item_id, type, source, created_time, before_change_item) VALUES (?, ?, ?, ?, ?, ?)', params: [itemType, itemId, type, changeSource, Date.now(), beforeChangeItemJson] }]);
|
||||
await this.db().transactionExecBatch([
|
||||
{
|
||||
sql: 'DELETE FROM item_changes WHERE item_id = ?',
|
||||
params: [itemId],
|
||||
},
|
||||
{
|
||||
sql: 'INSERT INTO item_changes (item_type, item_id, type, source, created_time, before_change_item) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
params: [itemType, itemId, type, changeSource, Date.now(), beforeChangeItemJson],
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
release();
|
||||
ItemChange.saveCalls_.pop();
|
||||
|
||||
eventManager.emit('itemChange', {
|
||||
itemType: itemType,
|
||||
itemId: itemId,
|
||||
eventType: type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +78,6 @@ ItemChange.TYPE_DELETE = 3;
|
||||
|
||||
ItemChange.SOURCE_UNSPECIFIED = 1;
|
||||
ItemChange.SOURCE_SYNC = 2;
|
||||
ItemChange.SOURCE_DECRYPTION = 2;
|
||||
ItemChange.SOURCE_DECRYPTION = 2; // CAREFUL - SAME ID AS SOURCE_SYNC!
|
||||
|
||||
module.exports = ItemChange;
|
||||
|
3651
packages/lib/package-lock.json
generated
3651
packages/lib/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "1.0.9",
|
||||
"description": "> TODO: description",
|
||||
"author": "Laurent Cozic <laurent@cozic.net>",
|
||||
"description": "Joplin Core library",
|
||||
"author": "Laurent Cozic",
|
||||
"homepage": "",
|
||||
"license": "ISC",
|
||||
"publishConfig": {
|
||||
@ -11,9 +11,13 @@
|
||||
"scripts": {
|
||||
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
||||
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
||||
"generatePluginTypes": "rm -rf ./plugin_types && node node_modules/typescript/bin/tsc --declaration --declarationDir ./plugin_types --project tsconfig.json"
|
||||
"generatePluginTypes": "rm -rf ./plugin_types && node node_modules/typescript/bin/tsc --declaration --declarationDir ./plugin_types --project tsconfig.json",
|
||||
"test": "jest",
|
||||
"test-ci": "npm run test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.15",
|
||||
"jest": "^26.6.3",
|
||||
"@types/node": "^14.14.6",
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
|
@ -8,4 +8,8 @@ export default abstract class BasePluginRunner extends BaseService {
|
||||
throw new Error(`Not implemented: ${plugin} / ${sandbox}`);
|
||||
}
|
||||
|
||||
public async waitForSandboxCalls(): Promise<void> {
|
||||
throw new Error('Not implemented: waitForSandboxCalls');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -385,4 +385,8 @@ export default class PluginService extends BaseService {
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
public async destroy() {
|
||||
await this.runner_.waitForSandboxCalls();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export default class Joplin {
|
||||
constructor(implementation: any, plugin: Plugin, store: any) {
|
||||
this.data_ = new JoplinData();
|
||||
this.plugins_ = new JoplinPlugins(plugin);
|
||||
this.workspace_ = new JoplinWorkspace(implementation.workspace, store);
|
||||
this.workspace_ = new JoplinWorkspace(store);
|
||||
this.filters_ = new JoplinFilters();
|
||||
this.commands_ = new JoplinCommands();
|
||||
this.views_ = new JoplinViews(implementation.views, plugin, store);
|
||||
|
@ -1,13 +1,31 @@
|
||||
import { ModelType } from '../../../BaseModel';
|
||||
import eventManager from '../../../eventManager';
|
||||
import makeListener from '../utils/makeListener';
|
||||
import { Disposable } from './types';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
const Note = require('../../../models/Note');
|
||||
|
||||
enum ItemChangeEventType {
|
||||
Create = 1,
|
||||
Update = 2,
|
||||
Delete = 3,
|
||||
}
|
||||
|
||||
interface ItemChangeEvent {
|
||||
id: string;
|
||||
event: ItemChangeEventType;
|
||||
}
|
||||
|
||||
type ItemChangeHandler = (event: ItemChangeEvent)=> void;
|
||||
|
||||
/**
|
||||
* The workspace service provides access to all the parts of Joplin that are being worked on - i.e. the currently selected notes or notebooks as well
|
||||
* as various related events, such as when a new note is selected, or when the note content changes.
|
||||
* The workspace service provides access to all the parts of Joplin that
|
||||
* are being worked on - i.e. the currently selected notes or notebooks as
|
||||
* well as various related events, such as when a new note is selected, or
|
||||
* when the note content changes.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins)
|
||||
*/
|
||||
@ -15,45 +33,68 @@ export default class JoplinWorkspace {
|
||||
// TODO: unregister events when plugin is closed or disabled
|
||||
|
||||
private store: any;
|
||||
// private implementation_:any;
|
||||
|
||||
constructor(_implementation: any, store: any) {
|
||||
constructor(store: any) {
|
||||
this.store = store;
|
||||
// this.implementation_ = implementation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new note or notes are selected.
|
||||
*/
|
||||
async onNoteSelectionChange(callback: Function) {
|
||||
public async onNoteSelectionChange(callback: Function): Promise<Disposable> {
|
||||
eventManager.appStateOn('selectedNoteIds', callback);
|
||||
|
||||
return {};
|
||||
|
||||
// return {
|
||||
// dispose: () => {
|
||||
// eventManager.appStateOff('selectedNoteIds', callback);
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the content of a note changes.
|
||||
* @deprecated Use `onNoteChange()` instead, which is reliably triggered whenever the note content, or any note property changes.
|
||||
*/
|
||||
public async onNoteContentChange(callback: Function) {
|
||||
eventManager.on('noteContentChange', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the content of a note changes.
|
||||
*/
|
||||
async onNoteContentChange(callback: Function) {
|
||||
eventManager.on('noteContentChange', callback);
|
||||
public async onNoteChange(handler: ItemChangeHandler): Promise<Disposable> {
|
||||
const wrapperHandler = (event: any) => {
|
||||
if (event.itemType !== ModelType.Note) return;
|
||||
|
||||
handler({
|
||||
id: event.itemId,
|
||||
event: event.eventType,
|
||||
});
|
||||
};
|
||||
|
||||
return makeListener(eventManager, 'itemChange', wrapperHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an alarm associated with a to-do is triggered.
|
||||
*/
|
||||
async onNoteAlarmTrigger(callback: Function) {
|
||||
eventManager.on('noteAlarmTrigger', callback);
|
||||
public async onNoteAlarmTrigger(callback: Function): Promise<Disposable> {
|
||||
return makeListener(eventManager, 'noteAlarmTrigger', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the synchronisation process has finished.
|
||||
*/
|
||||
async onSyncComplete(callback: Function) {
|
||||
eventManager.on('syncComplete', callback);
|
||||
public async onSyncComplete(callback: Function): Promise<Disposable> {
|
||||
return makeListener(eventManager, 'syncComplete', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently selected note
|
||||
*/
|
||||
async selectedNote(): Promise<any> {
|
||||
public async selectedNote(): Promise<any> {
|
||||
const noteIds = this.store.getState().selectedNoteIds;
|
||||
if (noteIds.length !== 1) { return null; }
|
||||
return Note.load(noteIds[0]);
|
||||
@ -62,7 +103,7 @@ export default class JoplinWorkspace {
|
||||
/**
|
||||
* Gets the IDs of the selected notes (can be zero, one, or many). Use the data API to retrieve information about these notes.
|
||||
*/
|
||||
async selectedNoteIds(): Promise<string[]> {
|
||||
public async selectedNoteIds(): Promise<string[]> {
|
||||
return this.store.getState().selectedNoteIds.slice();
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +189,10 @@ export interface Script {
|
||||
onStart?(event: any): Promise<void>;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
// dispose():void;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Menu types
|
||||
// =================================================================
|
||||
|
22
packages/lib/services/plugins/utils/makeListener.ts
Normal file
22
packages/lib/services/plugins/utils/makeListener.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { EventManager } from '../../../eventManager';
|
||||
import { Disposable } from '../api/types';
|
||||
|
||||
export default function(eventManager: EventManager, eventName: string, callback: Function): Disposable {
|
||||
eventManager.on(eventName, callback);
|
||||
|
||||
return {};
|
||||
|
||||
// Note: It is not currently possible to return an object with a dispose() function because function cannot be serialized when sent via IPC. So it would need send callback mechanism as for plugin functions.
|
||||
//
|
||||
// Or it could return a simple string ID, which can then be used to stop listening to the event. eg:
|
||||
//
|
||||
// const listenerId = await joplin.workspace.onNoteChange(() => {});
|
||||
// // ... later:
|
||||
// await joplin.workspace.removeListener(listenerId);
|
||||
|
||||
// return {
|
||||
// dispose: () => {
|
||||
// eventManager.off(eventName, callback);
|
||||
// }
|
||||
// };
|
||||
}
|
Loading…
Reference in New Issue
Block a user