All: Add support for application plugins (#3257)

pull/3777/head^2
Laurent 2020-10-09 18:35:46 +01:00 committed by GitHub
parent 833fb1264f
commit fe41d37f8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
804 changed files with 95622 additions and 5307 deletions

View File

@ -59,15 +59,25 @@ Tools/node_modules
Tools/PortableAppsLauncher
Modules/TinyMCE/IconPack/postinstall.js
Modules/TinyMCE/langs/
CliClient/build/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/build/LinkSelector.js
CliClient/app/services/plugins/PluginRunner.js
CliClient/tests/models_Setting.js
CliClient/tests/services_CommandService.js
CliClient/tests/services_InteropService.js
CliClient/tests/services_PluginService.js
CliClient/tests/services/plugins/sandboxProxy.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/app.js
ElectronClient/bridge.js
ElectronClient/commands/copyDevCommand.js
ElectronClient/commands/focusElement.js
ElectronClient/commands/startExternalEditing.js
ElectronClient/commands/stopExternalEditing.js
ElectronClient/ElectronAppWrapper.js
ElectronClient/global.d.js
ElectronClient/gui/Button/Button.js
ElectronClient/gui/ConfigScreen/ButtonBar.js
@ -103,6 +113,7 @@ ElectronClient/gui/MainScreen/commands/toggleNoteList.js
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
@ -138,7 +149,7 @@ ElectronClient/gui/NoteEditor/utils/useFormNote.js
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
ElectronClient/gui/NoteEditor/utils/useNoteToolbarButtons.js
ElectronClient/gui/NoteEditor/utils/usePluginServiceRegistration.js
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
ElectronClient/gui/NoteList/commands/focusElementNoteList.js
@ -154,6 +165,7 @@ ElectronClient/gui/ResizableLayout/hooks/useWindowResizeEvent.js
ElectronClient/gui/ResizableLayout/ResizableLayout.js
ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/Root_UpgradeSyncTarget.js
ElectronClient/gui/Root.js
ElectronClient/gui/SearchBar/hooks/useSearch.js
ElectronClient/gui/SearchBar/SearchBar.js
ElectronClient/gui/SearchBar/styles/index.js
@ -169,12 +181,25 @@ ElectronClient/gui/ToggleEditorsButton/ToggleEditorsButton.js
ElectronClient/gui/ToolbarBase.js
ElectronClient/gui/ToolbarButton/styles/index.js
ElectronClient/gui/ToolbarButton/ToolbarButton.js
ElectronClient/gui/utils/NoteListUtils.js
ElectronClient/InteropServiceHelper.js
ElectronClient/services/bridge.js
ElectronClient/services/plugins/hooks/useThemeCss.js
ElectronClient/services/plugins/hooks/useViewIsReady.js
ElectronClient/services/plugins/PlatformImplementation.js
ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
ReactNativeClient/lib/commands/synchronize.js
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/errorUtils.js
ReactNativeClient/lib/eventManager.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
@ -184,18 +209,79 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/locale.js
ReactNativeClient/lib/Logger.js
ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/reducer.js
ReactNativeClient/lib/services/AlarmService.js
ReactNativeClient/lib/services/AlarmServiceDriver.android.js
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
ReactNativeClient/lib/services/BaseService.js
ReactNativeClient/lib/services/BooleanExpression.js
ReactNativeClient/lib/services/commands/MenuUtils.js
ReactNativeClient/lib/services/commands/propsHaveChanged.js
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
ReactNativeClient/lib/services/CommandService.js
ReactNativeClient/lib/services/contextkey/contextkey.js
ReactNativeClient/lib/services/debug/populateDatabase.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Base.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Custom.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Html.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Jex.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Md.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Raw.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Base.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Custom.js
ReactNativeClient/lib/services/interop/InteropService_Importer_EnexToHtml.js
ReactNativeClient/lib/services/interop/InteropService_Importer_EnexToMd.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Jex.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Md.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Raw.js
ReactNativeClient/lib/services/interop/InteropService.js
ReactNativeClient/lib/services/interop/types.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js
ReactNativeClient/lib/services/KeymapService.js
ReactNativeClient/lib/services/plugins/api/Global.js
ReactNativeClient/lib/services/plugins/api/Joplin.js
ReactNativeClient/lib/services/plugins/api/JoplinCommands.js
ReactNativeClient/lib/services/plugins/api/JoplinData.js
ReactNativeClient/lib/services/plugins/api/JoplinFilters.js
ReactNativeClient/lib/services/plugins/api/JoplinInterop.js
ReactNativeClient/lib/services/plugins/api/JoplinPlugins.js
ReactNativeClient/lib/services/plugins/api/JoplinSettings.js
ReactNativeClient/lib/services/plugins/api/JoplinViews.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsDialogs.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsMenuItems.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsPanels.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsToolbarButtons.js
ReactNativeClient/lib/services/plugins/api/JoplinWorkspace.js
ReactNativeClient/lib/services/plugins/api/types.js
ReactNativeClient/lib/services/plugins/BasePluginRunner.js
ReactNativeClient/lib/services/plugins/MenuItemController.js
ReactNativeClient/lib/services/plugins/Plugin.js
ReactNativeClient/lib/services/plugins/PluginService.js
ReactNativeClient/lib/services/plugins/reducer.js
ReactNativeClient/lib/services/plugins/sandboxProxy.js
ReactNativeClient/lib/services/plugins/ToolbarButtonController.js
ReactNativeClient/lib/services/plugins/utils/createViewHandle.js
ReactNativeClient/lib/services/plugins/utils/executeSandboxCall.js
ReactNativeClient/lib/services/plugins/utils/manifestFromObject.js
ReactNativeClient/lib/services/plugins/utils/mapEventHandlersToIds.js
ReactNativeClient/lib/services/plugins/utils/types.js
ReactNativeClient/lib/services/plugins/ViewController.js
ReactNativeClient/lib/services/plugins/WebviewController.js
ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/Api.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
@ -209,6 +295,8 @@ ReactNativeClient/lib/services/synchronizer/utils/types.js
ReactNativeClient/lib/services/UndoRedoService.js
ReactNativeClient/lib/ShareExtension.js
ReactNativeClient/lib/shareHandler.js
ReactNativeClient/lib/shim.js
ReactNativeClient/lib/Synchronizer.js
ReactNativeClient/lib/theme.js
ReactNativeClient/lib/themes/aritimDark.js
ReactNativeClient/lib/themes/dark.js
@ -219,6 +307,7 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/setUpQuickActions.js

View File

@ -48,8 +48,9 @@ module.exports = {
// -------------------------------
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'no-unused-vars': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }],
'@typescript-eslint/explicit-member-accessibility': 'off',
'no-constant-condition': 0,
'no-prototype-builtins': 0,
// This error is always a false positive so far since it detects
@ -121,4 +122,15 @@ module.exports = {
'react-hooks',
'import',
],
'overrides': [
{
// enable the rule specifically for TypeScript files
'files': ['*.ts', '*.tsx'],
'rules': {
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we
// make everything public which is not great. New code however should specify member accessibility.
'@typescript-eslint/explicit-member-accessibility': ['warn'],
},
},
],
};

94
.gitignore vendored
View File

@ -52,15 +52,26 @@ Tools/commit_hook.txt
*.map
ReactNativeClient/lib/sql-extensions/spellfix.so
ReactNativeClient/lib/sql-extensions/spellfix.dylib
CliClient/build/
plugin_types/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/build/LinkSelector.js
CliClient/app/services/plugins/PluginRunner.js
CliClient/tests/models_Setting.js
CliClient/tests/services_CommandService.js
CliClient/tests/services_InteropService.js
CliClient/tests/services_PluginService.js
CliClient/tests/services/plugins/sandboxProxy.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/app.js
ElectronClient/bridge.js
ElectronClient/commands/copyDevCommand.js
ElectronClient/commands/focusElement.js
ElectronClient/commands/startExternalEditing.js
ElectronClient/commands/stopExternalEditing.js
ElectronClient/ElectronAppWrapper.js
ElectronClient/global.d.js
ElectronClient/gui/Button/Button.js
ElectronClient/gui/ConfigScreen/ButtonBar.js
@ -96,6 +107,7 @@ ElectronClient/gui/MainScreen/commands/toggleNoteList.js
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
@ -131,7 +143,7 @@ ElectronClient/gui/NoteEditor/utils/useFormNote.js
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
ElectronClient/gui/NoteEditor/utils/useNoteToolbarButtons.js
ElectronClient/gui/NoteEditor/utils/usePluginServiceRegistration.js
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
ElectronClient/gui/NoteList/commands/focusElementNoteList.js
@ -147,6 +159,7 @@ ElectronClient/gui/ResizableLayout/hooks/useWindowResizeEvent.js
ElectronClient/gui/ResizableLayout/ResizableLayout.js
ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/Root_UpgradeSyncTarget.js
ElectronClient/gui/Root.js
ElectronClient/gui/SearchBar/hooks/useSearch.js
ElectronClient/gui/SearchBar/SearchBar.js
ElectronClient/gui/SearchBar/styles/index.js
@ -162,12 +175,25 @@ ElectronClient/gui/ToggleEditorsButton/ToggleEditorsButton.js
ElectronClient/gui/ToolbarBase.js
ElectronClient/gui/ToolbarButton/styles/index.js
ElectronClient/gui/ToolbarButton/ToolbarButton.js
ElectronClient/gui/utils/NoteListUtils.js
ElectronClient/InteropServiceHelper.js
ElectronClient/services/bridge.js
ElectronClient/services/plugins/hooks/useThemeCss.js
ElectronClient/services/plugins/hooks/useViewIsReady.js
ElectronClient/services/plugins/PlatformImplementation.js
ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
ReactNativeClient/lib/commands/synchronize.js
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/errorUtils.js
ReactNativeClient/lib/eventManager.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
@ -177,18 +203,79 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/locale.js
ReactNativeClient/lib/Logger.js
ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/reducer.js
ReactNativeClient/lib/services/AlarmService.js
ReactNativeClient/lib/services/AlarmServiceDriver.android.js
ReactNativeClient/lib/services/AlarmServiceDriver.ios.js
ReactNativeClient/lib/services/AlarmServiceDriverNode.js
ReactNativeClient/lib/services/BaseService.js
ReactNativeClient/lib/services/BooleanExpression.js
ReactNativeClient/lib/services/commands/MenuUtils.js
ReactNativeClient/lib/services/commands/propsHaveChanged.js
ReactNativeClient/lib/services/commands/ToolbarButtonUtils.js
ReactNativeClient/lib/services/CommandService.js
ReactNativeClient/lib/services/contextkey/contextkey.js
ReactNativeClient/lib/services/debug/populateDatabase.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Base.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Custom.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Html.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Jex.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Md.js
ReactNativeClient/lib/services/interop/InteropService_Exporter_Raw.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Base.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Custom.js
ReactNativeClient/lib/services/interop/InteropService_Importer_EnexToHtml.js
ReactNativeClient/lib/services/interop/InteropService_Importer_EnexToMd.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Jex.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Md.js
ReactNativeClient/lib/services/interop/InteropService_Importer_Raw.js
ReactNativeClient/lib/services/interop/InteropService.js
ReactNativeClient/lib/services/interop/types.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js
ReactNativeClient/lib/services/KeymapService.js
ReactNativeClient/lib/services/plugins/api/Global.js
ReactNativeClient/lib/services/plugins/api/Joplin.js
ReactNativeClient/lib/services/plugins/api/JoplinCommands.js
ReactNativeClient/lib/services/plugins/api/JoplinData.js
ReactNativeClient/lib/services/plugins/api/JoplinFilters.js
ReactNativeClient/lib/services/plugins/api/JoplinInterop.js
ReactNativeClient/lib/services/plugins/api/JoplinPlugins.js
ReactNativeClient/lib/services/plugins/api/JoplinSettings.js
ReactNativeClient/lib/services/plugins/api/JoplinViews.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsDialogs.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsMenuItems.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsPanels.js
ReactNativeClient/lib/services/plugins/api/JoplinViewsToolbarButtons.js
ReactNativeClient/lib/services/plugins/api/JoplinWorkspace.js
ReactNativeClient/lib/services/plugins/api/types.js
ReactNativeClient/lib/services/plugins/BasePluginRunner.js
ReactNativeClient/lib/services/plugins/MenuItemController.js
ReactNativeClient/lib/services/plugins/Plugin.js
ReactNativeClient/lib/services/plugins/PluginService.js
ReactNativeClient/lib/services/plugins/reducer.js
ReactNativeClient/lib/services/plugins/sandboxProxy.js
ReactNativeClient/lib/services/plugins/ToolbarButtonController.js
ReactNativeClient/lib/services/plugins/utils/createViewHandle.js
ReactNativeClient/lib/services/plugins/utils/executeSandboxCall.js
ReactNativeClient/lib/services/plugins/utils/manifestFromObject.js
ReactNativeClient/lib/services/plugins/utils/mapEventHandlersToIds.js
ReactNativeClient/lib/services/plugins/utils/types.js
ReactNativeClient/lib/services/plugins/ViewController.js
ReactNativeClient/lib/services/plugins/WebviewController.js
ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/Api.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
@ -202,6 +289,8 @@ ReactNativeClient/lib/services/synchronizer/utils/types.js
ReactNativeClient/lib/services/UndoRedoService.js
ReactNativeClient/lib/ShareExtension.js
ReactNativeClient/lib/shareHandler.js
ReactNativeClient/lib/shim.js
ReactNativeClient/lib/Synchronizer.js
ReactNativeClient/lib/theme.js
ReactNativeClient/lib/themes/aritimDark.js
ReactNativeClient/lib/themes/dark.js
@ -212,6 +301,7 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/setUpQuickActions.js

View File

@ -96,4 +96,4 @@ The Markdown renderer is located under ReactNativeClient/lib/joplin-renderer. Wh
# Troubleshooting
Please read for the [Build Troubleshooting Document](https://github.com/laurent22/joplin/blob/master/readme/build_troubleshooting.md) for various tips on how to get the build working.
Please read for the [Build Troubleshooting Document](https://github.com/laurent22/joplin/blob/dev/readme/build_troubleshooting.md) for various tips on how to get the build working.

View File

@ -1,4 +1,4 @@
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const { netUtils } = require('lib/net-utils.js');
const http = require('http');

View File

@ -1,15 +1,17 @@
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const Setting = require('lib/models/Setting.js');
const { reducer, defaultState } = require('lib/reducer.js');
const Setting = require('lib/models/Setting').default;
const reducer = require('lib/reducer').default;
const { defaultState } = require('lib/reducer');
const { splitCommandString } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const shim = require('lib/shim').default;
const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode;
@ -477,7 +479,7 @@ class AppGui {
this.linkSelector_.noteX + cursorOffsetX,
this.linkSelector_.noteY + cursorOffsetY
);
setTimeout(() => this.term_.term().inverse(this.linkSelector_.link), 50);
shim.setTimeout(() => this.term_.term().inverse(this.linkSelector_.link), 50);
}
} else if (cmd === 'open_link') {
if (this.widget('noteText').hasFocus) {

View File

@ -1,4 +1,4 @@
const { BaseApplication } = require('lib/BaseApplication');
const BaseApplication = require('lib/BaseApplication').default;
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const ResourceService = require('lib/services/ResourceService');
const BaseModel = require('lib/BaseModel.js');
@ -6,14 +6,15 @@ const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { reg } = require('lib/registry.js');
const { fileExtension } = require('lib/path-utils.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const fs = require('fs-extra');
const { cliUtils } = require('./cli-utils.js');
const Cache = require('lib/Cache');
const RevisionService = require('lib/services/RevisionService');
const shim = require('lib/shim').default;
class Application extends BaseApplication {
constructor() {
@ -161,7 +162,7 @@ class Application extends BaseApplication {
};
// Give it a few seconds to cancel otherwise exit anyway
setTimeout(async () => {
shim.setTimeout(async () => {
await doExit();
}, 5000);

View File

@ -1,4 +1,4 @@
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { reg } = require('lib/registry.js');
class BaseCommand {

View File

@ -1,7 +1,7 @@
const fs = require('fs-extra');
const { fileExtension, dirname } = require('lib/path-utils.js');
const wrap_ = require('word-wrap');
const { languageCode } = require('lib/locale.js');
const { languageCode } = require('lib/locale');
const rootDir = dirname(dirname(__dirname));
const MAX_WIDTH = 78;

View File

@ -1,14 +1,14 @@
'use strict';
const fs = require('fs-extra');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const { dirname } = require('lib/path-utils.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { sprintf } = require('sprintf-js');
const exec = require('child_process').exec;

View File

@ -1,8 +1,8 @@
const yargParser = require('yargs-parser');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { time } = require('lib/time-utils.js');
const stringPadding = require('string-padding');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const cliUtils = {};

View File

@ -3,7 +3,7 @@ const BaseItem = require('lib/models/BaseItem');
const BaseModel = require('lib/BaseModel');
const { toTitleCase } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const markdownUtils = require('lib/markdownUtils');
const markdownUtils = require('lib/markdownUtils').default;
const { Database } = require('lib/database.js');
class Command extends BaseCommand {
@ -53,9 +53,9 @@ class Command extends BaseCommand {
const lines = [];
lines.push('# Joplin API');
lines.push('# Joplin Data API');
lines.push('');
lines.push('This API is available when the clipper server is running. It provides access to the notes, notebooks, tags and other Joplin object via a REST API. Plugins can also access this API even when the clipper server is not running.');
lines.push('');
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
lines.push('');

View File

@ -1,8 +1,8 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
class Command extends BaseCommand {
usage() {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');

View File

@ -1,8 +1,8 @@
const { BaseCommand } = require('./base-command.js');
const { _, setLocale } = require('lib/locale.js');
const { _, setLocale } = require('lib/locale');
const { app } = require('./app.js');
const fs = require('fs-extra');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
class Command extends BaseCommand {
usage() {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');

View File

@ -1,10 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const EncryptionService = require('lib/services/EncryptionService');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseItem = require('lib/models/BaseItem');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
const pathUtils = require('lib/path-utils.js');
const imageType = require('image-type');
const readChunk = require('read-chunk');
@ -38,7 +38,7 @@ class Command extends BaseCommand {
this.stdout(_('Operation cancelled'));
return false;
}
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
Setting.setObjectValue('encryption.passwordCache', masterKeyId, password);
await EncryptionService.instance().loadMasterKeysFromSettings();
return true;
};

View File

@ -1,11 +1,11 @@
const fs = require('fs-extra');
const { BaseCommand } = require('./base-command.js');
const { splitCommandString } = require('lib/string-utils.js');
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
class Command extends BaseCommand {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class Command extends BaseCommand {
usage() {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { ReportService } = require('lib/services/report.js');
const fs = require('fs-extra');

View File

@ -1,8 +1,8 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/InteropService.js');
const InteropService = require('lib/services/interop/InteropService').default;
const BaseModel = require('lib/BaseModel.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class Command extends BaseCommand {
usage() {
@ -14,7 +14,7 @@ class Command extends BaseCommand {
}
options() {
const service = new InteropService();
const service = InteropService.instance();
const formats = service
.modules()
.filter(m => m.type === 'exporter' && m.format !== 'html')
@ -41,7 +41,7 @@ class Command extends BaseCommand {
exportOptions.sourceFolderIds = folders.map(n => n.id);
}
const service = new InteropService();
const service = InteropService.instance();
const result = await service.export(exportOptions);
result.warnings.map(w => this.stdout(w));

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');

View File

@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { renderCommandHelp } = require('./help-utils.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {

View File

@ -1,9 +1,9 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/InteropService.js');
const InteropService = require('lib/services/interop/InteropService').default;
const BaseModel = require('lib/BaseModel.js');
const { cliUtils } = require('./cli-utils.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class Command extends BaseCommand {
usage() {
@ -15,7 +15,7 @@ class Command extends BaseCommand {
}
options() {
const service = new InteropService();
const service = InteropService.instance();
const formats = service
.modules()
.filter(m => m.type === 'importer')
@ -63,7 +63,7 @@ class Command extends BaseCommand {
app().gui().showConsole();
this.stdout(_('Importing notes...'));
const service = new InteropService();
const service = InteropService.instance();
const result = await service.import(importOptions);
result.warnings.map(w => this.stdout(w));
cliUtils.redrawDone();

View File

@ -1,9 +1,9 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const { sprintf } = require('sprintf-js');
const { time } = require('lib/time-utils.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Folder = require('lib/models/Folder.js');
class Command extends BaseCommand {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Folder = require('lib/models/Folder.js');
const BaseModel = require('lib/BaseModel.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');

View File

@ -1,8 +1,8 @@
const { BaseCommand } = require('./base-command.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
class Command extends BaseCommand {
usage() {

View File

@ -1,8 +1,8 @@
const { BaseCommand } = require('./base-command.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { Logger } = require('lib/logger.js');
const { shim } = require('lib/shim');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
const Logger = require('lib/Logger').default;
const shim = require('lib/shim').default;
class Command extends BaseCommand {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const { Database } = require('lib/database.js');
const Note = require('lib/models/Note.js');

View File

@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting').default;
const { _ } = require('lib/locale');
const { ReportService } = require('lib/services/report.js');
class Command extends BaseCommand {

View File

@ -1,10 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { OneDriveApiNodeUtils } = require('lib/onedrive-api-node-utils.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
const { reg } = require('lib/registry.js');
const { cliUtils } = require('./cli-utils.js');
const md5 = require('md5');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const { time } = require('lib/time-utils.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');

View File

@ -1,5 +1,5 @@
const { BaseCommand } = require('./base-command.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const CommandDone = require('./command-done.js');

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
class Command extends BaseCommand {

View File

@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting').default;
const { _ } = require('lib/locale');
class Command extends BaseCommand {
usage() {

View File

@ -1,7 +1,7 @@
'use strict';
const { time } = require('lib/time-utils.js');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const Resource = require('lib/models/Resource.js');
const { dirname } = require('lib/path-utils.js');
const { FsDriverNode } = require('./fs-driver-node.js');

View File

@ -2,7 +2,7 @@ const Folder = require('lib/models/Folder.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const ListWidget = require('tkwidgets/ListWidget.js');
const _ = require('lib/locale.js')._;
const _ = require('lib/locale')._;
class FolderListWidget extends ListWidget {
constructor() {

View File

@ -1,6 +1,6 @@
const Note = require('lib/models/Note.js');
const TextWidget = require('tkwidgets/TextWidget.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class NoteWidget extends TextWidget {
constructor() {

View File

@ -1,6 +1,6 @@
const { wrap } = require('lib/string-utils.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting').default;
const { _ } = require('lib/locale');
const MAX_WIDTH = 78;
const INDENT = ' ';

View File

@ -21,12 +21,12 @@ const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const MasterKey = require('lib/models/MasterKey');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Revision = require('lib/models/Revision.js');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const { FsDriverNode } = require('lib/fs-driver-node.js');
const { shimInit } = require('lib/shim-init-node.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
const EncryptionService = require('lib/services/EncryptionService');
const envFromArgs = require('lib/envFromArgs');

View File

@ -0,0 +1,68 @@
import * as vm from 'vm';
import Plugin from 'lib/services/plugins/Plugin';
import sandboxProxy from 'lib/services/plugins/sandboxProxy';
import BasePluginRunner from 'lib/services/plugins/BasePluginRunner';
import executeSandboxCall from 'lib/services/plugins/utils/executeSandboxCall';
import Global from 'lib/services/plugins/api/Global';
import mapEventHandlersToIds, { EventHandlers } from 'lib/services/plugins/utils/mapEventHandlersToIds';
function createConsoleWrapper(pluginId:string) {
const wrapper:any = {};
for (const n in console) {
if (!console.hasOwnProperty(n)) continue;
wrapper[n] = (...args:any[]) => {
const newArgs = args.slice();
newArgs.splice(0, 0, `Plugin "${pluginId}":`);
return (console as any)[n](...newArgs);
};
}
return wrapper;
}
// The CLI plugin runner is more complex than it needs to be because it more or less emulates
// how it would work in a multi-process architecture, as in the desktop app (and probably how
// it would work in the mobile app too). This is mainly to allow doing integration testing.
//
// For example, all plugin calls go through a proxy, however they could made directly since
// the plugin script is running within the same process as the main app.
export default class PluginRunner extends BasePluginRunner {
private eventHandlers_:EventHandlers = {};
constructor() {
super();
this.eventHandler = this.eventHandler.bind(this);
}
private async eventHandler(eventHandlerId:string, args:any[]) {
const cb = this.eventHandlers_[eventHandlerId];
return cb(...args);
}
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);
};
return {
joplin: sandboxProxy(target),
console: createConsoleWrapper(pluginId),
};
}
async run(plugin:Plugin, sandbox:Global) {
const vmSandbox = vm.createContext(this.newSandboxProxy(plugin.id, sandbox));
try {
vm.runInContext(plugin.scriptText, vmSandbox);
} catch (error) {
this.logger().error(`In plugin ${plugin.id}:`, error);
return;
}
}
}

View File

@ -46,8 +46,14 @@ tasks.prepareTestBuild = {
],
});
await utils.copyDir(`${__dirname}/../ReactNativeClient/lib`, `${testBuildDir}/lib`);
await utils.copyDir(`${__dirname}/../ReactNativeClient/locales`, `${testBuildDir}/locales`);
const rootDir = utils.rootDir();
await utils.copyDir(`${rootDir}/ReactNativeClient/lib`, `${testBuildDir}/lib`, {
excluded: [
`${rootDir}/ReactNativeClient/lib/joplin-renderer/node_modules`,
],
});
await utils.copyDir(`${rootDir}/ReactNativeClient/locales`, `${testBuildDir}/locales`);
await fs.mkdirp(`${testBuildDir}/data`);
},
};

View File

@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.2.3",
"version": "1.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -606,6 +606,11 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"builtin-modules": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw=="
},
"cache-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
@ -4344,6 +4349,11 @@
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
},
"nanoid": {
"version": "3.1.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
"integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A=="
},
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@ -5250,6 +5260,11 @@
}
}
},
"re-reselect": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/re-reselect/-/re-reselect-4.0.0.tgz",
"integrity": "sha512-wuygyq8TXUlSdVXv2kigXxQNOgdb9m7LbIjwfTNGSpaY1riLd5e+VeQjlQMyUtrk0oiyhi1AqIVynworl3qxHA=="
},
"read-chunk": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz",
@ -5610,6 +5625,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"reselect": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
},
"resolve": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",

View File

@ -28,7 +28,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.2.3",
"version": "1.3.0",
"bin": {
"joplin": "./main.js"
},
@ -41,6 +41,7 @@
"aws-sdk": "^2.588.0",
"base-64": "^0.1.0",
"base64-stream": "^1.0.0",
"builtin-modules": "^3.1.0",
"clean-html": "^1.5.0",
"compare-version": "^0.1.2",
"diacritics": "^1.3.0",
@ -83,6 +84,7 @@
"mime": "^2.0.3",
"moment": "^2.24.0",
"multiparty": "^4.2.1",
"nanoid": "^3.1.12",
"node-emoji": "^1.8.1",
"node-fetch": "^1.7.1",
"node-persist": "^2.1.0",
@ -91,13 +93,16 @@
"promise": "^7.1.1",
"proper-lockfile": "^2.0.1",
"query-string": "4.3.4",
"re-reselect": "^4.0.0",
"read-chunk": "^2.1.0",
"redux": "^3.7.2",
"relative": "^3.0.2",
"request": "^2.88.0",
"reselect": "^4.0.0",
"sax": "^1.2.4",
"server-destroy": "^1.0.1",
"sharp": "^0.23.2",
"slug": "^3.3.4",
"sprintf-js": "^1.1.1",
"sqlite3": "^4.1.1",
"string-padding": "^1.0.2",
@ -109,7 +114,6 @@
"terminal-kit": "^1.30.0",
"tkwidgets": "^0.5.26",
"url-parse": "^1.4.7",
"slug": "^3.3.4",
"uuid": "^3.0.1",
"valid-url": "^1.0.9",
"word-wrap": "^1.2.3",

View File

@ -1,7 +1,7 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const { enexXmlToHtml } = require('lib/import-enex-html-gen.js');
process.on('unhandledRejection', (reason, p) => {

View File

@ -9,7 +9,7 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
process.on('unhandledRejection', (reason, p) => {

View File

@ -9,7 +9,7 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');

View File

@ -9,7 +9,7 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const HtmlToMd = require('lib/HtmlToMd');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');

View File

@ -9,7 +9,7 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const MdToHtml = require('lib/joplin-renderer/MdToHtml');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
const { themeStyle } = require('lib/theme');

View File

@ -1,4 +1,4 @@
const mdImporterService = require('lib/services/InteropService_Importer_Md');
const mdImporterService = require('lib/services/interop/InteropService_Importer_Md').default;
const Note = require('lib/models/Note.js');
const { setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');

View File

@ -6,10 +6,10 @@ const { time } = require('lib/time-utils.js');
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -0,0 +1,121 @@
'use strict';
require('app-module-path').addPath(__dirname);
const { asyncTest,checkThrow } = require('test-utils.js');
const eventManager = require('lib/eventManager').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('eventManager', function() {
beforeEach(async (done) => {
eventManager.reset();
done();
});
afterEach(async (done) => {
eventManager.reset();
done();
});
it('should watch state props', asyncTest(async () => {
let localStateName = '';
let callCount = 0;
function nameWatch(event) {
callCount++;
localStateName = event.value;
}
const globalState = {
name: 'john',
};
eventManager.appStateOn('name', nameWatch);
eventManager.appStateEmit(globalState);
expect(localStateName).toBe('john');
globalState.name = 'paul';
eventManager.appStateEmit(globalState);
expect(localStateName).toBe('paul');
expect(callCount).toBe(2);
eventManager.appStateEmit(globalState);
expect(callCount).toBe(2);
}));
it('should unwatch state props', asyncTest(async () => {
let localStateName = '';
function nameWatch(event) {
localStateName = event.value;
}
const globalState = {
name: 'john',
};
eventManager.appStateOn('name', nameWatch);
eventManager.appStateOff('name', nameWatch);
eventManager.appStateEmit(globalState);
expect(localStateName).toBe('');
}));
it('should watch nested props', asyncTest(async () => {
let localStateName = '';
function nameWatch(event) {
localStateName = event.value;
}
const globalState = {
user: {
name: 'john',
},
};
eventManager.appStateOn('user.name', nameWatch);
eventManager.appStateEmit(globalState);
expect(localStateName).toBe('john');
globalState.user.name = 'paul';
eventManager.appStateEmit(globalState);
expect(localStateName).toBe('paul');
}));
it('should not be possible to modify state props', asyncTest(async () => {
let localUser = {};
function userWatch(event) {
// Normally, the user should not keep a reference to the whole object
// but make a copy. However, if they do keep a reference and try to
// modify it, it should throw an exception as that would be an attempt
// to directly modify the Redux state.
localUser = event.value;
}
const globalState = {
user: {
name: 'john',
},
};
eventManager.appStateOn('user', userWatch);
eventManager.appStateEmit(globalState);
expect(checkThrow(() => localUser.name = 'paul')).toBe(true);
}));
});

View File

@ -1,7 +1,7 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, id, ids, createNTestFolders, sortedIds, createNTestNotes, TestApp } = require('test-utils.js');
const BaseModel = require('lib/BaseModel.js');
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
const Note = require('lib/models/Note.js');
const Folder = require('lib/models/Folder.js');

View File

@ -1,7 +1,7 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { setupDatabaseAndSynchronizer, switchClient, asyncTest, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('test-utils.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');

View File

@ -1,7 +1,7 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { setupDatabaseAndSynchronizer, switchClient, asyncTest, id, ids, sortedIds, at, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('test-utils.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');

View File

@ -1,7 +1,7 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { setupDatabaseAndSynchronizer, switchClient, asyncTest, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('test-utils.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');

View File

@ -2,12 +2,12 @@
require('app-module-path').addPath(__dirname);
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
const { time } = require('lib/time-utils.js');
const { asyncTest, sleep, fileApi, fileContentEqual, checkThrowAsync } = require('test-utils.js');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const fs = require('fs-extra');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const { asyncTest } = require('test-utils.js');
const markdownUtils = require('lib/markdownUtils.js');
const markdownUtils = require('lib/markdownUtils').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -9,7 +9,7 @@ const Note = require('lib/models/Note.js');
const BaseItem = require('lib/models/BaseItem.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -7,7 +7,7 @@ const { createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatab
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -8,7 +8,7 @@ const SearchEngine = require('lib/services/searchengine/SearchEngine');
const ResourceService = require('lib/services/ResourceService');
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const ItemChange = require('lib/models/ItemChange');
process.on('unhandledRejection', (reason, p) => {

View File

@ -6,10 +6,10 @@ const { time } = require('lib/time-utils.js');
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -6,10 +6,10 @@ const { time } = require('lib/time-utils.js');
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -8,7 +8,7 @@ const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -10,7 +10,7 @@ const NoteTag = require('lib/models/NoteTag.js');
const Tag = require('lib/models/Tag.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -1,37 +0,0 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('models_Setting', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should return only sub-values', asyncTest(async () => {
const settings = {
'sync.5.path': 'http://example.com',
'sync.5.username': 'testing',
};
let output = Setting.subValues('sync.5', settings);
expect(output['path']).toBe('http://example.com');
expect(output['username']).toBe('testing');
output = Setting.subValues('sync.4', settings);
expect('path' in output).toBe(false);
expect('username' in output).toBe(false);
}));
});

View File

@ -0,0 +1,135 @@
import Setting from 'lib/models/Setting';
require('app-module-path').addPath(__dirname);
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow } = require('test-utils.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('models_Setting', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should return only sub-values', asyncTest(async () => {
const settings = {
'sync.5.path': 'http://example.com',
'sync.5.username': 'testing',
};
let output = Setting.subValues('sync.5', settings);
expect(output['path']).toBe('http://example.com');
expect(output['username']).toBe('testing');
output = Setting.subValues('sync.4', settings);
expect('path' in output).toBe(false);
expect('username' in output).toBe(false);
}));
it('should allow registering new settings dynamically', asyncTest(async () => {
await expectThrow(async () => Setting.setValue('myCustom', '123'));
await Setting.registerSetting('myCustom', {
public: true,
value: 'default',
type: Setting.TYPE_STRING,
});
await expectNotThrow(async () => Setting.setValue('myCustom', '123'));
expect(Setting.value('myCustom')).toBe('123');
}));
it('should not clear old custom settings', asyncTest(async () => {
// In general the following should work:
//
// - Plugin register a new setting
// - User set new value for setting
// - Settings are saved
// - => App restart
// - Plugin does not register setting again
// - Settings are loaded
// - Settings are saved
// - Plugin register setting again
// - Previous value set by user should still be there.
//
// In other words, once a custom setting has been set we don't clear it
// even if registration doesn't happen immediately. That allows for example
// to delay setting registration without a risk for the custom setting to be deleted.
await Setting.registerSetting('myCustom', {
public: true,
value: 'default',
type: Setting.TYPE_STRING,
});
Setting.setValue('myCustom', '123');
await Setting.saveAll();
await Setting.reset();
await Setting.load();
await Setting.registerSetting('myCustom', {
public: true,
value: 'default',
type: Setting.TYPE_STRING,
});
await Setting.saveAll();
expect(Setting.value('myCustom')).toBe('123');
}));
it('should return values with correct type for custom settings', asyncTest(async () => {
await Setting.registerSetting('myCustom', {
public: true,
value: 123,
type: Setting.TYPE_INT,
});
Setting.setValue('myCustom', 456);
await Setting.saveAll();
await Setting.reset();
await Setting.load();
await Setting.registerSetting('myCustom', {
public: true,
value: 123,
type: Setting.TYPE_INT,
});
expect(typeof Setting.value('myCustom')).toBe('number');
expect(Setting.value('myCustom')).toBe(456);
}));
it('should validate registered keys', asyncTest(async () => {
const md = {
public: true,
value: 'default',
type: Setting.TYPE_STRING,
};
await expectThrow(async () => await Setting.registerSetting('', md));
await expectThrow(async () => await Setting.registerSetting('no spaces', md));
await expectThrow(async () => await Setting.registerSetting('nospecialcharacters!!!', md));
await expectThrow(async () => await Setting.registerSetting('Robert\'); DROP TABLE Students;--', md));
await expectNotThrow(async () => await Setting.registerSetting('numbersareok123', md));
await expectNotThrow(async () => await Setting.registerSetting('so-ARE-dashes_123', md));
}));
it('should register new sections', asyncTest(async () => {
await Setting.registerSection('mySection', {
label: 'My section',
});
expect(Setting.sectionNameToLabel('mySection')).toBe('My section');
}));
});

View File

@ -9,7 +9,7 @@ const Note = require('lib/models/Note.js');
const NoteTag = require('lib/models/NoteTag.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -5,7 +5,8 @@ const { setupDatabaseAndSynchronizer, switchClient, asyncTest, createNTestNotes,
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { reducer, defaultState, stateUtils, MAX_HISTORY } = require('lib/reducer.js');
const reducer = require('lib/reducer').default;
const { defaultState, stateUtils, MAX_HISTORY } = require('lib/reducer');
function initTestState(folders, selectedFolderIndex, notes, selectedNoteIndexes, tags = null, selectedTagIndex = null) {
let state = defaultState;
@ -87,7 +88,7 @@ function getIds(items, indexes = null) {
let insideBeforeEach = false;
describe('Reducer', function() {
describe('reducer', function() {
beforeEach(async (done) => {
insideBeforeEach = true;

View File

@ -0,0 +1,58 @@
import sandboxProxy, { Target } from 'lib/services/plugins/sandboxProxy';
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
describe('services_plugins_sandboxProxy', function() {
beforeEach(async (done:Function) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should create a new sandbox proxy', asyncTest(async () => {
interface Result {
path: string,
args: any[],
}
const results:Result[] = [];
const target:Target = (path:string, args:any[]) => {
results.push({ path, args });
};
const proxy = sandboxProxy(target);
proxy.testing.bla.test('hello', '123');
proxy.testing.test2();
expect(results[0].path).toBe('testing.bla.test');
expect(results[0].args.join('_')).toBe('hello_123');
expect(results[1].path).toBe('testing.test2');
expect(results[1].args.join('_')).toBe('');
}));
it('should allow importing a namespace', asyncTest(async () => {
interface Result {
path: string,
args: any[],
}
const results:Result[] = [];
const target:Target = (path:string, args:any[]) => {
results.push({ path, args });
};
const proxy = sandboxProxy(target);
const ns = proxy.testing.blabla;
ns.test();
ns.test2();
expect(results[0].path).toBe('testing.blabla.test');
expect(results[1].path).toBe('testing.blabla.test2');
}));
});

View File

@ -0,0 +1,278 @@
import MenuUtils from 'lib/services/commands/MenuUtils';
import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
import CommandService, { CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
interface TestCommand {
declaration: CommandDeclaration,
runtime: CommandRuntime,
}
function newService():CommandService {
const service = new CommandService();
service.initialize({});
return service;
}
function createCommand(name:string, options:any):TestCommand {
const declaration:CommandDeclaration = {
name: name,
};
const runtime:CommandRuntime = {
execute: options.execute,
};
if (options.mapStateToProps) runtime.mapStateToProps = options.mapStateToProps;
if (options.isEnabled) runtime.isEnabled = options.isEnabled;
return { declaration, runtime };
}
function registerCommand(service:CommandService, cmd:TestCommand) {
service.registerDeclaration(cmd.declaration);
service.registerRuntime(cmd.declaration.name, cmd.runtime);
}
describe('services_CommandService', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should create toolbar button infos from commands', asyncTest(async () => {
const service = newService();
const toolbarButtonUtils = new ToolbarButtonUtils(service);
const executedCommands:string[] = [];
registerCommand(service, createCommand('test1', {
execute: () => {
executedCommands.push('test1');
},
}));
registerCommand(service, createCommand('test2', {
execute: () => {
executedCommands.push('test2');
},
}));
const toolbarInfos = toolbarButtonUtils.commandsToToolbarButtons({}, ['test1', 'test2']);
await toolbarInfos[0].onClick();
await toolbarInfos[1].onClick();
expect(executedCommands.join('_')).toBe('test1_test2');
expect(toolbarInfos[0].enabled).toBe(true);
expect(toolbarInfos[1].enabled).toBe(true);
}));
it('should enable and disable toolbar buttons depending on state', asyncTest(async () => {
const service = newService();
const toolbarButtonUtils = new ToolbarButtonUtils(service);
registerCommand(service, createCommand('test1', {
execute: () => {},
mapStateToProps: (state:any) => {
return {
selectedNoteId: state.selectedNoteId,
selectedFolderId: state.selectedFolderId,
};
},
isEnabled: (props:any) => {
return props.selectedNoteId === 'abc';
},
}));
registerCommand(service, createCommand('test2', {
execute: () => {},
mapStateToProps: (state:any) => {
return {
selectedNoteId: state.selectedNoteId,
selectedFolderId: state.selectedFolderId,
};
},
isEnabled: (props:any) => {
return props.selectedNoteId === '123';
},
}));
const toolbarInfos = toolbarButtonUtils.commandsToToolbarButtons({
selectedNoteId: '123',
selectedFolderId: 'aaa',
}, ['test1', 'test2']);
expect(toolbarInfos[0].enabled).toBe(false);
expect(toolbarInfos[1].enabled).toBe(true);
}));
it('should return the same toolbarButtons array if nothing has changed', asyncTest(async () => {
const service = newService();
const toolbarButtonUtils = new ToolbarButtonUtils(service);
registerCommand(service, createCommand('test1', {
execute: () => {},
mapStateToProps: (state:any) => {
return {
selectedNoteId: state.selectedNoteId,
};
},
isEnabled: (props:any) => {
return props.selectedNoteId === 'ok';
},
}));
registerCommand(service, createCommand('test2', {
execute: () => {},
mapStateToProps: (state:any) => {
return {
selectedFolderId: state.selectedFolderId,
};
},
isEnabled: (props:any) => {
return props.selectedFolderId === 'ok';
},
}));
const toolbarInfos1 = toolbarButtonUtils.commandsToToolbarButtons({
selectedNoteId: 'ok',
selectedFolderId: 'notok',
}, ['test1', 'test2']);
const toolbarInfos2 = toolbarButtonUtils.commandsToToolbarButtons({
selectedNoteId: 'ok',
selectedFolderId: 'notok',
}, ['test1', 'test2']);
expect(toolbarInfos1).toBe(toolbarInfos2);
expect(toolbarInfos1[0] === toolbarInfos2[0]).toBe(true);
expect(toolbarInfos1[1] === toolbarInfos2[1]).toBe(true);
const toolbarInfos3 = toolbarButtonUtils.commandsToToolbarButtons({
selectedNoteId: 'ok',
selectedFolderId: 'ok',
}, ['test1', 'test2']);
expect(toolbarInfos2 === toolbarInfos3).toBe(false);
expect(toolbarInfos2[0] === toolbarInfos3[0]).toBe(true);
expect(toolbarInfos2[1] === toolbarInfos3[1]).toBe(false);
{
expect(toolbarButtonUtils.commandsToToolbarButtons({
selectedNoteId: 'ok',
selectedFolderId: 'notok',
}, ['test1', '-', 'test2'])).toBe(toolbarButtonUtils.commandsToToolbarButtons({
selectedNoteId: 'ok',
selectedFolderId: 'notok',
}, ['test1', '-', 'test2']));
}
}));
it('should create menu items from commands', asyncTest(async () => {
const service = newService();
const utils = new MenuUtils(service);
registerCommand(service, createCommand('test1', {
execute: () => {},
}));
registerCommand(service, createCommand('test2', {
execute: () => {},
}));
const clickedCommands:string[] = [];
const onClick = (commandName:string) => {
clickedCommands.push(commandName);
};
const menuItems = utils.commandsToMenuItems(['test1', 'test2'], onClick);
menuItems.test1.click();
menuItems.test2.click();
expect(clickedCommands.join('_')).toBe('test1_test2');
// Also check that the same commands always return strictly the same menu
expect(utils.commandsToMenuItems(['test1', 'test2'], onClick)).toBe(utils.commandsToMenuItems(['test1', 'test2'], onClick));
}));
it('should give menu item props from state', asyncTest(async () => {
const service = newService();
const utils = new MenuUtils(service);
registerCommand(service, createCommand('test1', {
mapStateToProps: (state:any) => {
return {
isOk: state.test1 === 'ok',
};
},
execute: () => {},
}));
registerCommand(service, createCommand('test2', {
mapStateToProps: (state:any) => {
return {
isOk: state.test2 === 'ok',
};
},
execute: () => {},
}));
{
const menuItemProps = utils.commandsToMenuItemProps({
test1: 'ok',
test2: 'notok',
}, ['test1', 'test2']);
expect(menuItemProps.test1.isOk).toBe(true);
expect(menuItemProps.test2.isOk).toBe(false);
}
{
const menuItemProps = utils.commandsToMenuItemProps({
test1: 'ok',
test2: 'ok',
}, ['test1', 'test2']);
expect(menuItemProps.test1.isOk).toBe(true);
expect(menuItemProps.test2.isOk).toBe(true);
}
expect(utils.commandsToMenuItemProps({
test1: 'ok',
test2: 'ok',
}, ['test1', 'test2'])).toBe(utils.commandsToMenuItemProps({
test1: 'ok',
test2: 'ok',
}, ['test1', 'test2']));
}));
it('should create stateful menu items', asyncTest(async () => {
const service = newService();
const utils = new MenuUtils(service);
let propValue = null;
registerCommand(service, createCommand('test1', {
mapStateToProps: (state:any) => {
return {
isOk: state.test1 === 'ok',
};
},
execute: (props:any) => {
propValue = props.isOk;
},
}));
const menuItem = utils.commandToStatefulMenuItem('test1', { isOk: 'hello' });
menuItem.click();
expect(propValue).toBe('hello');
}));
});

View File

@ -8,7 +8,7 @@ const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const MasterKey = require('lib/models/MasterKey');
@ -289,35 +289,4 @@ describe('services_EncryptionService', function() {
const plainText = await service.decryptString(cipherText);
expect(plainText).toBe('🐶🐶🐶'.substr(0,5));
}));
// it('should upgrade master key encryption mode', asyncTest(async () => {
// let masterKey = await service.generateMasterKey('123456', {
// encryptionMethod: EncryptionService.METHOD_SJCL_2,
// });
// masterKey = await MasterKey.save(masterKey);
// Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
// Setting.setValue('encryption.activeMasterKeyId', masterKey.id);
// await sleep(0.01);
// await service.loadMasterKeysFromSettings();
// masterKeyNew = await MasterKey.load(masterKey.id);
// // Check that the master key has been upgraded
// expect(masterKeyNew.created_time).toBe(masterKey.created_time);
// expect(masterKeyNew.updated_time === masterKey.updated_time).toBe(false);
// expect(masterKeyNew.content === masterKey.content).toBe(false);
// expect(masterKeyNew.encryption_method === masterKey.encryption_method).toBe(false);
// expect(masterKeyNew.checksum === masterKey.checksum).toBe(false);
// expect(masterKeyNew.encryption_method).toBe(service.defaultMasterKeyEncryptionMethod_);
// // Check that encryption still works
// const cipherText = await service.encryptString('some secret');
// const plainText = await service.decryptString(cipherText);
// expect(plainText).toBe('some secret');
// }));
});

View File

@ -1,19 +1,16 @@
/* eslint-disable no-unused-vars */
import InteropService from 'lib/services/interop/InteropService';
import { CustomExportContext, CustomImportContext, Module, ModuleType } from 'lib/services/interop/types';
import shim from 'lib/shim';
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, expectNotThrow, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const InteropService = require('lib/services/InteropService.js');
const { asyncTest, fileContentEqual, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const Resource = require('lib/models/Resource.js');
const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@ -23,7 +20,7 @@ function exportDir() {
return `${__dirname}/export`;
}
function fieldsEqual(model1, model2, fieldNames) {
function fieldsEqual(model1:any, model2:any, fieldNames:string[]) {
for (let i = 0; i < fieldNames.length; i++) {
const f = fieldNames[i];
expect(model1[f]).toBe(model2[f], `For key ${f}`);
@ -43,7 +40,7 @@ describe('services_InteropService', function() {
});
it('should export and import folders', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
let folder1 = await Folder.save({ title: 'folder1' });
folder1 = await Folder.load(folder1.id);
const filePath = `${exportDir()}/test.jex`;
@ -78,7 +75,7 @@ describe('services_InteropService', function() {
}));
it('should import folders and de-duplicate titles when needed', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const folder1 = await Folder.save({ title: 'folder' });
const folder2 = await Folder.save({ title: 'folder' });
const filePath = `${exportDir()}/test.jex`;
@ -90,15 +87,15 @@ describe('services_InteropService', function() {
await service.import({ path: filePath });
const allFolders = await Folder.all();
expect(allFolders.map(f => f.title).sort().join(' - ')).toBe('folder - folder (1)');
expect(allFolders.map((f:any) => f.title).sort().join(' - ')).toBe('folder - folder (1)');
}));
it('should import folders, and only de-duplicate titles when needed', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const folder1 = await Folder.save({ title: 'folder1' });
const folder2 = await Folder.save({ title: 'folder2' });
const sub1 = await Folder.save({ title: 'Sub', parent_id: folder1.id });
const sub2 = await Folder.save({ title: 'Sub', parent_id: folder2.id });
await Folder.save({ title: 'Sub', parent_id: folder1.id });
await Folder.save({ title: 'Sub', parent_id: folder2.id });
const filePath = `${exportDir()}/test.jex`;
await service.export({ path: filePath });
@ -117,7 +114,7 @@ describe('services_InteropService', function() {
}));
it('should export and import folders and notes', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
@ -156,7 +153,7 @@ describe('services_InteropService', function() {
}));
it('should export and import notes to specific folder', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
@ -175,7 +172,7 @@ describe('services_InteropService', function() {
}));
it('should export and import tags', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const filePath = `${exportDir()}/test.jex`;
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
@ -215,7 +212,7 @@ describe('services_InteropService', function() {
}));
it('should export and import resources', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const filePath = `${exportDir()}/test.jex`;
const folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
@ -251,7 +248,7 @@ describe('services_InteropService', function() {
}));
it('should export and import single notes', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const filePath = `${exportDir()}/test.jex`;
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
@ -271,7 +268,7 @@ describe('services_InteropService', function() {
}));
it('should export and import single folders', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const filePath = `${exportDir()}/test.jex`;
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
@ -292,7 +289,7 @@ describe('services_InteropService', function() {
it('should export and import folder and its sub-folders', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const filePath = `${exportDir()}/test.jex`;
const folder1 = await Folder.save({ title: 'folder1' });
const folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id });
@ -326,7 +323,7 @@ describe('services_InteropService', function() {
}));
it('should export and import links to notes', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const filePath = `${exportDir()}/test.jex`;
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
@ -349,43 +346,21 @@ describe('services_InteropService', function() {
expect(note2_2.body.indexOf(note1_2.id) >= 0).toBe(true);
}));
it('should export into json format', asyncTest(async () => {
const service = new InteropService();
const folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
const filePath = exportDir();
await service.export({ path: filePath, format: 'json' });
// verify that the json files exist and can be parsed
const items = [folder1, note1];
for (let i = 0; i < items.length; i++) {
const jsonFile = `${filePath}/${items[i].id}.json`;
const json = await fs.readFile(jsonFile, 'utf-8');
const obj = JSON.parse(json);
expect(obj.id).toBe(items[i].id);
expect(obj.type_).toBe(items[i].type_);
expect(obj.title).toBe(items[i].title);
expect(obj.body).toBe(items[i].body);
}
}));
it('should export selected notes in md format', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const folder1 = await Folder.save({ title: 'folder1' });
let note11 = await Note.save({ title: 'title note11', parent_id: folder1.id });
note11 = await Note.load(note11.id);
let note12 = await Note.save({ title: 'title note12', parent_id: folder1.id });
note12 = await Note.load(note12.id);
const note12 = await Note.save({ title: 'title note12', parent_id: folder1.id });
await Note.load(note12.id);
let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id });
folder2 = await Folder.load(folder2.id);
let note21 = await Note.save({ title: 'title note21', parent_id: folder2.id });
note21 = await Note.load(note21.id);
let folder3 = await Folder.save({ title: 'folder3', parent_id: folder1.id });
folder3 = await Folder.load(folder2.id);
await Folder.save({ title: 'folder3', parent_id: folder1.id });
await Folder.load(folder2.id);
const outDir = exportDir();
@ -401,16 +376,16 @@ describe('services_InteropService', function() {
}));
it('should export MD with unicode filenames', asyncTest(async () => {
const service = new InteropService();
const service = InteropService.instance();
const folder1 = await Folder.save({ title: 'folder1' });
const folder2 = await Folder.save({ title: 'ジョプリン' });
const note1 = await Note.save({ title: '生活', parent_id: folder1.id });
const note2 = await Note.save({ title: '生活', parent_id: folder1.id });
const note2b = await Note.save({ title: '生活', parent_id: folder1.id });
const note3 = await Note.save({ title: '', parent_id: folder1.id });
const note4 = await Note.save({ title: '', parent_id: folder1.id });
const note5 = await Note.save({ title: 'salut, ça roule ?', parent_id: folder1.id });
const note6 = await Note.save({ title: 'ジョプリン', parent_id: folder2.id });
await Note.save({ title: '生活', parent_id: folder1.id });
await Note.save({ title: '生活', parent_id: folder1.id });
await Note.save({ title: '生活', parent_id: folder1.id });
await Note.save({ title: '', parent_id: folder1.id });
await Note.save({ title: '', parent_id: folder1.id });
await Note.save({ title: 'salut, ça roule ?', parent_id: folder1.id });
await Note.save({ title: 'ジョプリン', parent_id: folder2.id });
const outDir = exportDir();
@ -430,7 +405,7 @@ describe('services_InteropService', function() {
await Note.save({ title: 'textexportnote1', parent_id: folder1.id });
await Note.save({ title: 'textexportnote2', parent_id: folder1.id });
const service = new InteropService();
const service = InteropService.instance();
await service.export({
path: exportDir(),
@ -454,7 +429,7 @@ describe('services_InteropService', function() {
await Folder.save({ title: 'orphan', parent_id: '0c5bbd8a1b5a48189484a412a7e534cc' });
const service = new InteropService();
const service = InteropService.instance();
const result = await service.export({
path: exportDir(),
@ -464,4 +439,99 @@ describe('services_InteropService', function() {
expect(result.warnings.length).toBe(0);
}));
it('should allow registering new import modules', asyncTest(async () => {
const testImportFilePath = `${exportDir()}/testImport${Math.random()}.test`;
await shim.fsDriver().writeFile(testImportFilePath, 'test', 'utf8');
const result = {
hasBeenExecuted: false,
sourcePath: '',
};
const module:Module = {
type: ModuleType.Importer,
description: 'Test Import Module',
format: 'testing',
fileExtensions: ['test'],
isCustom: true,
onExec: async (context:CustomImportContext) => {
result.hasBeenExecuted = true;
result.sourcePath = context.sourcePath;
},
};
const service = InteropService.instance();
service.registerModule(module);
await service.import({
format: 'testing',
path: testImportFilePath,
});
expect(result.hasBeenExecuted).toBe(true);
expect(result.sourcePath).toBe(testImportFilePath);
}));
it('should allow registering new export modules', asyncTest(async () => {
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'note1', parent_id: folder1.id });
await Note.save({ title: 'note2', parent_id: folder1.id });
await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`);
const filePath = `${exportDir()}/example.test`;
const result:any = {
destPath: '',
itemTypes: [],
items: [],
resources: [],
filePaths: [],
closeCalled: false,
};
const module:Module = {
type: ModuleType.Exporter,
description: 'Test Export Module',
format: 'testing',
fileExtensions: ['test'],
isCustom: true,
onInit: async (context:CustomExportContext) => {
result.destPath = context.destPath;
},
onProcessItem: async (_context:CustomExportContext, itemType:number, item:any) => {
result.itemTypes.push(itemType);
result.items.push(item);
},
onProcessResource: async (_context:CustomExportContext, resource:any, filePath:string) => {
result.resources.push(resource);
result.filePaths.push(filePath);
},
onClose: async (_context:CustomExportContext) => {
result.closeCalled = true;
},
};
const service = InteropService.instance();
service.registerModule(module);
await service.export({
format: 'testing',
path: filePath,
});
expect(result.destPath).toBe(filePath);
expect(result.itemTypes.sort().join('_')).toBe('1_1_2_4');
expect(result.items.length).toBe(4);
expect(result.items.map((o:any) => o.title).sort().join('_')).toBe('folder1_note1_note2_photo.jpg');
expect(result.resources.length).toBe(1);
expect(result.resources[0].title).toBe('photo.jpg');
expect(result.filePaths.length).toBe(1);
expect(!!result.filePaths[0]).toBe(true);
expect(result.closeCalled).toBe(true);
}));
});

View File

@ -4,12 +4,12 @@ require('app-module-path').addPath(__dirname);
const fs = require('fs-extra');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const InteropService_Exporter_Md = require('lib/services/InteropService_Exporter_Md.js');
const InteropService_Exporter_Md = require('lib/services/interop/InteropService_Exporter_Md').default;
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Resource = require('lib/models/Resource.js');
const Note = require('lib/models/Note.js');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const exportDir = `${__dirname}/export`;
@ -67,8 +67,8 @@ describe('services_InteropService_Exporter_Md', function() {
expect(!exporter.context() && !(exporter.context().notePaths || Object.keys(exporter.context().notePaths).length)).toBe(false, 'Context should be empty before processing.');
await exporter.processItem(Folder, folder1);
await exporter.processItem(Folder, folder2);
await exporter.processItem(Folder.modelType(), folder1);
await exporter.processItem(Folder.modelType(), folder2);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
expect(Object.keys(exporter.context().notePaths).length).toBe(3, 'There should be 3 note paths in the context.');
@ -96,7 +96,7 @@ describe('services_InteropService_Exporter_Md', function() {
queueExportItem(BaseModel.TYPE_NOTE, note1);
queueExportItem(BaseModel.TYPE_NOTE, note1_2);
await exporter.processItem(Folder, folder1);
await exporter.processItem(Folder.modelType(), folder1);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
expect(Object.keys(exporter.context().notePaths).length).toBe(2, 'There should be 2 note paths in the context.');
@ -121,7 +121,7 @@ describe('services_InteropService_Exporter_Md', function() {
queueExportItem(BaseModel.TYPE_FOLDER, folder1.id);
queueExportItem(BaseModel.TYPE_NOTE, note1);
await exporter.processItem(Folder, folder1);
await exporter.processItem(Folder.modelType(), folder1);
// Create a file with the path of note1 before processing note1
await shim.fsDriver().writeFile(`${exportDir}/folder1/note1.md`, 'Note content', 'utf-8');
@ -189,10 +189,10 @@ describe('services_InteropService_Exporter_Md', function() {
const folder3 = await Folder.save({ title: 'folder3', parent_id: folder1.id });
queueExportItem(BaseModel.TYPE_FOLDER, folder3.id);
await exporter.processItem(Folder, folder2);
await exporter.processItem(Folder, folder3);
await exporter.processItem(Folder.modelType(), folder2);
await exporter.processItem(Folder.modelType(), folder3);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
await exporter.processItem(Note, note2);
await exporter.processItem(Note.modelType(), note2);
expect(await shim.fsDriver().exists(`${exportDir}/folder1`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1/folder2`)).toBe(true, 'Folder should be created in filesystem.');
@ -227,9 +227,9 @@ describe('services_InteropService_Exporter_Md', function() {
queueExportItem(BaseModel.TYPE_NOTE, note3);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
await exporter.processItem(Note, note1);
await exporter.processItem(Note, note2);
await exporter.processItem(Note, note3);
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
await exporter.processItem(Note.modelType(), note3);
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note1.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note2.id]}`)).toBe(true, 'File should be saved in filesystem.');
@ -262,8 +262,8 @@ describe('services_InteropService_Exporter_Md', function() {
queueExportItem(BaseModel.TYPE_NOTE, note2);
const resource2 = await Resource.load((await Note.linkedResourceIds(note2.body))[0]);
await exporter.processItem(Folder, folder1);
await exporter.processItem(Folder, folder2);
await exporter.processItem(Folder.modelType(), folder1);
await exporter.processItem(Folder.modelType(), folder2);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
const context = {
resourcePaths: {},
@ -271,8 +271,8 @@ describe('services_InteropService_Exporter_Md', function() {
context.resourcePaths[resource1.id] = 'resource1.jpg';
context.resourcePaths[resource2.id] = 'resource2.jpg';
exporter.updateContext(context);
await exporter.processItem(Note, note1);
await exporter.processItem(Note, note2);
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
@ -315,13 +315,13 @@ describe('services_InteropService_Exporter_Md', function() {
queueExportItem(BaseModel.TYPE_NOTE, note2);
queueExportItem(BaseModel.TYPE_NOTE, note3);
await exporter.processItem(Folder, folder1);
await exporter.processItem(Folder, folder2);
await exporter.processItem(Folder, folder3);
await exporter.processItem(Folder.modelType(), folder1);
await exporter.processItem(Folder.modelType(), folder2);
await exporter.processItem(Folder.modelType(), folder3);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
await exporter.processItem(Note, note1);
await exporter.processItem(Note, note2);
await exporter.processItem(Note, note3);
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
await exporter.processItem(Note.modelType(), note3);
const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
@ -351,10 +351,10 @@ describe('services_InteropService_Exporter_Md', function() {
queueExportItem(BaseModel.TYPE_NOTE, note1);
queueExportItem(BaseModel.TYPE_NOTE, note2);
await exporter.processItem(Folder, folder1);
await exporter.processItem(Folder.modelType(), folder1);
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
await exporter.processItem(Note, note1);
await exporter.processItem(Note, note2);
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
expect(note2_body).toContain('[link](../folder%20with%20space1/note1%20name%20with%20space.md)', 'Whitespace in URL should be encoded');

View File

@ -1,5 +1,6 @@
require('app-module-path').addPath(__dirname);
const { tempFilePath } = require('test-utils.js');
const KeymapService = require('lib/services/KeymapService').default;
const keymapService = KeymapService.instance();
@ -76,6 +77,31 @@ describe('services_KeymapService', () => {
});
});
describe('registerCommandAccelerator', () => {
beforeEach(() => keymapService.initialize());
it('should allow registering new commands', async () => {
keymapService.initialize('linux');
keymapService.registerCommandAccelerator('myCustomCommand', 'Ctrl+Shift+Alt+B');
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Ctrl+Shift+Alt+B');
// Check that macOS key conversion is working
keymapService.initialize('darwin');
keymapService.registerCommandAccelerator('myCustomCommand', 'CmdOrCtrl+Shift+Alt+B');
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Cmd+Shift+Option+B');
keymapService.setAccelerator('myCustomCommand', 'Cmd+Shift+Option+X');
// Check that the new custom shortcut is being saved and loaded
const keymapFilePath = tempFilePath('json');
await keymapService.saveCustomKeymap(keymapFilePath);
keymapService.initialize('darwin');
await keymapService.loadCustomKeymap(keymapFilePath);
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Cmd+Shift+Option+X');
});
});
describe('getAccelerator', () => {
beforeEach(() => keymapService.initialize());
@ -254,15 +280,15 @@ describe('services_KeymapService', () => {
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
});
it('should throw when the provided commands are invalid', () => {
const customKeymapItems = [
{ command: 'totallyInvalidCommand', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J' },
];
// it('should throw when the provided commands are invalid', () => {
// const customKeymapItems = [
// { command: 'totallyInvalidCommand', accelerator: 'Ctrl+Shift+G' },
// { command: 'print', accelerator: 'Alt+P' },
// { command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J' },
// ];
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
});
// expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
// });
it('should throw when duplicate accelerators are provided', () => {
const customKeymaps_Darwin = [

View File

@ -0,0 +1,81 @@
import PluginRunner from '../app/services/plugins/PluginRunner';
import PluginService from 'lib/services/plugins/PluginService';
require('app-module-path').addPath(__dirname);
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
const testPluginDir = `${__dirname}/../tests/support/plugins`;
function newPluginService() {
const runner = new PluginRunner();
const service = new PluginService();
service.initialize(
{
joplin: {
workspace: {},
},
},
runner,
{
dispatch: () => {},
getState: () => {},
}
);
return service;
}
describe('services_PluginService', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should load and run a simple plugin', asyncTest(async () => {
const service = newPluginService();
const plugin = await service.loadPlugin(`${testPluginDir}/simple`);
await service.runPlugin(plugin);
const allFolders = await Folder.all();
expect(allFolders.length).toBe(1);
expect(allFolders[0].title).toBe('my plugin folder');
const allNotes = await Note.all();
expect(allNotes.length).toBe(1);
expect(allNotes[0].title).toBe('testing plugin!');
expect(allNotes[0].parent_id).toBe(allFolders[0].id);
}));
it('should load and run a plugin that uses external packages', asyncTest(async () => {
const service = newPluginService();
const plugin = await service.loadPlugin(`${testPluginDir}/withExternalModules`);
expect(plugin.id).toBe('withexternalmodules');
await service.runPlugin(plugin);
const allFolders = await Folder.all();
expect(allFolders.length).toBe(1);
expect(allFolders[0].title).toBe(' foo');
}));
it('should load multiple plugins from a directory', asyncTest(async () => {
const service = newPluginService();
await service.loadAndRunPlugins(`${testPluginDir}/multi_plugins`);
const plugin1 = service.pluginById('simple1');
const plugin2 = service.pluginById('simple2');
expect(!!plugin1).toBe(true);
expect(!!plugin2).toBe(true);
const allFolders = await Folder.all();
expect(allFolders.length).toBe(2);
expect(allFolders.map((f:any) => f.title).sort().join(', ')).toBe('multi - simple1, multi - simple2');
}));
});

View File

@ -4,7 +4,7 @@ require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, resourceService, decryptionWorker, encryptionService, loadEncryptionMasterKey, allSyncTargetItemsEncrypted, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const InteropService = require('lib/services/InteropService.js');
const InteropService = require('lib/services/interop/InteropService').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
@ -16,7 +16,7 @@ const ResourceService = require('lib/services/ResourceService.js');
const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const SearchEngine = require('lib/services/searchengine/SearchEngine');
process.on('unhandledRejection', (reason, p) => {

View File

@ -5,7 +5,7 @@ require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const NoteTag = require('lib/models/NoteTag.js');
const ItemChange = require('lib/models/ItemChange.js');
@ -13,7 +13,7 @@ const Tag = require('lib/models/Tag.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const RevisionService = require('lib/services/RevisionService.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -8,7 +8,7 @@ const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -10,9 +10,9 @@ const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
const Tag = require('lib/models/Tag');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const Resource = require('lib/models/Resource.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const ResourceService = require('lib/services/ResourceService.js');

View File

@ -12,10 +12,9 @@
// const ItemChange = require('lib/models/ItemChange');
// const Setting = require('lib/models/Setting');
// const Resource = require('lib/models/Resource.js');
// const { shim } = require('lib/shim');
// const shim = require('lib/shim').default;
// const ResourceService = require('lib/services/ResourceService.js');
// process.on('unhandledRejection', (reason, p) => {
// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
// });

View File

@ -1,84 +0,0 @@
// /* eslint-disable no-unused-vars */
// require('app-module-path').addPath(__dirname);
// const { asyncTest, fileContentEqual, setupDatabase, checkThrow, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
// const KvStore = require('lib/services/KvStore.js');
// const UndoRedoService = require('lib/services/UndoRedoService.js').default;
// process.on('unhandledRejection', (reason, p) => {
// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
// });
// describe('services_UndoRedoService', function() {
// beforeEach(async (done) => {
// await setupDatabaseAndSynchronizer(1);
// await switchClient(1);
// done();
// });
// it('should undo and redo', asyncTest(async () => {
// const service = new UndoRedoService();
// expect(service.canUndo).toBe(false);
// expect(service.canRedo).toBe(false);
// service.push('test');
// expect(service.canUndo).toBe(true);
// expect(service.canRedo).toBe(false);
// service.push('test 2');
// service.push('test 3');
// expect(service.undo()).toBe('test 3');
// expect(service.canRedo).toBe(true);
// expect(service.undo()).toBe('test 2');
// expect(service.undo()).toBe('test');
// expect(checkThrow(() => service.undo())).toBe(true);
// expect(service.canUndo).toBe(false);
// expect(service.canRedo).toBe(true);
// expect(service.redo()).toBe('test');
// expect(service.canUndo).toBe(true);
// expect(service.redo()).toBe('test 2');
// expect(service.redo()).toBe('test 3');
// expect(service.canRedo).toBe(false);
// expect(checkThrow(() => service.redo())).toBe(true);
// }));
// it('should clear the redo stack when undoing', asyncTest(async () => {
// const service = new UndoRedoService();
// service.push('test');
// service.push('test 2');
// service.push('test 3');
// service.undo();
// expect(service.canRedo).toBe(true);
// service.push('test 4');
// expect(service.canRedo).toBe(false);
// expect(service.undo()).toBe('test 4');
// expect(service.undo()).toBe('test 2');
// }));
// it('should limit the size of the undo stack', asyncTest(async () => {
// const service = new UndoRedoService();
// for (let i = 0; i < 30; i++) {
// service.push(`test${i}`);
// }
// for (let i = 0; i < 20; i++) {
// service.undo();
// }
// expect(service.canUndo).toBe(false);
// }));
// });

View File

@ -3,8 +3,8 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting');
const shim = require('lib/shim').default;
const Setting = require('lib/models/Setting').default;
const KeychainService = require('lib/services/keychain/KeychainService').default;
process.on('unhandledRejection', (reason, p) => {
@ -50,7 +50,7 @@ describeIfCompatible('services_KeychainService', function() {
}));
it('should save and load secure settings', asyncTest(async () => {
Setting.setObjectKey('encryption.passwordCache', 'testing', '123456');
Setting.setObjectValue('encryption.passwordCache', 'testing', '123456');
await Setting.saveAll();
await Setting.load();
const passwords = Setting.value('encryption.passwordCache');

View File

@ -3,13 +3,13 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('test-utils.js');
const Api = require('lib/services/rest/Api');
const Api = require('lib/services/rest/Api').default;
const Folder = require('lib/models/Folder');
const Resource = require('lib/models/Resource');
const Note = require('lib/models/Note');
const Tag = require('lib/models/Tag');
const NoteTag = require('lib/models/NoteTag');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@ -2,6 +2,7 @@
"spec_dir": "tests-build",
"spec_files": [
"*.js",
"services/plugins/*.js",
"!test-utils.js"
],
"stopSpecOnExpectationFailure": false,

View File

@ -0,0 +1 @@
_temp

View File

@ -0,0 +1,2 @@
dist/*
node_modules/

View File

@ -0,0 +1,14 @@
# Joplin Plugin
This is a template to create a new Joplin plugin.
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
The plugin is built using webpack, which create the compiled code in `/dist`. The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Building the plugin
To build the plugin, simply run `npm run dist`.

View File

@ -0,0 +1,15 @@
import Plugin from '../Plugin';
import Joplin from './Joplin';
import Logger from 'lib/Logger';
/**
* @ignore
*/
export default class Global {
private joplin_;
private requireWhiteList_;
constructor(logger: Logger, implementation: any, plugin: Plugin, store: any);
get joplin(): Joplin;
private requireWhiteList;
require(filePath: string): any;
get process(): any;
}

View File

@ -0,0 +1,38 @@
import Plugin from '../Plugin';
import JoplinData from './JoplinData';
import JoplinPlugins from './JoplinPlugins';
import JoplinWorkspace from './JoplinWorkspace';
import JoplinFilters from './JoplinFilters';
import JoplinCommands from './JoplinCommands';
import JoplinViews from './JoplinViews';
import JoplinInterop from './JoplinInterop';
import JoplinSettings from './JoplinSettings';
import Logger from 'lib/Logger';
/**
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
*/
export default class Joplin {
private data_;
private plugins_;
private workspace_;
private filters_;
private commands_;
private views_;
private interop_;
private settings_;
constructor(logger: Logger, implementation: any, plugin: Plugin, store: any);
get data(): JoplinData;
get plugins(): JoplinPlugins;
get workspace(): JoplinWorkspace;
/**
* @ignore
*
* Not sure if it's the best way to hook into the app
* so for now disable filters.
*/
get filters(): JoplinFilters;
get commands(): JoplinCommands;
get views(): JoplinViews;
get interop(): JoplinInterop;
get settings(): JoplinSettings;
}

View File

@ -0,0 +1,51 @@
import { Command } from './types';
/**
* This class allows executing or registering new Joplin commands. Commands can be executed or associated with
* {@link JoplinViewsToolbarButtons | toolbar buttons} or {@link JoplinViewsMenuItems | menu items}.
*
* [View the demo plugin](https://github.com/laurent22/joplin/CliClient/tests/support/plugins/register_command)
*
* ## Executing Joplin's internal commands
*
* It is also possible to execute internal Joplin's commands which, as of now, are not well documented.
* You can find the list directly on GitHub though at the following locations:
*
* https://github.com/laurent22/joplin/tree/dev/ElectronClient/gui/MainScreen/commands
* https://github.com/laurent22/joplin/tree/dev/ElectronClient/commands
* https://github.com/laurent22/joplin/tree/dev/ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.ts
*
* To view what arguments are supported, you can open any of these files and look at the `execute()` command.
*/
export default class JoplinCommands {
/**
* <span class="platform-desktop">desktop</span> Executes the given command.
* The `props` are the arguments passed to the command, and they vary based on the command
*
* ```typescript
* // Create a new note in the current notebook:
* await joplin.commands.execute('newNote');
*
* // Create a new sub-notebook under the provided notebook
* // Note: internally, notebooks are called "folders".
* await joplin.commands.execute('newFolder', { parent_id: "SOME_FOLDER_ID" });
* ```
*/
execute(commandName: string, props?: any): Promise<any>;
/**
* <span class="platform-desktop">desktop</span> Registers a new command.
*
* ```typescript
* // Register a new commmand called "testCommand1"
*
* await joplin.commands.register({
* name: 'testCommand1',
* label: 'My Test Command 1',
* iconName: 'fas fa-music',
* execute: () => {
* alert('Testing plugin command 1');
* },
* });
* ```
*/
register(command: Command): Promise<void>;
}

View File

@ -0,0 +1,47 @@
import { Path } from './types';
/**
* This module provides access to the Joplin data API: https://joplinapp.org/api/
* This is the main way to retrieve data, such as notes, notebooks, tags, etc.
* or to update them or delete them.
*
* This is also what you would use to search notes, via the `search` endpoint.
*
* [View the demo plugin](https://github.com/laurent22/joplin/CliClient/tests/support/plugins/simple)
*
* In general you would use the methods in this class as if you were using a REST API. There are four methods that map to GET, POST, PUT and DELETE calls.
* And each method takes these parameters:
*
* * `path`: This is an array that represents the path to the resource in the form `["resouceName", "resourceId", "resourceLink"]` (eg. ["tags", ":id", "notes"]). The "resources" segment is the name of the resources you want to access (eg. "notes", "folders", etc.). If not followed by anything, it will refer to all the resources in that collection. The optional "resourceId" points to a particular resources within the collection. Finally, an optional "link" can be present, which links the resource to a collection of resources. This can be used in the API for example to retrieve all the notes associated with a tag.
* * `query`: (Optional) The query parameters. In a URL, this is the part after the question mark "?". In this case, it should be an object with key/value pairs.
* * `data`: (Optional) Applies to PUT and POST calls only. The request body contains the data you want to create or modify, for example the content of a note or folder.
* * `files`: (Optional) Used to create new resources and associate them with files.
*
* Please refer to the [Joplin API documentation](https://joplinapp.org/api/) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
*
* For example:
*
* ```typescript
* // Get a note ID, title and body
* const noteId = 'some_note_id';
* const note = await joplin.data.get(['notes', noteId], { fields: ['id', 'title', 'body'] });
*
* // Get all folders
* const folders = await joplin.data.get(['folders']);
*
* // Set the note body
* await joplin.data.put(['notes', noteId], null, { body: "New note body" });
*
* // Create a new note under one of the folders
* await joplin.data.post(['notes'], null, { body: "my new note", title: "some title", parent_id: folders[0].id });
* ```
*/
export default class JoplinData {
private api_;
private pathSegmentRegex_;
private serializeApiBody;
private pathToString;
get(path: Path, query?: any): Promise<any>;
post(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
put(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
delete(path: Path, query?: any): Promise<any>;
}

Some files were not shown because too many files have changed in this diff Show More