You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-08 23:07:32 +02:00
Compare commits
22 Commits
plugin-gen
...
fix_html_n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bba9a8ed6 | ||
|
|
3344e84c7c | ||
|
|
e0c2b62a6c | ||
|
|
544d879c0b | ||
|
|
28f75449d7 | ||
|
|
fdc84aa6bb | ||
|
|
592b9d95c6 | ||
|
|
4abdab5cdb | ||
|
|
858508bfa9 | ||
|
|
116413e78d | ||
|
|
031a26116c | ||
|
|
65962e26ce | ||
|
|
30913a5d58 | ||
|
|
61618fb37c | ||
|
|
a40ab434ca | ||
|
|
dbbd930f81 | ||
|
|
ff3ae3860e | ||
|
|
e1c4d1a417 | ||
|
|
e1180a1d84 | ||
|
|
e57444dc32 | ||
|
|
f36019c94d | ||
|
|
c67c8cefdf |
@@ -241,6 +241,15 @@ packages/app-cli/tests/support/plugins/events/api/types.js.map
|
||||
packages/app-cli/tests/support/plugins/events/src/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/events/src/index.js
|
||||
packages/app-cli/tests/support/plugins/events/src/index.js.map
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/index.js
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/index.js.map
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/types.d.ts
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/types.js
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/types.js.map
|
||||
packages/app-cli/tests/support/plugins/jpl_test/src/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/jpl_test/src/index.js
|
||||
packages/app-cli/tests/support/plugins/jpl_test/src/index.js.map
|
||||
packages/app-cli/tests/support/plugins/json_export/api/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/json_export/api/index.js
|
||||
packages/app-cli/tests/support/plugins/json_export/api/index.js.map
|
||||
@@ -367,6 +376,9 @@ packages/app-desktop/gui/ConfigScreen/ConfigScreen.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js.map
|
||||
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
||||
@@ -730,6 +742,9 @@ packages/app-desktop/gui/ToolbarButton/ToolbarButton.js.map
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.d.ts
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js.map
|
||||
packages/app-desktop/gui/dialogs.d.ts
|
||||
packages/app-desktop/gui/dialogs.js
|
||||
packages/app-desktop/gui/dialogs.js.map
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.d.ts
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.js.map
|
||||
@@ -742,6 +757,9 @@ packages/app-desktop/gui/hooks/usePrevious.js.map
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.d.ts
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js.map
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.d.ts
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js.map
|
||||
packages/app-desktop/gui/menuCommandNames.d.ts
|
||||
packages/app-desktop/gui/menuCommandNames.js
|
||||
packages/app-desktop/gui/menuCommandNames.js.map
|
||||
@@ -1333,6 +1351,9 @@ packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.js.map
|
||||
packages/renderer/HtmlToHtml.d.ts
|
||||
packages/renderer/HtmlToHtml.js
|
||||
packages/renderer/HtmlToHtml.js.map
|
||||
packages/renderer/InMemoryCache.d.ts
|
||||
packages/renderer/InMemoryCache.js
|
||||
packages/renderer/InMemoryCache.js.map
|
||||
@@ -1342,6 +1363,12 @@ packages/renderer/MarkupToHtml.js.map
|
||||
packages/renderer/MdToHtml.d.ts
|
||||
packages/renderer/MdToHtml.js
|
||||
packages/renderer/MdToHtml.js.map
|
||||
packages/renderer/MdToHtml/linkReplacement.d.ts
|
||||
packages/renderer/MdToHtml/linkReplacement.js
|
||||
packages/renderer/MdToHtml/linkReplacement.js.map
|
||||
packages/renderer/MdToHtml/linkReplacement.test.d.ts
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js.map
|
||||
packages/renderer/MdToHtml/rules/checkbox.d.ts
|
||||
packages/renderer/MdToHtml/rules/checkbox.js
|
||||
packages/renderer/MdToHtml/rules/checkbox.js.map
|
||||
@@ -1375,6 +1402,9 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
|
||||
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
|
||||
packages/renderer/MdToHtml/rules/sanitize_html.js
|
||||
packages/renderer/MdToHtml/rules/sanitize_html.js.map
|
||||
packages/renderer/htmlUtils.d.ts
|
||||
packages/renderer/htmlUtils.js
|
||||
packages/renderer/htmlUtils.js.map
|
||||
packages/renderer/index.d.ts
|
||||
packages/renderer/index.js
|
||||
packages/renderer/index.js.map
|
||||
@@ -1384,4 +1414,7 @@ packages/renderer/noteStyle.js.map
|
||||
packages/renderer/pathUtils.d.ts
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/pathUtils.js.map
|
||||
packages/renderer/utils.d.ts
|
||||
packages/renderer/utils.js
|
||||
packages/renderer/utils.js.map
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
|
||||
@@ -15,7 +15,8 @@ module.exports = {
|
||||
'Atomics': 'readonly',
|
||||
'SharedArrayBuffer': 'readonly',
|
||||
|
||||
// Jasmine variables
|
||||
// Jest variables
|
||||
'test': 'readonly',
|
||||
'expect': 'readonly',
|
||||
'describe': 'readonly',
|
||||
'it': 'readonly',
|
||||
@@ -23,10 +24,6 @@ module.exports = {
|
||||
'afterAll': 'readonly',
|
||||
'beforeEach': 'readonly',
|
||||
'afterEach': 'readonly',
|
||||
'jasmine': 'readonly',
|
||||
|
||||
// Jest variables
|
||||
'test': 'readonly',
|
||||
|
||||
// React Native variables
|
||||
'__DEV__': 'readonly',
|
||||
|
||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -233,6 +233,15 @@ packages/app-cli/tests/support/plugins/events/api/types.js.map
|
||||
packages/app-cli/tests/support/plugins/events/src/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/events/src/index.js
|
||||
packages/app-cli/tests/support/plugins/events/src/index.js.map
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/index.js
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/index.js.map
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/types.d.ts
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/types.js
|
||||
packages/app-cli/tests/support/plugins/jpl_test/api/types.js.map
|
||||
packages/app-cli/tests/support/plugins/jpl_test/src/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/jpl_test/src/index.js
|
||||
packages/app-cli/tests/support/plugins/jpl_test/src/index.js.map
|
||||
packages/app-cli/tests/support/plugins/json_export/api/index.d.ts
|
||||
packages/app-cli/tests/support/plugins/json_export/api/index.js
|
||||
packages/app-cli/tests/support/plugins/json_export/api/index.js.map
|
||||
@@ -359,6 +368,9 @@ packages/app-desktop/gui/ConfigScreen/ConfigScreen.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js
|
||||
packages/app-desktop/gui/ConfigScreen/SideBar.js.map
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.d.ts
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.js.map
|
||||
packages/app-desktop/gui/DropboxLoginScreen.d.ts
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js.map
|
||||
@@ -722,6 +734,9 @@ packages/app-desktop/gui/ToolbarButton/ToolbarButton.js.map
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.d.ts
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js.map
|
||||
packages/app-desktop/gui/dialogs.d.ts
|
||||
packages/app-desktop/gui/dialogs.js
|
||||
packages/app-desktop/gui/dialogs.js.map
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.d.ts
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.js.map
|
||||
@@ -734,6 +749,9 @@ packages/app-desktop/gui/hooks/usePrevious.js.map
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.d.ts
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
||||
packages/app-desktop/gui/hooks/usePropsDebugger.js.map
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.d.ts
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js
|
||||
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js.map
|
||||
packages/app-desktop/gui/menuCommandNames.d.ts
|
||||
packages/app-desktop/gui/menuCommandNames.js
|
||||
packages/app-desktop/gui/menuCommandNames.js.map
|
||||
@@ -1325,6 +1343,9 @@ packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.js.map
|
||||
packages/renderer/HtmlToHtml.d.ts
|
||||
packages/renderer/HtmlToHtml.js
|
||||
packages/renderer/HtmlToHtml.js.map
|
||||
packages/renderer/InMemoryCache.d.ts
|
||||
packages/renderer/InMemoryCache.js
|
||||
packages/renderer/InMemoryCache.js.map
|
||||
@@ -1334,6 +1355,12 @@ packages/renderer/MarkupToHtml.js.map
|
||||
packages/renderer/MdToHtml.d.ts
|
||||
packages/renderer/MdToHtml.js
|
||||
packages/renderer/MdToHtml.js.map
|
||||
packages/renderer/MdToHtml/linkReplacement.d.ts
|
||||
packages/renderer/MdToHtml/linkReplacement.js
|
||||
packages/renderer/MdToHtml/linkReplacement.js.map
|
||||
packages/renderer/MdToHtml/linkReplacement.test.d.ts
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js.map
|
||||
packages/renderer/MdToHtml/rules/checkbox.d.ts
|
||||
packages/renderer/MdToHtml/rules/checkbox.js
|
||||
packages/renderer/MdToHtml/rules/checkbox.js.map
|
||||
@@ -1367,6 +1394,9 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
|
||||
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
|
||||
packages/renderer/MdToHtml/rules/sanitize_html.js
|
||||
packages/renderer/MdToHtml/rules/sanitize_html.js.map
|
||||
packages/renderer/htmlUtils.d.ts
|
||||
packages/renderer/htmlUtils.js
|
||||
packages/renderer/htmlUtils.js.map
|
||||
packages/renderer/index.d.ts
|
||||
packages/renderer/index.js
|
||||
packages/renderer/index.js.map
|
||||
@@ -1376,4 +1406,7 @@ packages/renderer/noteStyle.js.map
|
||||
packages/renderer/pathUtils.d.ts
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/pathUtils.js.map
|
||||
packages/renderer/utils.d.ts
|
||||
packages/renderer/utils.js
|
||||
packages/renderer/utils.js.map
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
@@ -13,6 +13,7 @@
|
||||
"_vieux/": true,
|
||||
".gitignore": true,
|
||||
".eslintignore": true,
|
||||
"**/*.jpl": true,
|
||||
"./packages/app-cli/**/*.*~": true,
|
||||
"./packages/app-cli/**/*.mo": true,
|
||||
"./packages/app-cli/**/build/": true,
|
||||
@@ -55,7 +56,6 @@
|
||||
"./packages/app-desktop/**/*.min.js": true,
|
||||
"./packages/app-desktop/**/dist/": true,
|
||||
"./packages/app-desktop/**/gui/note-viewer/pluginAssets/": true,
|
||||
"./packages/app-desktop/**/lib/": true,
|
||||
"./packages/app-desktop/**/node_modules/": true,
|
||||
"./packages/app-desktop/**/packageInfo.js": true,
|
||||
"./packages/app-desktop/**/pluginAssets/": true,
|
||||
@@ -242,7 +242,6 @@
|
||||
"packages/app-desktop/**/*.min.js": true,
|
||||
"packages/app-desktop/**/dist/": true,
|
||||
"packages/app-desktop/**/gui/note-viewer/pluginAssets/": true,
|
||||
"packages/app-desktop/**/lib/": true,
|
||||
"packages/app-desktop/**/node_modules/": true,
|
||||
"packages/app-desktop/**/packageInfo.js": true,
|
||||
"packages/app-desktop/**/pluginAssets/": true,
|
||||
|
||||
22
package-lock.json
generated
22
package-lock.json
generated
@@ -1227,12 +1227,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/jasmine": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.0.tgz",
|
||||
"integrity": "sha512-CPT4r0a63e5wpNj5ejMnconM7a+0Hdx6/APsyw8AQOHk0/Mxp3xYrym1ZabWJiYuQkgKB3MonYoN04mxtvAvRA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
||||
@@ -6439,22 +6433,6 @@
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
|
||||
"dev": true
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.3.tgz",
|
||||
"integrity": "sha512-Th91zHsbsALWjDUIiU5d/W5zaYQsZFMPTdeNmi8GivZPmAaUAK8MblSG3yQI4VMGC/abF2us7ex60NH1AAIMTA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.6",
|
||||
"jasmine-core": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"jasmine-core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz",
|
||||
"integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
||||
14
package.json
14
package.json
@@ -7,6 +7,12 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"addPackageCli": "lerna add --scope joplin",
|
||||
"addPackageCliD": "lerna add --scope joplin -D",
|
||||
"addPackageDesktop": "lerna add --scope @joplin/app-desktop",
|
||||
"addPackageDesktopD": "lerna add --scope @joplin/app-desktop -D",
|
||||
"addPackageMobile": "lerna add --scope @joplin/app-mobile",
|
||||
"addPackageMobileD": "lerna add --scope @joplin/app-mobile -D",
|
||||
"buildApiDoc": "npm start --prefix=packages/app-cli -- apidoc ../../readme/api/references/rest_api.md",
|
||||
"buildDoc": "./packages/tools/build-all.sh",
|
||||
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out docs/api/references/plugin_api packages/lib/services/plugins/api/",
|
||||
@@ -26,11 +32,11 @@
|
||||
"releaseDesktop": "node packages/tools/release-electron.js",
|
||||
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
|
||||
"setupNewRelease": "node ./packages/tools/setupNewRelease",
|
||||
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
|
||||
"tsc": "lerna run tsc --stream --parallel",
|
||||
"test": "lerna run test --stream",
|
||||
"test-ci": "lerna run test-ci --stream",
|
||||
"test": "lerna run test --stream",
|
||||
"tsc": "lerna run tsc --stream --parallel",
|
||||
"updateIgnored": "gulp updateIgnoredTypeScriptBuild",
|
||||
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
|
||||
"watch": "lerna run watch --stream --parallel"
|
||||
},
|
||||
"husky": {
|
||||
@@ -39,7 +45,6 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^3.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
||||
"@typescript-eslint/parser": "^4.6.0",
|
||||
"eslint": "^7.6.0",
|
||||
@@ -50,7 +55,6 @@
|
||||
"glob": "^7.1.6",
|
||||
"gulp": "^4.0.2",
|
||||
"husky": "^3.0.2",
|
||||
"jasmine": "^3.5.0",
|
||||
"lerna": "^3.22.1",
|
||||
"lint-staged": "^9.2.1",
|
||||
"typedoc": "^0.17.8",
|
||||
|
||||
@@ -426,7 +426,7 @@ class Application extends BaseApplication {
|
||||
|
||||
const AppGui = require('./app-gui.js');
|
||||
this.gui_ = new AppGui(this, this.store(), keymap);
|
||||
this.gui_.setLogger(this.logger_);
|
||||
this.gui_.setLogger(this.logger());
|
||||
await this.gui_.start();
|
||||
|
||||
// Since the settings need to be loaded before the store is created, it will never
|
||||
|
||||
@@ -30,12 +30,12 @@ module.exports = {
|
||||
],
|
||||
|
||||
testPathIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'/tests\\/support/',
|
||||
'/build/',
|
||||
'test-utils.js',
|
||||
'file_api_driver.js',
|
||||
'/tests\\/tmp/',
|
||||
'<rootDir>/node_modules/',
|
||||
'<rootDir>/tests/support/',
|
||||
'<rootDir>/build/',
|
||||
'<rootDir>/tests/test-utils.js',
|
||||
'<rootDir>/tests/file_api_driver.js',
|
||||
'<rootDir>/tests/tmp/',
|
||||
],
|
||||
|
||||
// To avoid this warning:
|
||||
@@ -52,5 +52,5 @@ module.exports = {
|
||||
],
|
||||
|
||||
testEnvironment: 'node',
|
||||
setupFilesAfterEnv: ['./jest.setup.js'],
|
||||
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
|
||||
};
|
||||
|
||||
46
packages/app-cli/package-lock.json
generated
46
packages/app-cli/package-lock.json
generated
@@ -898,11 +898,15 @@
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"@types/jasmine": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.0.tgz",
|
||||
"integrity": "sha512-CPT4r0a63e5wpNj5ejMnconM7a+0Hdx6/APsyw8AQOHk0/Mxp3xYrym1ZabWJiYuQkgKB3MonYoN04mxtvAvRA==",
|
||||
"dev": true
|
||||
"@types/jest": {
|
||||
"version": "26.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz",
|
||||
"integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jest-diff": "^26.0.0",
|
||||
"pretty-format": "^26.0.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.6",
|
||||
@@ -4511,38 +4515,6 @@
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.5.0.tgz",
|
||||
"integrity": "sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.4",
|
||||
"jasmine-core": "~3.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
|
||||
"integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jasmine-core": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz",
|
||||
"integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==",
|
||||
"dev": true
|
||||
},
|
||||
"jest": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz",
|
||||
|
||||
@@ -66,10 +66,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "^1.0.9",
|
||||
"@types/jasmine": "^3.6.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
"gulp": "^4.0.2",
|
||||
"jasmine": "^3.5.0",
|
||||
"jest": "^26.6.3",
|
||||
"temp": "^0.9.1",
|
||||
"typescript": "^4.0.5"
|
||||
|
||||
@@ -9,7 +9,7 @@ const Folder = require('@joplin/lib/models/Folder.js');
|
||||
const Note = require('@joplin/lib/models/Note.js');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const HtmlToHtml = require('@joplin/renderer/HtmlToHtml');
|
||||
const HtmlToHtml = require('@joplin/renderer/HtmlToHtml').default;
|
||||
const { enexXmlToMd } = require('@joplin/lib/import-enex-md-gen.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
|
||||
@@ -262,14 +262,14 @@ describe('models_Note', function() {
|
||||
for (const testCase of testCases) {
|
||||
const [useAbsolutePaths, input, expected] = testCase;
|
||||
const internalToExternal = await Note.replaceResourceInternalToExternalLinks(input, { useAbsolutePaths });
|
||||
expect(internalToExternal).toBe(expected, 'replaceResourceInternalToExternalLinks failed');
|
||||
expect(internalToExternal).toBe(expected);
|
||||
|
||||
const externalToInternal = await Note.replaceResourceExternalToInternalLinks(internalToExternal, { useAbsolutePaths });
|
||||
expect(externalToInternal).toBe(input, 'replaceResourceExternalToInternalLinks failed');
|
||||
expect(externalToInternal).toBe(input);
|
||||
}
|
||||
|
||||
const result = await Note.replaceResourceExternalToInternalLinks(`[](joplin://${note1.id})`);
|
||||
expect(result).toBe(`[](:/${note1.id})`, 'replaceResourceExternalToInternalLinks failed (note link)');
|
||||
expect(result).toBe(`[](:/${note1.id})`);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ async function recreateExportDir() {
|
||||
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}`);
|
||||
expect(model1[f]).toBe(model2[f]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
|
||||
import MdToHtml from '@joplin/renderer/MdToHtml';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir } = require('./test-utils.js');
|
||||
const fs = require('fs-extra');
|
||||
const { asyncTest, expectNotThrow, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir } = require('./test-utils.js');
|
||||
const Note = require('@joplin/lib/models/Note');
|
||||
const Folder = require('@joplin/lib/models/Folder');
|
||||
|
||||
@@ -43,7 +45,7 @@ describe('services_PluginService', function() {
|
||||
|
||||
it('should load and run a simple plugin', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/simple`]);
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/simple`], {});
|
||||
|
||||
expect(() => service.pluginById('org.joplinapp.plugins.Simple')).not.toThrowError();
|
||||
|
||||
@@ -59,13 +61,13 @@ describe('services_PluginService', function() {
|
||||
|
||||
it('should load and run a simple plugin and handle trailing slash', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/simple/`]);
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/simple/`], {});
|
||||
expect(() => service.pluginById('org.joplinapp.plugins.Simple')).not.toThrowError();
|
||||
}));
|
||||
|
||||
it('should load and run a plugin that uses external packages', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/withExternalModules`]);
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/withExternalModules`], {});
|
||||
expect(() => service.pluginById('org.joplinapp.plugins.ExternalModuleDemo')).not.toThrowError();
|
||||
|
||||
const allFolders = await Folder.all();
|
||||
@@ -78,7 +80,7 @@ describe('services_PluginService', function() {
|
||||
|
||||
it('should load multiple plugins from a directory', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
await service.loadAndRunPlugins(`${testPluginDir}/multi_plugins`);
|
||||
await service.loadAndRunPlugins(`${testPluginDir}/multi_plugins`, {});
|
||||
|
||||
const plugin1 = service.pluginById('org.joplinapp.plugins.MultiPluginDemo1');
|
||||
const plugin2 = service.pluginById('org.joplinapp.plugins.MultiPluginDemo2');
|
||||
@@ -125,14 +127,14 @@ describe('services_PluginService', function() {
|
||||
|
||||
it('should load plugins from JS bundle files', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
await service.loadAndRunPlugins(`${testPluginDir}/jsbundles`);
|
||||
await service.loadAndRunPlugins(`${testPluginDir}/jsbundles`, {});
|
||||
expect(!!service.pluginById('org.joplinapp.plugins.JsBundleDemo')).toBe(true);
|
||||
expect((await Folder.all()).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should load plugins from JPL archive', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`]);
|
||||
await service.loadAndRunPlugins([`${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`], {});
|
||||
expect(!!service.pluginById('org.joplinapp.FirstJplPlugin')).toBe(true);
|
||||
expect((await Folder.all()).length).toBe(1);
|
||||
}));
|
||||
@@ -248,10 +250,35 @@ describe('services_PluginService', function() {
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const [appVersion, expected] = testCase;
|
||||
const plugin = await newPluginService(appVersion as string).loadPluginFromJsBundle('', pluginScript);
|
||||
expect(plugin.enabled).toBe(expected as boolean);
|
||||
const [appVersion, hasNoError] = testCase;
|
||||
const service = newPluginService(appVersion as string);
|
||||
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
|
||||
|
||||
if (hasNoError) {
|
||||
await expectNotThrow(() => service.runPlugin(plugin));
|
||||
} else {
|
||||
await expectThrow(() => service.runPlugin(plugin));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
it('should install a plugin', asyncTest(async () => {
|
||||
const service = newPluginService();
|
||||
const pluginPath = `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`;
|
||||
await service.installPlugin(pluginPath);
|
||||
const installedPluginPath = `${Setting.value('pluginDir')}/org.joplinapp.FirstJplPlugin.jpl`;
|
||||
expect(await fs.existsSync(installedPluginPath)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should rename the plugin archive to the right name', asyncTest(async () => {
|
||||
const tempDir = await createTempDir();
|
||||
const service = newPluginService();
|
||||
const pluginPath = `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`;
|
||||
const tempPath = `${tempDir}/something.jpl`;
|
||||
await shim.fsDriver().copy(pluginPath, tempPath);
|
||||
const installedPluginPath = `${Setting.value('pluginDir')}/org.joplinapp.FirstJplPlugin.jpl`;
|
||||
await service.installPlugin(tempPath);
|
||||
expect(await fs.existsSync(installedPluginPath)).toBe(true);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, mockDate, restoreDate } = require('./test-utils.js');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, restoreDate } = require('./test-utils.js');
|
||||
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine');
|
||||
const Note = require('@joplin/lib/models/Note');
|
||||
const ItemChange = require('@joplin/lib/models/ItemChange');
|
||||
|
||||
@@ -23,12 +23,6 @@ let engine = null;
|
||||
|
||||
const ids = (array) => array.map(a => a.id);
|
||||
|
||||
// For pretty printing.
|
||||
// See https://stackoverflow.com/questions/23676459/karma-jasmine-pretty-printing-object-comparison/26324116
|
||||
// jasmine.pp = function(obj) {
|
||||
// return JSON.stringify(obj, undefined, 2);
|
||||
// };
|
||||
|
||||
describe('services_SearchFilter', function() {
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
|
||||
@@ -538,7 +538,7 @@ describe('services_rest_Api', function() {
|
||||
const r3 = await api.route(RequestMethod.GET, 'folders', { ...baseQuery, page: 3 });
|
||||
|
||||
expect(r3.items.length).toBe(0);
|
||||
expect(r3.has_more).toBe(undefined);
|
||||
expect(r3.has_more).toBe(false);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -560,7 +560,7 @@ describe('services_rest_Api', function() {
|
||||
|
||||
expect(r2.items.length).toBe(1);
|
||||
expect(r2.items[0].title).toBe('folder4');
|
||||
expect(r2.has_more).toBe(undefined);
|
||||
expect(r2.has_more).toBe(false);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -787,11 +793,6 @@
|
||||
"wrap-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.58.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.2.tgz",
|
||||
"integrity": "sha512-K/hOh24cCwRutd1Mk3uLtjWzNISOkm4fvXiMO7LucCrqbh6aJDdtqUziim3MZUI6wOY0rvY1SlL1Ork01uMy6w=="
|
||||
},
|
||||
"collection-visit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||
@@ -1617,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2155,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2564,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3788,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1621,6 +1627,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2159,6 +2177,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2575,6 +2611,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3799,6 +3841,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
17
packages/app-cli/tests/support/plugins/installPlugins.sh
Executable file
17
packages/app-cli/tests/support/plugins/installPlugins.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# cd "$SCRIPT_DIR/jpl_test/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/codemirror_content_script/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/content_script/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/dialog/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/editor_context_menu/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/events/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/json_export/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/menu/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/multi_selection/" && npm i && \
|
||||
# cd "$SCRIPT_DIR/register_command/" && npm i && \
|
||||
cd "$SCRIPT_DIR/selected_text/" && npm i && \
|
||||
cd "$SCRIPT_DIR/settings/" && npm i && \
|
||||
cd "$SCRIPT_DIR/toc/" && npm i && \
|
||||
cd "$SCRIPT_DIR/withExternalModules/" && npm i
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "org.joplinapp.FirstJplPlugin",
|
||||
"name": "joplin_plugin",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "test_plugin",
|
||||
"name": "joplin_plugin",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -49,6 +49,6 @@ joplin.plugins.register({
|
||||
},
|
||||
});
|
||||
|
||||
joplin.views.toolbarButtons.create('prettyMarkdownTable', ToolbarButtonLocation.EditorToolbar);
|
||||
joplin.views.toolbarButtons.create('prettyMarkdownTableButton', 'prettyMarkdownTable', ToolbarButtonLocation.EditorToolbar);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2559,6 +2595,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3783,6 +3825,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -37,7 +37,7 @@ joplin.plugins.register({
|
||||
},
|
||||
});
|
||||
|
||||
await joplin.views.toolbarButtons.create('incValue', ToolbarButtonLocation.NoteToolbar);
|
||||
await joplin.views.toolbarButtons.create('checkValue', ToolbarButtonLocation.NoteToolbar);
|
||||
await joplin.views.toolbarButtons.create('incValueButton', 'incValue', ToolbarButtonLocation.NoteToolbar);
|
||||
await joplin.views.toolbarButtons.create('checkValueButton', 'checkValue', ToolbarButtonLocation.NoteToolbar);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "test_webview",
|
||||
"name": "joplin_plugin",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
@@ -407,6 +407,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1873,6 +1879,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2378,6 +2396,24 @@
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2420,11 +2456,6 @@
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.toarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
|
||||
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -2746,14 +2777,6 @@
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-emoji": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
|
||||
"integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==",
|
||||
"requires": {
|
||||
"lodash.toarray": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node-libs-browser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
||||
@@ -2863,6 +2886,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3989,6 +4018,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unorm": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz",
|
||||
@@ -4081,10 +4116,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"uslug": {
|
||||
"version": "git+https://github.com/laurent22/uslug.git#ba2834d79beb0435318709958b2f5e817d96674d",
|
||||
"from": "git+https://github.com/laurent22/uslug.git#emoji-support",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uslug/-/uslug-1.0.4.tgz",
|
||||
"integrity": "sha1-uaIvCRTgqGFAYz2swwLl9PpFBnc=",
|
||||
"requires": {
|
||||
"node-emoji": "^1.10.0",
|
||||
"unorm": ">= 1.0.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,5 +19,8 @@
|
||||
"typescript": "^3.9.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"uslug": "^1.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,6 +400,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1612,6 +1618,18 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -2150,6 +2168,24 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -2564,6 +2600,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-build-webpack": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-build-webpack/-/on-build-webpack-0.1.0.tgz",
|
||||
"integrity": "sha1-oofA4Xdm5hQZJuXyy7DYu1O3aBQ=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -3788,6 +3830,12 @@
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
|
||||
"dev": true
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
||||
@@ -19,5 +19,8 @@
|
||||
"typescript": "^3.9.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"left-pad": "^1.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,9 +131,11 @@ setSyncTargetName('memory');
|
||||
|
||||
const syncDir = `${__dirname}/../tests/sync`;
|
||||
|
||||
let defaultJasmineTimeout = 90 * 1000;
|
||||
if (isNetworkSyncTarget_) defaultJasmineTimeout = 60 * 1000 * 10;
|
||||
if (typeof jasmine !== 'undefined') jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultJasmineTimeout;
|
||||
// TODO: Should probably update this for Jest?
|
||||
|
||||
// let defaultJasmineTimeout = 90 * 1000;
|
||||
// if (isNetworkSyncTarget_) defaultJasmineTimeout = 60 * 1000 * 10;
|
||||
// if (typeof jasmine !== 'undefined') jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultJasmineTimeout;
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget('console');
|
||||
@@ -145,6 +147,8 @@ logger.addTarget('console');
|
||||
logger.addTarget('file', { path: `${logDir}/log.txt` });
|
||||
logger.setLevel(Logger.LEVEL_WARN); // Set to DEBUG to display sync process in console
|
||||
|
||||
Logger.initializeGlobalLogger(logger);
|
||||
|
||||
BaseItem.loadClass('Note', Note);
|
||||
BaseItem.loadClass('Folder', Folder);
|
||||
BaseItem.loadClass('Resource', Resource);
|
||||
@@ -212,6 +216,7 @@ async function switchClient(id, options = null) {
|
||||
await Setting.reset();
|
||||
Setting.setConstant('resourceDirName', resourceDirName(id));
|
||||
Setting.setConstant('resourceDir', resourceDir(id));
|
||||
Setting.setConstant('pluginDir', pluginDir(id));
|
||||
|
||||
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
|
||||
|
||||
@@ -290,6 +295,11 @@ function resourceDir(id = null) {
|
||||
return `${__dirname}/data/${resourceDirName(id)}`;
|
||||
}
|
||||
|
||||
function pluginDir(id = null) {
|
||||
if (id === null) id = currentClient_;
|
||||
return `${__dirname}/data/plugins-${id}`;
|
||||
}
|
||||
|
||||
async function setupDatabaseAndSynchronizer(id = null, options = null) {
|
||||
if (id === null) id = currentClient_;
|
||||
|
||||
@@ -303,6 +313,9 @@ async function setupDatabaseAndSynchronizer(id = null, options = null) {
|
||||
await fs.remove(resourceDir(id));
|
||||
await fs.mkdirp(resourceDir(id), 0o755);
|
||||
|
||||
await fs.remove(pluginDir(id));
|
||||
await fs.mkdirp(pluginDir(id), 0o755);
|
||||
|
||||
if (!synchronizers_[id]) {
|
||||
const SyncTargetClass = SyncTargetRegistry.classById(syncTargetId_);
|
||||
const syncTarget = new SyncTargetClass(db(id));
|
||||
@@ -544,7 +557,7 @@ function asyncTest(callback) {
|
||||
await callback();
|
||||
} catch (error) {
|
||||
if (error.constructor && error.constructor.name === 'ExpectationFailed') {
|
||||
// OK - will be reported by Jasmine
|
||||
// OK - will be reported by Jest
|
||||
} else {
|
||||
// Better to rethrow exception as stack trace is more useful in this case
|
||||
throw error;
|
||||
@@ -656,15 +669,17 @@ async function createTempDir() {
|
||||
return tempDirPath;
|
||||
}
|
||||
|
||||
function mockDate(year, month, day, tick) {
|
||||
const fixedDate = new Date(2020, 0, 1);
|
||||
jasmine.clock().install();
|
||||
jasmine.clock().mockDate(fixedDate);
|
||||
}
|
||||
// TODO: Update for Jest
|
||||
|
||||
function restoreDate() {
|
||||
jasmine.clock().uninstall();
|
||||
}
|
||||
// function mockDate(year, month, day, tick) {
|
||||
// const fixedDate = new Date(2020, 0, 1);
|
||||
// jasmine.clock().install();
|
||||
// jasmine.clock().mockDate(fixedDate);
|
||||
// }
|
||||
|
||||
// function restoreDate() {
|
||||
// jasmine.clock().uninstall();
|
||||
// }
|
||||
|
||||
// Application for feature integration testing
|
||||
class TestApp extends BaseApplication {
|
||||
@@ -721,7 +736,7 @@ class TestApp extends BaseApplication {
|
||||
}
|
||||
|
||||
async profileDir() {
|
||||
return await Setting.value('profileDir');
|
||||
return Setting.value('profileDir');
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
@@ -734,4 +749,4 @@ class TestApp extends BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, mockDate, restoreDate, TestApp };
|
||||
module.exports = { synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
|
||||
|
||||
1
packages/app-desktop/.gitignore
vendored
1
packages/app-desktop/.gitignore
vendored
@@ -1,7 +1,6 @@
|
||||
node_modules/
|
||||
packageInfo.js
|
||||
dist/
|
||||
lib/
|
||||
*.min.js
|
||||
.DS_Store
|
||||
gui/note-viewer/pluginAssets/
|
||||
|
||||
@@ -87,6 +87,7 @@ export default class ElectronAppWrapper {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
spellcheck: true,
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
webviewTag: true,
|
||||
// We start with a hidden window, which is then made visible depending on the showTrayIcon setting
|
||||
|
||||
@@ -490,17 +490,24 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
private async initPluginService() {
|
||||
const pluginLogger = new Logger();
|
||||
pluginLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-plugins.txt` });
|
||||
pluginLogger.addTarget(TargetType.Console, { prefix: 'Plugin Service:' });
|
||||
pluginLogger.setLevel(Setting.value('env') == 'dev' ? Logger.LEVEL_DEBUG : Logger.LEVEL_INFO);
|
||||
const service = PluginService.instance();
|
||||
|
||||
const pluginRunner = new PluginRunner();
|
||||
PluginService.instance().setLogger(pluginLogger);
|
||||
PluginService.instance().initialize(packageInfo.version, PlatformImplementation.instance(), pluginRunner, this.store());
|
||||
service.initialize(packageInfo.version, PlatformImplementation.instance(), pluginRunner, this.store());
|
||||
|
||||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
|
||||
|
||||
// Users can add and remove plugins from the config screen at any
|
||||
// time, however we only effectively uninstall the plugin the next
|
||||
// time the app is started. What plugin should be uninstalled is
|
||||
// stored in the settings.
|
||||
const newSettings = await service.uninstallPlugins(pluginSettings);
|
||||
Setting.setValue('plugins.states', newSettings);
|
||||
|
||||
try {
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) await PluginService.instance().loadAndRunPlugins(Setting.value('pluginDir'));
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) {
|
||||
await service.loadAndRunPlugins(Setting.value('pluginDir'), pluginSettings);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('pluginDir')}:`, error);
|
||||
}
|
||||
@@ -508,12 +515,12 @@ class Application extends BaseApplication {
|
||||
try {
|
||||
if (Setting.value('plugins.devPluginPaths')) {
|
||||
const paths = Setting.value('plugins.devPluginPaths').split(',').map((p: string) => p.trim());
|
||||
await PluginService.instance().loadAndRunPlugins(paths);
|
||||
await service.loadAndRunPlugins(paths, pluginSettings, true);
|
||||
}
|
||||
|
||||
// Also load dev plugins that have passed via command line arguments
|
||||
if (Setting.value('startupDevPlugins')) {
|
||||
await PluginService.instance().loadAndRunPlugins(Setting.value('startupDevPlugins'));
|
||||
await service.loadAndRunPlugins(Setting.value('startupDevPlugins'), pluginSettings, true);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
|
||||
@@ -723,6 +730,14 @@ class Application extends BaseApplication {
|
||||
// console.info(CommandService.instance().commandsToMarkdownTable(this.store().getState()));
|
||||
// }, 2000);
|
||||
|
||||
// this.dispatch({
|
||||
// type: 'NAV_GO',
|
||||
// routeName: 'Config',
|
||||
// props: {
|
||||
// defaultSection: 'plugins',
|
||||
// },
|
||||
// });
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ export class Bridge {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
showOpenDialog(options: any) {
|
||||
showOpenDialog(options: any = null) {
|
||||
const { dialog } = require('electron');
|
||||
if (!options) options = {};
|
||||
let fileType = 'file';
|
||||
@@ -117,13 +117,16 @@ export class Bridge {
|
||||
}
|
||||
|
||||
showConfirmMessageBox(message: string, options: any = null) {
|
||||
if (options === null) options = {};
|
||||
options = {
|
||||
buttons: [_('OK'), _('Cancel')],
|
||||
...options,
|
||||
};
|
||||
|
||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||
type: 'question',
|
||||
message: message,
|
||||
cancelId: 1,
|
||||
buttons: [_('OK'), _('Cancel')],
|
||||
buttons: options.buttons,
|
||||
}, options));
|
||||
|
||||
return result === 0;
|
||||
@@ -167,8 +170,8 @@ export class Bridge {
|
||||
return require('electron').shell.openExternal(url);
|
||||
}
|
||||
|
||||
openItem(fullPath: string) {
|
||||
return require('electron').shell.openItem(fullPath);
|
||||
async openItem(fullPath: string) {
|
||||
return require('electron').shell.openPath(fullPath);
|
||||
}
|
||||
|
||||
checkForUpdates(inBackground: boolean, window: any, logFilePath: string, options: any) {
|
||||
|
||||
@@ -3,17 +3,23 @@ import SideBar from './SideBar';
|
||||
import ButtonBar from './ButtonBar';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../../services/bridge';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import control_PluginsStates from './controls/PluginsStates';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const pathUtils = require('@joplin/lib/path-utils');
|
||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const { EncryptionConfigScreen } = require('../EncryptionConfigScreen.min');
|
||||
const { ClipperConfigScreen } = require('../ClipperConfigScreen.min');
|
||||
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
||||
|
||||
const settingKeyToControl: any = {
|
||||
'plugins.states': control_PluginsStates,
|
||||
};
|
||||
|
||||
class ConfigScreenComponent extends React.Component<any, any> {
|
||||
|
||||
rowStyle_: any = null;
|
||||
@@ -27,6 +33,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
selectedSectionName: 'general',
|
||||
screenName: '',
|
||||
changedSettingKeys: [],
|
||||
needRestart: false,
|
||||
};
|
||||
|
||||
this.rowStyle_ = {
|
||||
@@ -41,6 +48,8 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
this.onCancelClick = this.onCancelClick.bind(this);
|
||||
this.onSaveClick = this.onSaveClick.bind(this);
|
||||
this.onApplyClick = this.onApplyClick.bind(this);
|
||||
this.renderLabel = this.renderLabel.bind(this);
|
||||
this.renderDescription = this.renderDescription.bind(this);
|
||||
}
|
||||
|
||||
async checkSyncConfig_() {
|
||||
@@ -261,6 +270,40 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
);
|
||||
}
|
||||
|
||||
private labelStyle(themeId: number) {
|
||||
const theme = themeStyle(themeId);
|
||||
return Object.assign({}, theme.textStyle, {
|
||||
display: 'block',
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.083333,
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.mainPadding / 4,
|
||||
});
|
||||
}
|
||||
|
||||
private descriptionStyle(themeId: number) {
|
||||
const theme = themeStyle(themeId);
|
||||
return Object.assign({}, theme.textStyle, {
|
||||
color: theme.colorFaded,
|
||||
fontStyle: 'italic',
|
||||
maxWidth: '70em',
|
||||
marginTop: 5,
|
||||
});
|
||||
}
|
||||
|
||||
private renderLabel(themeId: number, label: string) {
|
||||
const labelStyle = this.labelStyle(themeId);
|
||||
return (
|
||||
<div style={labelStyle}>
|
||||
<label>{label}</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderDescription(themeId: number, description: string) {
|
||||
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
|
||||
}
|
||||
|
||||
settingToComponent(key: string, value: any) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
@@ -270,13 +313,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
marginBottom: theme.mainPadding,
|
||||
};
|
||||
|
||||
const labelStyle = Object.assign({}, theme.textStyle, {
|
||||
display: 'block',
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.083333,
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.mainPadding / 4,
|
||||
});
|
||||
const labelStyle = this.labelStyle(this.props.themeId);
|
||||
|
||||
const subLabel = Object.assign({}, labelStyle, {
|
||||
display: 'block',
|
||||
@@ -297,13 +334,6 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
};
|
||||
|
||||
const descriptionStyle = Object.assign({}, theme.textStyle, {
|
||||
color: theme.colorFaded,
|
||||
marginTop: 5,
|
||||
fontStyle: 'italic',
|
||||
maxWidth: '70em',
|
||||
});
|
||||
|
||||
const textInputBaseStyle = Object.assign({}, controlStyle, {
|
||||
fontFamily: theme.fontFamily,
|
||||
border: '1px solid',
|
||||
@@ -318,18 +348,39 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
});
|
||||
|
||||
const updateSettingValue = (key: string, value: any) => {
|
||||
// console.info(key + ' = ' + value);
|
||||
return shared.updateSettingValue(this, key, value);
|
||||
};
|
||||
const md = Setting.settingMetadata(key);
|
||||
if (md.needRestart) {
|
||||
this.setState({ needRestart: true });
|
||||
}
|
||||
shared.updateSettingValue(this, key, value);
|
||||
|
||||
// Component key needs to be key+value otherwise it doesn't update when the settings change.
|
||||
if (md.autoSave) {
|
||||
shared.scheduleSaveSettings(this);
|
||||
}
|
||||
};
|
||||
|
||||
const md = Setting.settingMetadata(key);
|
||||
|
||||
const descriptionText = Setting.keyDescription(key, 'desktop');
|
||||
const descriptionComp = descriptionText ? <div style={descriptionStyle}>{descriptionText}</div> : null;
|
||||
const descriptionComp = this.renderDescription(this.props.themeId, descriptionText);
|
||||
|
||||
if (md.isEnum) {
|
||||
if (settingKeyToControl[key]) {
|
||||
const SettingComponent = settingKeyToControl[key];
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
{this.renderLabel(this.props.themeId, md.label())}
|
||||
{this.renderDescription(this.props.themeId, md.description ? md.description() : null)}
|
||||
<SettingComponent
|
||||
metadata={md}
|
||||
value={value}
|
||||
themeId={this.props.themeId}
|
||||
onChange={(event: any) => {
|
||||
updateSettingValue(key, event.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (md.isEnum) {
|
||||
const items = [];
|
||||
const settingOptions = md.options();
|
||||
const array = this.keyValueToArray(settingOptions);
|
||||
@@ -568,12 +619,33 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
return output;
|
||||
}
|
||||
|
||||
onApplyClick() {
|
||||
shared.saveSettings(this);
|
||||
private restartMessage() {
|
||||
return _('The application must be restarted for these changes to take effect.');
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
private async restartApp() {
|
||||
await Setting.saveAll();
|
||||
bridge().restart();
|
||||
}
|
||||
|
||||
private async checkNeedRestart() {
|
||||
if (this.state.needRestart) {
|
||||
const doItNow = await bridge().showConfirmMessageBox(this.restartMessage(), {
|
||||
buttons: [_('Do it now'), _('Later')],
|
||||
});
|
||||
|
||||
if (doItNow) await this.restartApp();
|
||||
}
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
shared.saveSettings(this);
|
||||
await this.checkNeedRestart();
|
||||
}
|
||||
|
||||
async onSaveClick() {
|
||||
shared.saveSettings(this);
|
||||
await this.checkNeedRestart();
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
}
|
||||
|
||||
@@ -621,6 +693,13 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
|
||||
const sections = shared.settingsSections({ device: 'desktop', settings });
|
||||
|
||||
const needRestartComp: any = this.state.needRestart ? (
|
||||
<div style={{ ...theme.textStyle, padding: 10, paddingLeft: 24, backgroundColor: theme.warningBackgroundColor, color: theme.color }}>
|
||||
{this.restartMessage()}
|
||||
<a style={{ ...theme.urlStyle, marginLeft: 10 }} href="#" onClick={() => { this.restartApp(); }}>{_('Restart now')}</a>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<SideBar
|
||||
@@ -630,6 +709,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
/>
|
||||
<div style={style}>
|
||||
{screenComp}
|
||||
{needRestartComp}
|
||||
<div style={containerStyle}>{settingComps}</div>
|
||||
<ButtonBar
|
||||
hasChanges={hasChanges}
|
||||
|
||||
255
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.tsx
Normal file
255
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import PluginService, { defaultPluginSetting, Plugins, PluginSetting, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import styled from 'styled-components';
|
||||
import ToggleButton from '../../lib/ToggleButton/ToggleButton';
|
||||
import Button, { ButtonLevel } from '../../Button/Button';
|
||||
import bridge from '../../../services/bridge';
|
||||
import produce from 'immer';
|
||||
|
||||
const Root = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const TableRoot = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const InstallButton = styled(Button)`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const CellRoot = styled.div`
|
||||
display: flex;
|
||||
background-color: ${props => props.theme.backgroundColor};
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 15px;
|
||||
border: 1px solid ${props => props.theme.dividerColor};
|
||||
border-radius: 6px;
|
||||
width: 250px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 1px 1px 3px rgba(0,0,0,0.2);
|
||||
`;
|
||||
|
||||
const CellTop = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const CellContent = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const CellFooter = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const DevModeLabel = styled.div`
|
||||
border: 1px solid ${props => props.theme.color};
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
font-size: ${props => props.theme.fontSize * 0.75}px;
|
||||
color: ${props => props.theme.color};
|
||||
`;
|
||||
|
||||
const StyledName = styled.div`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
color: ${props => props.theme.color};
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
color: ${props => props.theme.colorFaded};
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
line-height: 1.6em;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
value: any;
|
||||
themeId: number;
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
interface CellProps {
|
||||
item: PluginItem;
|
||||
themeId: number;
|
||||
onToggle: Function;
|
||||
onDelete: Function;
|
||||
}
|
||||
|
||||
interface PluginItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
deleted: boolean;
|
||||
devMode: boolean;
|
||||
}
|
||||
|
||||
function Cell(props: CellProps) {
|
||||
const { item } = props;
|
||||
|
||||
// For plugins in dev mode things like enabling/disabling or
|
||||
// uninstalling them doesn't make sense, as that should be done by
|
||||
// adding/removing them from wherever they were loaded from.
|
||||
|
||||
function renderToggleButton() {
|
||||
if (item.devMode) {
|
||||
return <DevModeLabel>DEV</DevModeLabel>;
|
||||
}
|
||||
|
||||
return <ToggleButton
|
||||
themeId={props.themeId}
|
||||
value={item.enabled}
|
||||
onToggle={() => props.onToggle({ item: props.item })}
|
||||
/>;
|
||||
}
|
||||
|
||||
function renderFooter() {
|
||||
if (item.devMode) return null;
|
||||
|
||||
return (
|
||||
<CellFooter>
|
||||
<Button level={ButtonLevel.Secondary} onClick={() => props.onDelete({ item: props.item })} title={_('Delete')}/>
|
||||
<div style={{ display: 'flex', flex: 1 }}/>
|
||||
</CellFooter>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellRoot>
|
||||
<CellTop>
|
||||
<StyledName mb={'5px'}>{item.name} {item.deleted ? '(Deleted)' : ''}</StyledName>
|
||||
{renderToggleButton()}
|
||||
</CellTop>
|
||||
<CellContent>
|
||||
<StyledDescription>{item.description}</StyledDescription>
|
||||
</CellContent>
|
||||
{renderFooter()}
|
||||
</CellRoot>
|
||||
);
|
||||
}
|
||||
|
||||
function usePluginItems(plugins: Plugins, settings: PluginSettings): PluginItem[] {
|
||||
return useMemo(() => {
|
||||
const output: PluginItem[] = [];
|
||||
|
||||
for (const pluginId in plugins) {
|
||||
const plugin = plugins[pluginId];
|
||||
|
||||
const setting: PluginSetting = {
|
||||
...defaultPluginSetting(),
|
||||
...settings[pluginId],
|
||||
};
|
||||
|
||||
output.push({
|
||||
id: pluginId,
|
||||
name: plugin.manifest.name,
|
||||
description: plugin.manifest.description,
|
||||
enabled: setting.enabled,
|
||||
deleted: setting.deleted,
|
||||
devMode: plugin.devMode,
|
||||
});
|
||||
}
|
||||
|
||||
output.sort((a: PluginItem, b: PluginItem) => {
|
||||
return a.name < b.name ? -1 : +1;
|
||||
});
|
||||
|
||||
return output;
|
||||
}, [plugins, settings]);
|
||||
}
|
||||
|
||||
export default function(props: Props) {
|
||||
const pluginService = PluginService.instance();
|
||||
|
||||
const pluginSettings = useMemo(() => {
|
||||
return pluginService.unserializePluginSettings(props.value);
|
||||
}, [props.value]);
|
||||
|
||||
const onDelete = useCallback(async (event: any) => {
|
||||
const item: PluginItem = event.item;
|
||||
const confirm = await bridge().showConfirmMessageBox(_('Delete plugin "%s"?', item.name));
|
||||
if (!confirm) return;
|
||||
|
||||
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
if (!draft[item.id]) draft[item.id] = defaultPluginSetting();
|
||||
draft[item.id].deleted = true;
|
||||
});
|
||||
|
||||
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
|
||||
}, [pluginSettings, props.onChange]);
|
||||
|
||||
const onToggle = useCallback((event: any) => {
|
||||
const item: PluginItem = event.item;
|
||||
|
||||
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
if (!draft[item.id]) draft[item.id] = defaultPluginSetting();
|
||||
draft[item.id].enabled = !draft[item.id].enabled;
|
||||
});
|
||||
|
||||
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
|
||||
}, [pluginSettings, props.onChange]);
|
||||
|
||||
const onInstall = useCallback(async () => {
|
||||
const result = bridge().showOpenDialog({
|
||||
filters: [{ name: 'Joplin Plugin Archive', extensions: ['jpl'] }],
|
||||
});
|
||||
|
||||
const filePath = result && result.length ? result[0] : null;
|
||||
if (!filePath) return;
|
||||
|
||||
const plugin = await pluginService.installPlugin(filePath);
|
||||
|
||||
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
draft[plugin.manifest.id] = defaultPluginSetting();
|
||||
});
|
||||
|
||||
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
|
||||
}, [pluginSettings, props.onChange]);
|
||||
|
||||
function renderCells(items: PluginItem[]) {
|
||||
const output = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (item.deleted) continue;
|
||||
|
||||
output.push(<Cell
|
||||
key={item.id}
|
||||
item={item}
|
||||
themeId={props.themeId}
|
||||
onDelete={onDelete}
|
||||
onToggle={onToggle}
|
||||
/>);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const pluginItems = usePluginItems(pluginService.plugins, pluginSettings);
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<InstallButton level={ButtonLevel.Primary} onClick={onInstall} title={_('Install plugin')}/>
|
||||
<div style={{ display: 'flex', flex: 1 }}/>
|
||||
</div>
|
||||
<TableRoot>
|
||||
{renderCells(pluginItems)}
|
||||
</TableRoot>
|
||||
</Root>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const dialogs = require('./dialogs');
|
||||
const dialogs = require('./dialogs').default;
|
||||
const shared = require('@joplin/lib/components/shared/encryption-config-shared.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import { MenuItem, MenuItemLocation } from '@joplin/lib/services/plugins/api/typ
|
||||
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
|
||||
import menuCommandNames from './menuCommandNames';
|
||||
import stateToWhenClauseContext from '../services/commands/stateToWhenClauseContext';
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const { shell, clipboard } = require('electron');
|
||||
const { clipboard } = require('electron');
|
||||
const Menu = bridge().Menu;
|
||||
const PluginManager = require('@joplin/lib/services/PluginManager');
|
||||
const TemplateUtils = require('@joplin/lib/TemplateUtils');
|
||||
@@ -300,7 +300,7 @@ function useMenu(props: Props) {
|
||||
}, {
|
||||
label: _('Open template directory'),
|
||||
click: () => {
|
||||
shell.openItem(Setting.value('templateDir'));
|
||||
bridge().openItem(Setting.value('templateDir'));
|
||||
},
|
||||
}, {
|
||||
label: _('Refresh templates'),
|
||||
|
||||
@@ -22,6 +22,8 @@ import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { ThemeAppearance } from '@joplin/lib/themes/type';
|
||||
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
|
||||
import dialogs from '../../../dialogs';
|
||||
|
||||
const Note = require('@joplin/lib/models/Note.js');
|
||||
const { clipboard } = require('electron');
|
||||
@@ -29,7 +31,6 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const dialogs = require('../../../dialogs');
|
||||
|
||||
const menuUtils = new MenuUtils(CommandService.instance());
|
||||
|
||||
@@ -255,54 +256,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onEditorContextMenu = useCallback(() => {
|
||||
const menu = new Menu();
|
||||
|
||||
const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
|
||||
const clipboardText = clipboard.readText();
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Cut'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCutText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Copy'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCopyText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Paste'),
|
||||
enabled: true,
|
||||
click: async () => {
|
||||
if (clipboardText) {
|
||||
editorPasteText();
|
||||
} else {
|
||||
// To handle pasting images
|
||||
onEditorPaste();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menuUtils.pluginContextMenuItems(props.plugins, MenuItemLocation.EditorContextMenu).forEach((item: any) => {
|
||||
menu.append(new MenuItem(item));
|
||||
});
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}, [props.content, editorCutText, editorPasteText, editorCopyText, onEditorPaste, props.plugins]);
|
||||
|
||||
const loadScript = async (script: any) => {
|
||||
return new Promise((resolve) => {
|
||||
let element: any = document.createElement('script');
|
||||
@@ -409,6 +362,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
padding-bottom: 400px !important;
|
||||
}
|
||||
|
||||
.CodeMirror-sizer {
|
||||
/* Add a fixed right padding to account for the appearance (and disappearance) */
|
||||
/* of the sidebar */
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.cm-header-1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
@@ -623,6 +582,91 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
editorRef.current.refresh();
|
||||
}, [rootSize, styles.editor, props.visiblePanes]);
|
||||
|
||||
// The below code adds support for spellchecking when it is enabled
|
||||
// It might be buggy, refer to the below issue
|
||||
// https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703
|
||||
useEffect(() => {
|
||||
function pointerInsideEditor(x: number, y: number) {
|
||||
const elements = document.getElementsByClassName('codeMirrorEditor');
|
||||
if (!elements.length) return null;
|
||||
const rect = elements[0].getBoundingClientRect();
|
||||
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y;
|
||||
}
|
||||
|
||||
function onContextMenu(_event: any, params: any) {
|
||||
if (!pointerInsideEditor(params.x, params.y)) return;
|
||||
|
||||
const menu = new Menu();
|
||||
|
||||
const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
|
||||
const clipboardText = clipboard.readText();
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Cut'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCutText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Copy'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCopyText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Paste'),
|
||||
enabled: true,
|
||||
click: async () => {
|
||||
if (clipboardText) {
|
||||
editorPasteText();
|
||||
} else {
|
||||
// To handle pasting images
|
||||
onEditorPaste();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
|
||||
|
||||
for (const item of spellCheckerMenuItems) {
|
||||
menu.append(new MenuItem(item));
|
||||
}
|
||||
|
||||
// Typically CodeMirror handles all interactions itself (highlighting etc.)
|
||||
// But in the case of clicking a mispelled word, we need electron to handle the click
|
||||
// The result is that CodeMirror doesn't know what's been selected and doesn't
|
||||
// move the cursor into the correct location.
|
||||
// and when the user selects a new spelling it will be inserted in the wrong location
|
||||
// So in this situation, we use must manually align the internal codemirror selection
|
||||
// to the contextmenu selection
|
||||
if (editorRef.current && spellCheckerMenuItems.length > 0) {
|
||||
editorRef.current.alignSelection(params);
|
||||
}
|
||||
|
||||
menuUtils.pluginContextMenuItems(props.plugins, MenuItemLocation.EditorContextMenu).forEach((item: any) => {
|
||||
menu.append(new MenuItem(item));
|
||||
});
|
||||
|
||||
menu.popup();
|
||||
}
|
||||
|
||||
bridge().window().webContents.on('context-menu', onContextMenu);
|
||||
|
||||
return () => {
|
||||
bridge().window().webContents.off('context-menu', onContextMenu);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function renderEditor() {
|
||||
|
||||
return (
|
||||
@@ -640,7 +684,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
plugins={props.plugins}
|
||||
onChange={codeMirror_change}
|
||||
onScroll={editor_scroll}
|
||||
onEditorContextMenu={onEditorContextMenu}
|
||||
onEditorPaste={onEditorPaste}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,8 @@ import 'codemirror/keymap/sublime'; // Used for swapLineUp and swapLineDown
|
||||
|
||||
import 'codemirror/mode/meta';
|
||||
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
// import eventManager from '@joplin/lib/eventManager';
|
||||
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
@@ -77,19 +79,18 @@ for (let i = 0; i < topLanguages.length; i++) {
|
||||
}
|
||||
|
||||
export interface EditorProps {
|
||||
value: string,
|
||||
searchMarkers: any,
|
||||
mode: string,
|
||||
style: any,
|
||||
codeMirrorTheme: any,
|
||||
readOnly: boolean,
|
||||
autoMatchBraces: boolean,
|
||||
keyMap: string,
|
||||
plugins: PluginStates,
|
||||
onChange: any,
|
||||
onScroll: any,
|
||||
onEditorContextMenu: any,
|
||||
onEditorPaste: any,
|
||||
value: string;
|
||||
searchMarkers: any;
|
||||
mode: string;
|
||||
style: any;
|
||||
codeMirrorTheme: any;
|
||||
readOnly: boolean;
|
||||
autoMatchBraces: boolean;
|
||||
keyMap: string;
|
||||
plugins: PluginStates;
|
||||
onChange: any;
|
||||
onScroll: any;
|
||||
onEditorPaste: any;
|
||||
}
|
||||
|
||||
function Editor(props: EditorProps, ref: any) {
|
||||
@@ -122,13 +123,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
props.onScroll();
|
||||
}, [props.onScroll]);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const editor_mousedown = useCallback((_cm: any, event: any) => {
|
||||
if (event && event.button === 2) {
|
||||
props.onEditorContextMenu();
|
||||
}
|
||||
}, [props.onEditorContextMenu]);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const editor_paste = useCallback((_cm: any, _event: any) => {
|
||||
props.onEditorPaste();
|
||||
@@ -163,7 +157,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
mode: props.mode,
|
||||
readOnly: props.readOnly,
|
||||
autoCloseBrackets: props.autoMatchBraces,
|
||||
inputStyle: 'textarea', // contenteditable loses cursor position on focus change, use textarea instead
|
||||
inputStyle: Setting.value('editor.spellcheckBeta') ? 'contenteditable' : 'textarea',
|
||||
lineWrapping: true,
|
||||
lineNumbers: false,
|
||||
indentWithTabs: true,
|
||||
@@ -177,7 +171,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
setEditor(cm);
|
||||
cm.on('change', editor_change);
|
||||
cm.on('scroll', editor_scroll);
|
||||
cm.on('mousedown', editor_mousedown);
|
||||
cm.on('paste', editor_paste);
|
||||
cm.on('drop', editor_drop);
|
||||
cm.on('dragover', editor_drag);
|
||||
@@ -191,7 +184,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
// Clean up codemirror
|
||||
cm.off('change', editor_change);
|
||||
cm.off('scroll', editor_scroll);
|
||||
cm.off('mousedown', editor_mousedown);
|
||||
cm.off('paste', editor_paste);
|
||||
cm.off('drop', editor_drop);
|
||||
cm.off('dragover', editor_drag);
|
||||
@@ -250,7 +242,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
}
|
||||
}, [pluginOptions, editor]);
|
||||
|
||||
return <div style={props.style} ref={editorParent} />;
|
||||
return <div className='codeMirrorEditor' style={props.style} ref={editorParent} />;
|
||||
}
|
||||
|
||||
export default forwardRef(Editor);
|
||||
|
||||
@@ -100,5 +100,33 @@ export default function useCursorUtils(CodeMirror: any) {
|
||||
});
|
||||
});
|
||||
|
||||
// params are the oncontextmenu params
|
||||
CodeMirror.defineExtension('alignSelection', function(params: any) {
|
||||
// The below is a HACK that uses the selectionText from electron and the coordinates of
|
||||
// the click to determine what the codemirror selection should be
|
||||
const alignStrings = (s1: string, s2: string) => {
|
||||
for (let i = 0; i < s1.length; i++) {
|
||||
if (s1.substr(i, s2.length) === s2) { return i; }
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const selectionText = params.selectionText;
|
||||
const coords = this.coordsChar({ left: params.x, top: params.y });
|
||||
const { anchor, head } = this.findWordAt(coords);
|
||||
const selectedWord = this.getRange(anchor, head);
|
||||
|
||||
if (selectionText.length > selectedWord.length) {
|
||||
const offset = alignStrings(selectionText, selectedWord);
|
||||
anchor.ch -= offset;
|
||||
head.ch = anchor.ch + selectionText.length;
|
||||
} else if (selectionText.length < selectedWord.length) {
|
||||
const offset = alignStrings(selectedWord, selectionText);
|
||||
anchor.ch += offset;
|
||||
head.ch = anchor.ch + selectionText.length;
|
||||
}
|
||||
|
||||
this.setSelection(anchor, head);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const { buildStyle } = require('@joplin/lib/theme');
|
||||
export default function styles(props: NoteBodyEditorProps) {
|
||||
return buildStyle(['TinyMCE', props.style.width, props.style.height], props.themeId, (theme: any) => {
|
||||
const extraToolbarContainer = {
|
||||
boxSizing: 'content-box',
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -3,15 +3,16 @@ import { FormNote, defaultFormNote, ResourceInfos } from './types';
|
||||
import { clearResourceCache, attachedResources } from './resourceHandling';
|
||||
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
|
||||
import { handleResourceDownloadMode } from './resourceHandling';
|
||||
import HtmlToHtml from '@joplin/renderer/HtmlToHtml';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
|
||||
|
||||
const { MarkupToHtml } = require('@joplin/renderer');
|
||||
const HtmlToHtml = require('@joplin/renderer/HtmlToHtml');
|
||||
const usePrevious = require('../../hooks/usePrevious').default;
|
||||
const Note = require('@joplin/lib/models/Note');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js');
|
||||
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker.js');
|
||||
const ResourceEditWatcher = require('@joplin/lib/services/ResourceEditWatcher/index').default;
|
||||
|
||||
export interface OnLoadEvent {
|
||||
formNote: FormNote;
|
||||
|
||||
@@ -15,7 +15,6 @@ const { render } = require('react-dom');
|
||||
const { connect, Provider } = require('react-redux');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
shim.setReact(React);
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
const { Navigator } = require('./Navigator.min.js');
|
||||
@@ -32,6 +31,10 @@ interface Props {
|
||||
}
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div, span, a {
|
||||
/*color: ${(props: any) => props.theme.color};*/
|
||||
/*font-size: ${(props: any) => props.theme.fontSize}px;*/
|
||||
|
||||
@@ -41,14 +41,14 @@ export default function styles(props: Props) {
|
||||
leftIcon: {
|
||||
fontSize: iconSize,
|
||||
position: 'relative',
|
||||
top: 2,
|
||||
top: 1,
|
||||
color: theme.color3,
|
||||
},
|
||||
rightIcon: {
|
||||
fontSize: iconSize - 1,
|
||||
borderLeft: 'none',
|
||||
position: 'relative',
|
||||
top: 2,
|
||||
top: 1,
|
||||
color: theme.color3,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ class ToolbarBaseComponent extends React.Component<Props, any> {
|
||||
const o = this.props.items[i];
|
||||
let key = o.iconName ? o.iconName : '';
|
||||
key += o.title ? o.title : '';
|
||||
key += o.name ? o.name : '';
|
||||
const itemType = !('type' in o) ? 'button' : o.type;
|
||||
|
||||
if (!key) key = `${o.type}_${i}`;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
const smalltalk = require('smalltalk');
|
||||
|
||||
class Dialogs {
|
||||
async alert(message, title = '') {
|
||||
async alert(message: string, title = '') {
|
||||
await smalltalk.alert(title, message);
|
||||
}
|
||||
|
||||
async confirm(message, title = '') {
|
||||
async confirm(message: string, title = '', options: any = {}) {
|
||||
try {
|
||||
await smalltalk.confirm(title, message);
|
||||
await smalltalk.confirm(title, message, options);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async prompt(message, title = '', defaultValue = '', options = null) {
|
||||
async prompt(message: string, title = '', defaultValue = '', options: any = null) {
|
||||
if (options === null) options = {};
|
||||
|
||||
try {
|
||||
@@ -28,4 +28,4 @@ class Dialogs {
|
||||
|
||||
const dialogs = new Dialogs();
|
||||
|
||||
module.exports = dialogs;
|
||||
export default dialogs;
|
||||
37
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.tsx
Normal file
37
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import * as React from 'react';
|
||||
const ReactToggleButton = require('react-toggle-button');
|
||||
const Color = require('color');
|
||||
|
||||
interface Props {
|
||||
value: boolean;
|
||||
onToggle: Function;
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
export default function(props: Props) {
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
return (
|
||||
<ReactToggleButton
|
||||
value={props.value}
|
||||
onToggle={props.onToggle}
|
||||
colors={{
|
||||
activeThumb: {
|
||||
base: Color(theme.color5).rgb().string(),
|
||||
},
|
||||
active: {
|
||||
base: Color(theme.backgroundColor5).alpha(0.7).rgb().string(),
|
||||
},
|
||||
}}
|
||||
trackStyle={{
|
||||
opacity: props.value ? 1 : 0.3,
|
||||
}}
|
||||
thumbStyle={{
|
||||
opacity: props.value ? 1 : 0.5,
|
||||
}}
|
||||
inactiveLabel=""
|
||||
activeLabel=""
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -27,6 +27,7 @@ const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
const EncryptionService = require('@joplin/lib/services/EncryptionService');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local.js');
|
||||
const React = require('react');
|
||||
|
||||
if (bridge().env() === 'dev') {
|
||||
const newConsole = function(oldConsole) {
|
||||
@@ -86,7 +87,7 @@ try {
|
||||
keytar = null;
|
||||
}
|
||||
|
||||
shimInit(null, keytar);
|
||||
shimInit(null, keytar, React);
|
||||
|
||||
// Disable drag and drop of links inside application (which would
|
||||
// open it as if the whole app was a browser)
|
||||
|
||||
2154
packages/app-desktop/package-lock.json
generated
2154
packages/app-desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "1.4.10",
|
||||
"version": "1.4.11",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -11,7 +11,8 @@
|
||||
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
||||
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
||||
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"test-ci": "test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -89,7 +90,6 @@
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "^1.0.9",
|
||||
"@testing-library/react-hooks": "^3.4.2",
|
||||
"@types/jasmine": "^3.5.11",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
"@types/react": "16.9.55",
|
||||
@@ -98,9 +98,9 @@
|
||||
"app-builder-bin": "^1.9.11",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"electron": "^8.2.5",
|
||||
"electron-builder": "22.3.2",
|
||||
"electron-rebuild": "^1.10.1",
|
||||
"electron": "^10.1.6",
|
||||
"electron-builder": "22.9.1",
|
||||
"electron-rebuild": "^2.3.2",
|
||||
"glob": "^7.1.6",
|
||||
"gulp": "^4.0.2",
|
||||
"jest": "^26.6.3",
|
||||
@@ -144,6 +144,7 @@
|
||||
"react-dom": "16.9.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-select": "^2.4.3",
|
||||
"react-toggle-button": "^2.2.0",
|
||||
"react-tooltip": "^3.10.0",
|
||||
"redux": "^3.7.2",
|
||||
"reselect": "^4.0.0",
|
||||
|
||||
@@ -100,9 +100,11 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
slashes: true,
|
||||
})}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}`);
|
||||
|
||||
pluginWindow.webContents.once('dom-ready', () => {
|
||||
pluginWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
});
|
||||
if (plugin.devMode) {
|
||||
pluginWindow.webContents.once('dom-ready', () => {
|
||||
pluginWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('pluginMessage', async (_event: any, message: PluginMessage) => {
|
||||
if (message.target !== PluginMessageTarget.MainWindow) return;
|
||||
|
||||
@@ -87,7 +87,6 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
codeTheme: theme.codeThemeCss,
|
||||
postMessageSyntax: 'window.joplinPostMessage_',
|
||||
enableLongPress: shim.mobilePlatform() === 'android', // On iOS, there's already a built-on open/share menu
|
||||
longPressDelay: 500, // TODO use system value
|
||||
};
|
||||
|
||||
// Whenever a resource state changes, for example when it goes from "not downloaded" to "downloaded", the "noteResources"
|
||||
|
||||
@@ -399,6 +399,8 @@ async function initialize(dispatch) {
|
||||
mainLogger.setLevel(Logger.LEVEL_DEBUG);
|
||||
}
|
||||
|
||||
Logger.initializeGlobalLogger(mainLogger);
|
||||
|
||||
reg.setLogger(mainLogger);
|
||||
reg.setShowErrorMessageBoxHandler((message) => { alert(message); });
|
||||
|
||||
|
||||
@@ -4,6 +4,29 @@ const Generator = require('yeoman-generator');
|
||||
const chalk = require('chalk');
|
||||
const yosay = require('yosay');
|
||||
|
||||
function mergePackageKey(parentKey, source, dest) {
|
||||
const output = Object.assign({}, dest);
|
||||
|
||||
for (const k in source) {
|
||||
if (!(k in output)) {
|
||||
// If the key doesn't exist in the destination, add it
|
||||
output[k] = source[k];
|
||||
} else if (parentKey === 'devDependencies') {
|
||||
// If we are dealing with the dependencies, overwrite with the
|
||||
// version from source.
|
||||
output[k] = source[k];
|
||||
} else if (typeof source[k] === 'object' && !Array.isArray(k) && source[k] !== null) {
|
||||
// If it's an object, recursively process it
|
||||
output[k] = mergePackageKey(k, source[k], output[k]);
|
||||
} else {
|
||||
// Otherwise, the default is to preserve the destination key
|
||||
output[k] = dest[k];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = class extends Generator {
|
||||
|
||||
constructor(args, opts) {
|
||||
@@ -18,8 +41,6 @@ module.exports = class extends Generator {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async prompting() {
|
||||
this.log(yosay(`Welcome to the fine ${chalk.red('generator-joplin')} generator!`));
|
||||
|
||||
@@ -110,12 +131,28 @@ module.exports = class extends Generator {
|
||||
if (this.options.update && noUpdateFiles.includes(file)) continue;
|
||||
|
||||
const destFile = file.replace(/_TEMPLATE/, '');
|
||||
const destFilePath = this.destinationPath(destFile);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath(file),
|
||||
this.destinationPath(destFile),
|
||||
this.props
|
||||
);
|
||||
if (this.options.update && destFile === 'package.json' && this.fs.exists(destFilePath)) {
|
||||
const destContent = this.fs.readJSON(destFilePath);
|
||||
|
||||
this.fs.copy(
|
||||
this.templatePath(file),
|
||||
destFilePath, {
|
||||
process: (sourceBuffer) => {
|
||||
const sourceContent = JSON.parse(sourceBuffer.toString());
|
||||
const newContent = mergePackageKey(null, sourceContent, destContent);
|
||||
return JSON.stringify(newContent, null, 2);
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.fs.copyTpl(
|
||||
this.templatePath(file),
|
||||
destFilePath,
|
||||
this.props
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.fs.copy(
|
||||
|
||||
@@ -16,6 +16,13 @@ function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`)
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) {
|
||||
// Usually means there's an error, which is going to be printed by
|
||||
// webpack
|
||||
console.info('Plugin archive was not created because the "dist" directory is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
@@ -38,6 +45,8 @@ const manifestPath = `${srcDir}/manifest.json`;
|
||||
const manifest = readManifest(manifestPath);
|
||||
const archiveFilePath = path.resolve(__dirname, `${manifest.id}.jpl`);
|
||||
|
||||
fs.removeSync(distDir);
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: './src/index.ts',
|
||||
|
||||
@@ -46,13 +46,13 @@ const MigrationService = require('./services/MigrationService');
|
||||
const { toSystemSlashes } = require('./path-utils');
|
||||
const { setAutoFreeze } = require('immer');
|
||||
|
||||
const appLogger = Logger.create('App');
|
||||
|
||||
// const ntpClient = require('./vendor/ntp-client');
|
||||
// ntpClient.dgram = require('dgram');
|
||||
|
||||
export default class BaseApplication {
|
||||
|
||||
private logger_: Logger;
|
||||
private dbLogger_: Logger;
|
||||
private eventEmitter_: any;
|
||||
private scheduleAutoAddResourcesIID_: any = null;
|
||||
private database_: any = null;
|
||||
@@ -68,10 +68,7 @@ export default class BaseApplication {
|
||||
protected store_: any = null;
|
||||
|
||||
constructor() {
|
||||
this.logger_ = new Logger();
|
||||
this.dbLogger_ = new Logger();
|
||||
this.eventEmitter_ = new EventEmitter();
|
||||
|
||||
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this);
|
||||
}
|
||||
|
||||
@@ -101,15 +98,13 @@ export default class BaseApplication {
|
||||
EncryptionService.instance_ = null;
|
||||
DecryptionWorker.instance_ = null;
|
||||
|
||||
this.logger_.info('Base application terminated...');
|
||||
this.logger_ = null;
|
||||
this.dbLogger_ = null;
|
||||
appLogger.info('Base application terminated...');
|
||||
this.eventEmitter_ = null;
|
||||
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = null;
|
||||
}
|
||||
|
||||
logger() {
|
||||
return this.logger_;
|
||||
return appLogger;
|
||||
}
|
||||
|
||||
public store() {
|
||||
@@ -289,7 +284,7 @@ export default class BaseApplication {
|
||||
parentType = BaseModel.TYPE_SMART_FILTER;
|
||||
}
|
||||
|
||||
this.logger().debug('Refreshing notes:', parentType, parentId);
|
||||
appLogger.debug('Refreshing notes:', parentType, parentId);
|
||||
|
||||
const options = {
|
||||
order: stateUtils.notesOrder(state.settings),
|
||||
@@ -460,7 +455,7 @@ export default class BaseApplication {
|
||||
}
|
||||
|
||||
async generalMiddleware(store: any, next: any, action: any) {
|
||||
// this.logger().debug('Reducer action', this.reducerActionToString(action));
|
||||
// appLogger.debug('Reducer action', this.reducerActionToString(action));
|
||||
|
||||
const result = next(action);
|
||||
const newState = store.getState();
|
||||
@@ -547,7 +542,11 @@ export default class BaseApplication {
|
||||
}
|
||||
|
||||
if (action.type === 'NOTE_UPDATE_ONE') {
|
||||
if (!action.changedFields.length || action.changedFields.includes('parent_id') || action.changedFields.includes('encryption_applied')) {
|
||||
if (!action.changedFields.length ||
|
||||
action.changedFields.includes('parent_id') ||
|
||||
action.changedFields.includes('encryption_applied') ||
|
||||
action.changedFields.includes('is_conflict')
|
||||
) {
|
||||
refreshFolders = true;
|
||||
}
|
||||
}
|
||||
@@ -701,35 +700,30 @@ export default class BaseApplication {
|
||||
const extraFlags = await this.readFlagsFromFile(`${profileDir}/flags.txt`);
|
||||
initArgs = Object.assign(initArgs, extraFlags);
|
||||
|
||||
this.logger_.addTarget(TargetType.File, { path: `${profileDir}/log.txt` });
|
||||
if (Setting.value('env') === 'dev' && !shim.isTestingEnv()) {
|
||||
// this.logger_.addTarget(TargetType.Console, { level: Logger.LEVEL_DEBUG });
|
||||
}
|
||||
this.logger_.setLevel(initArgs.logLevel);
|
||||
|
||||
reg.setLogger(this.logger_);
|
||||
|
||||
|
||||
const globalLogger = new Logger();
|
||||
globalLogger.addTarget(TargetType.File, { path: `${profileDir}/log.txt` });
|
||||
if (Setting.value('appType') === 'desktop') {
|
||||
globalLogger.addTarget(TargetType.Console);
|
||||
}
|
||||
globalLogger.setLevel(initArgs.logLevel);
|
||||
Logger.initializeGlobalLogger(globalLogger);
|
||||
|
||||
|
||||
|
||||
reg.setLogger(Logger.create(''));
|
||||
reg.dispatch = () => {};
|
||||
|
||||
BaseService.logger_ = this.logger_;
|
||||
// require('lib/ntpDate').setLogger(reg.logger());
|
||||
BaseService.logger_ = globalLogger;
|
||||
|
||||
this.dbLogger_.addTarget(TargetType.File, { path: `${profileDir}/log-database.txt` });
|
||||
this.dbLogger_.setLevel(initArgs.logLevel);
|
||||
|
||||
if (Setting.value('appType') === 'desktop') {
|
||||
this.logger_.addTarget(TargetType.Console, { level: Logger.LEVEL_WARN });
|
||||
this.dbLogger_.addTarget(TargetType.Console, { level: Logger.LEVEL_WARN });
|
||||
}
|
||||
|
||||
if (Setting.value('env') === 'dev') {
|
||||
this.dbLogger_.setLevel(Logger.LEVEL_INFO);
|
||||
}
|
||||
|
||||
this.logger_.info(`Profile directory: ${profileDir}`);
|
||||
appLogger.info(`Profile directory: ${profileDir}`);
|
||||
|
||||
this.database_ = new JoplinDatabase(new DatabaseDriverNode());
|
||||
this.database_.setLogExcludedQueryTypes(['SELECT']);
|
||||
this.database_.setLogger(this.dbLogger_);
|
||||
this.database_.setLogger(globalLogger);
|
||||
|
||||
// if (Setting.value('env') === 'dev') {
|
||||
// if (shim.isElectron()) {
|
||||
@@ -756,7 +750,7 @@ export default class BaseApplication {
|
||||
|
||||
await loadKeychainServiceAndSettings(KeychainServiceDriver);
|
||||
|
||||
this.logger_.info(`Client ID: ${Setting.value('clientId')}`);
|
||||
appLogger.info(`Client ID: ${Setting.value('clientId')}`);
|
||||
|
||||
if (Setting.value('firstStart')) {
|
||||
const locale = shim.detectAndSetLocale(Setting);
|
||||
@@ -817,9 +811,9 @@ export default class BaseApplication {
|
||||
|
||||
KvStore.instance().setDb(reg.db());
|
||||
|
||||
EncryptionService.instance().setLogger(this.logger_);
|
||||
EncryptionService.instance().setLogger(globalLogger);
|
||||
BaseItem.encryptionService_ = EncryptionService.instance();
|
||||
DecryptionWorker.instance().setLogger(this.logger_);
|
||||
DecryptionWorker.instance().setLogger(globalLogger);
|
||||
DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
|
||||
DecryptionWorker.instance().setKvStore(KvStore.instance());
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
@@ -828,7 +822,7 @@ export default class BaseApplication {
|
||||
ResourceFetcher.instance().setFileApi(() => {
|
||||
return reg.syncTarget().fileApi();
|
||||
});
|
||||
ResourceFetcher.instance().setLogger(this.logger_);
|
||||
ResourceFetcher.instance().setLogger(globalLogger);
|
||||
ResourceFetcher.instance().on('downloadComplete', this.resourceFetcher_downloadComplete);
|
||||
ResourceFetcher.instance().start();
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ interface Target {
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface LoggerWrapper {
|
||||
debug: Function;
|
||||
info: Function;
|
||||
warn: Function;
|
||||
error: Function;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
|
||||
// For backward compatibility
|
||||
@@ -36,6 +43,7 @@ class Logger {
|
||||
public static LEVEL_DEBUG = LogLevel.Debug;
|
||||
|
||||
public static fsDriver_: any = null;
|
||||
private static globalLogger_: Logger = null;
|
||||
|
||||
private targets_: Target[] = [];
|
||||
private level_: LogLevel = LogLevel.Info;
|
||||
@@ -46,6 +54,24 @@ class Logger {
|
||||
return Logger.fsDriver_;
|
||||
}
|
||||
|
||||
public static initializeGlobalLogger(logger: Logger) {
|
||||
this.globalLogger_ = logger;
|
||||
}
|
||||
|
||||
private static get globalLogger(): Logger {
|
||||
if (!this.globalLogger_) throw new Error('Global logger has not been initialized!!');
|
||||
return this.globalLogger_;
|
||||
}
|
||||
|
||||
static create(prefix: string): LoggerWrapper {
|
||||
return {
|
||||
debug: (...object: any[]) => this.globalLogger.log(LogLevel.Debug, prefix, ...object),
|
||||
info: (...object: any[]) => this.globalLogger.log(LogLevel.Info, prefix, ...object),
|
||||
warn: (...object: any[]) => this.globalLogger.log(LogLevel.Warn, prefix, ...object),
|
||||
error: (...object: any[]) => this.globalLogger.log(LogLevel.Error, prefix, ...object),
|
||||
};
|
||||
}
|
||||
|
||||
setLevel(level: LogLevel) {
|
||||
this.level_ = level;
|
||||
}
|
||||
@@ -132,14 +158,12 @@ class Logger {
|
||||
return this.level();
|
||||
}
|
||||
|
||||
log(level: LogLevel, ...object: any[]) {
|
||||
public log(level: LogLevel, prefix: string, ...object: any[]) {
|
||||
if (!this.targets_.length) return;
|
||||
|
||||
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
const line = `${timestamp}: `;
|
||||
|
||||
for (let i = 0; i < this.targets_.length; i++) {
|
||||
const target = this.targets_[i];
|
||||
const targetPrefix = prefix ? prefix : target.prefix;
|
||||
|
||||
if (this.targetLevel(target) < level) continue;
|
||||
|
||||
@@ -149,26 +173,30 @@ class Logger {
|
||||
if (level == LogLevel.Warn) fn = 'warn';
|
||||
if (level == LogLevel.Info) fn = 'info';
|
||||
const consoleObj = target.console ? target.console : console;
|
||||
let items = [moment().format('HH:mm:ss')];
|
||||
if (target.prefix) {
|
||||
items.push(target.prefix);
|
||||
}
|
||||
items = items.concat(...object);
|
||||
const prefixItems = [moment().format('HH:mm:ss')];
|
||||
if (targetPrefix) prefixItems.push(targetPrefix);
|
||||
const items = [`${prefixItems.join(': ')}:`].concat(...object);
|
||||
consoleObj[fn](...items);
|
||||
} else if (target.type == 'file') {
|
||||
const serializedObject = this.objectsToString(...object);
|
||||
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
const line = [timestamp];
|
||||
if (targetPrefix) line.push(targetPrefix);
|
||||
line.push(this.objectsToString(...object));
|
||||
try {
|
||||
Logger.fsDriver().appendFileSync(target.path, `${line + serializedObject}\n`);
|
||||
// TODO: Should log async
|
||||
Logger.fsDriver().appendFileSync(target.path, `${line.join(': ')}\n`);
|
||||
} catch (error) {
|
||||
console.error('Cannot write to log file:', error);
|
||||
}
|
||||
} else if (target.type == 'database') {
|
||||
const msg = this.objectsToString(...object);
|
||||
const msg = [];
|
||||
if (targetPrefix) msg.push(targetPrefix);
|
||||
msg.push(this.objectsToString(...object));
|
||||
|
||||
const queries = [
|
||||
{
|
||||
sql: 'INSERT INTO logs (`source`, `level`, `message`, `timestamp`) VALUES (?, ?, ?, ?)',
|
||||
params: [target.source, level, msg, time.unixMs()],
|
||||
params: [target.source, level, msg.join(': '), time.unixMs()],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -188,16 +216,16 @@ class Logger {
|
||||
}
|
||||
|
||||
error(...object: any[]) {
|
||||
return this.log(LogLevel.Error, ...object);
|
||||
return this.log(LogLevel.Error, null, ...object);
|
||||
}
|
||||
warn(...object: any[]) {
|
||||
return this.log(LogLevel.Warn, ...object);
|
||||
return this.log(LogLevel.Warn, null, ...object);
|
||||
}
|
||||
info(...object: any[]) {
|
||||
return this.log(LogLevel.Info, ...object);
|
||||
return this.log(LogLevel.Info, null, ...object);
|
||||
}
|
||||
debug(...object: any[]) {
|
||||
return this.log(LogLevel.Debug, ...object);
|
||||
return this.log(LogLevel.Debug, null, ...object);
|
||||
}
|
||||
|
||||
static levelStringToId(s: string) {
|
||||
|
||||
@@ -92,6 +92,15 @@ shared.updateSettingValue = function(comp, key, value) {
|
||||
});
|
||||
};
|
||||
|
||||
shared.scheduleSaveSettings = function(comp) {
|
||||
if (shared.scheduleSaveSettingsIID) clearTimeout(shared.scheduleSaveSettingsIID);
|
||||
|
||||
shared.scheduleSaveSettingsIID = setTimeout(() => {
|
||||
shared.scheduleSaveSettingsIID = null;
|
||||
shared.saveSettings(comp);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
shared.saveSettings = function(comp) {
|
||||
for (const key in comp.state.settings) {
|
||||
if (!comp.state.settings.hasOwnProperty(key)) continue;
|
||||
|
||||
@@ -6,6 +6,7 @@ const shim = require('./shim').default;
|
||||
class Database {
|
||||
constructor(driver) {
|
||||
this.debugMode_ = false;
|
||||
this.sqlQueryLogEnabled_ = false;
|
||||
this.driver_ = driver;
|
||||
this.logger_ = new Logger();
|
||||
this.logExcludedQueryTypes_ = [];
|
||||
@@ -238,6 +239,8 @@ class Database {
|
||||
}
|
||||
|
||||
logQuery(sql, params = null) {
|
||||
if (!this.sqlQueryLogEnabled_) return;
|
||||
|
||||
if (this.logExcludedQueryTypes_.length) {
|
||||
const temp = sql.toLowerCase();
|
||||
for (let i = 0; i < this.logExcludedQueryTypes_.length; i++) {
|
||||
|
||||
@@ -276,7 +276,7 @@ class Note extends BaseItem {
|
||||
includeTimestamps: true,
|
||||
}, options);
|
||||
|
||||
const output = ['id', 'title', 'is_todo', 'todo_completed', 'todo_due', 'parent_id', 'encryption_applied', 'order', 'markup_language'];
|
||||
const output = ['id', 'title', 'is_todo', 'todo_completed', 'todo_due', 'parent_id', 'encryption_applied', 'order', 'markup_language', 'is_conflict'];
|
||||
|
||||
if (options.includeTimestamps) {
|
||||
output.push('updated_time');
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface SettingItem {
|
||||
isEnum?: boolean;
|
||||
section?: string;
|
||||
label?(): string;
|
||||
description?(appType: string): string;
|
||||
description?: Function;
|
||||
options?(): any;
|
||||
appTypes?: string[];
|
||||
show?(settings: any): boolean;
|
||||
@@ -44,7 +44,9 @@ export interface SettingItem {
|
||||
maximum?: number;
|
||||
step?: number;
|
||||
onClick?(): void;
|
||||
unitLabel?(value: any): string;
|
||||
unitLabel?: Function;
|
||||
needRestart?: boolean;
|
||||
autoSave?: boolean;
|
||||
}
|
||||
|
||||
interface SettingItems {
|
||||
@@ -554,6 +556,17 @@ class Setting extends BaseModel {
|
||||
},
|
||||
},
|
||||
|
||||
'plugins.states': {
|
||||
value: '',
|
||||
type: SettingItemType.Object,
|
||||
section: 'plugins',
|
||||
public: true,
|
||||
appTypes: ['desktop'],
|
||||
label: () => _('Plugins'),
|
||||
needRestart: true,
|
||||
autoSave: true,
|
||||
},
|
||||
|
||||
'plugins.devPluginPaths': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
@@ -702,7 +715,7 @@ class Setting extends BaseModel {
|
||||
description: () => 'CSS file support is provided for your convenience, but they are advanced settings, and styles you define may break from one version to the next. If you want to use them, please know that it might require regular development work from you to keep them working. The Joplin team cannot make a commitment to keep the application HTML structure stable.',
|
||||
},
|
||||
|
||||
autoUpdateEnabled: { value: false, type: SettingItemType.Bool, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Automatically update the application') },
|
||||
autoUpdateEnabled: { value: false, type: SettingItemType.Bool, section: 'application', public: platform !== 'linux', appTypes: ['desktop'], label: () => _('Automatically update the application') },
|
||||
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
|
||||
'clipperServer.autoStart': { value: false, type: SettingItemType.Bool, public: false },
|
||||
'sync.interval': {
|
||||
@@ -762,6 +775,16 @@ class Setting extends BaseModel {
|
||||
},
|
||||
},
|
||||
|
||||
'editor.spellcheckBeta': {
|
||||
value: false,
|
||||
type: SettingItemType.Bool,
|
||||
public: true,
|
||||
appTypes: ['desktop'],
|
||||
advanced: true,
|
||||
label: () => 'Enable spell checking in Markdown editor? (WARNING BETA feature)',
|
||||
description: () => 'Spell checker in the Markdown editor was previously unstable (cursor location was not stable, sometimes edits would not be saved or reflected in the viewer, etc.) however it appears to be more reliable now. If you notice any issue, please report it on GitHub or the Joplin Forum (Help -> Joplin Forum)',
|
||||
},
|
||||
|
||||
'net.customCertificates': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
@@ -816,7 +839,7 @@ class Setting extends BaseModel {
|
||||
minimum: 1,
|
||||
maximum: 365 * 2,
|
||||
step: 1,
|
||||
unitLabel: (value = null) => {
|
||||
unitLabel: (value: number = null) => {
|
||||
return value === null ? _('days') : _('%d days', value);
|
||||
},
|
||||
label: () => _('Keep note history for'),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Pagination, PaginationOrder } from './types';
|
||||
|
||||
export interface ModelFeedPage {
|
||||
items: any[];
|
||||
has_more?: boolean;
|
||||
has_more: boolean;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
@@ -52,9 +52,8 @@ export default async function(db: any, tableName: string, pagination: Pagination
|
||||
|
||||
const rows = await db.selectAll(sql, sqlParams);
|
||||
|
||||
const output: ModelFeedPage = { items: rows };
|
||||
|
||||
if (rows.length >= pagination.limit) output.has_more = true;
|
||||
|
||||
return output;
|
||||
return {
|
||||
items: rows,
|
||||
has_more: rows.length >= pagination.limit,
|
||||
};
|
||||
}
|
||||
|
||||
202
packages/lib/package-lock.json
generated
202
packages/lib/package-lock.json
generated
@@ -98,35 +98,6 @@
|
||||
"requires": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
@@ -270,6 +241,15 @@
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
|
||||
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ=="
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
|
||||
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"inherits": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -531,6 +511,14 @@
|
||||
"whatwg-url": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
@@ -753,6 +741,18 @@
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"optional": true
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
|
||||
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"inherits": "~2.0.0",
|
||||
"mkdirp": ">=0.5 0",
|
||||
"rimraf": "2"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
@@ -1512,6 +1512,11 @@
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"multiparty": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz",
|
||||
@@ -1527,11 +1532,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz",
|
||||
"integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz",
|
||||
@@ -1545,21 +1545,6 @@
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"no-case": {
|
||||
@@ -1570,6 +1555,11 @@
|
||||
"lower-case": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
|
||||
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
|
||||
},
|
||||
"node-emoji": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
|
||||
@@ -1587,6 +1577,39 @@
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node-gyp": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
|
||||
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"fstream": "^1.0.0",
|
||||
"glob": "^7.0.3",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"mkdirp": "^0.5.0",
|
||||
"nopt": "2 || 3",
|
||||
"npmlog": "0 || 1 || 2 || 3 || 4",
|
||||
"osenv": "0",
|
||||
"request": "^2.87.0",
|
||||
"rimraf": "2",
|
||||
"semver": "~5.3.0",
|
||||
"tar": "^2.0.0",
|
||||
"which": "1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tar": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
|
||||
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"block-stream": "*",
|
||||
"fstream": "^1.0.12",
|
||||
"inherits": "2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-notifier": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz",
|
||||
@@ -1645,15 +1668,26 @@
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4"
|
||||
},
|
||||
"dependencies": {
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
@@ -1947,6 +1981,27 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
@@ -2098,9 +2153,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
|
||||
},
|
||||
"server-destroy": {
|
||||
"version": "1.0.1",
|
||||
@@ -2168,11 +2223,12 @@
|
||||
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
|
||||
},
|
||||
"sqlite3": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz",
|
||||
"integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.0.tgz",
|
||||
"integrity": "sha512-rjvqHFUaSGnzxDy2AHCwhHy6Zp6MNJzCPGYju4kD8yi6bze4d1/zMTg6C7JI49b7/EM7jKMTvyfN/4ylBKdwfw==",
|
||||
"requires": {
|
||||
"nan": "^2.12.1",
|
||||
"node-addon-api": "2.0.0",
|
||||
"node-gyp": "3.x",
|
||||
"node-pre-gyp": "^0.11.0"
|
||||
}
|
||||
},
|
||||
@@ -2275,6 +2331,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"stringify-parameters": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stringify-parameters/-/stringify-parameters-0.0.4.tgz",
|
||||
@@ -2644,6 +2715,15 @@
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"reselect": "^4.0.0",
|
||||
"server-destroy": "^1.0.1",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"sqlite3": "^4.1.1",
|
||||
"sqlite3": "^5.0.0",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"tar": "^4.4.10",
|
||||
|
||||
@@ -787,8 +787,14 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
|
||||
for (let i = 0; i < newNotes.length; i++) {
|
||||
const n = newNotes[i];
|
||||
if (n.id == modNote.id) {
|
||||
// Note is still in the same folder
|
||||
if (isViewingAllNotes || noteIsInFolder(modNote, n.parent_id)) {
|
||||
if (n.is_conflict && !modNote.is_conflict) {
|
||||
// Note was a conflict but was moved outside of
|
||||
// the conflict folder
|
||||
newNotes.splice(i, 1);
|
||||
noteFolderHasChanged = true;
|
||||
movedNotePreviousIndex = i;
|
||||
} else if (isViewingAllNotes || noteIsInFolder(modNote, n.parent_id)) {
|
||||
// Note is still in the same folder
|
||||
// Merge the properties that have changed (in modNote) into
|
||||
// the object we already have.
|
||||
newNotes[i] = Object.assign({}, newNotes[i]);
|
||||
|
||||
@@ -6,6 +6,8 @@ import { ContentScriptType } from './api/types';
|
||||
import Logger from '../../Logger';
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const logger = Logger.create('Plugin');
|
||||
|
||||
interface ViewControllers {
|
||||
[key: string]: ViewController;
|
||||
}
|
||||
@@ -21,37 +23,33 @@ interface ContentScripts {
|
||||
|
||||
export default class Plugin {
|
||||
|
||||
private id_: string;
|
||||
private baseDir_: string;
|
||||
private manifest_: PluginManifest;
|
||||
private scriptText_: string;
|
||||
private enabled_: boolean = true;
|
||||
private logger_: Logger = null;
|
||||
private viewControllers_: ViewControllers = {};
|
||||
private contentScripts_: ContentScripts = {};
|
||||
private dispatch_: Function;
|
||||
private eventEmitter_: any;
|
||||
private devMode_: boolean = false;
|
||||
|
||||
constructor(id: string, baseDir: string, manifest: PluginManifest, scriptText: string, logger: Logger, dispatch: Function) {
|
||||
this.id_ = id;
|
||||
constructor(baseDir: string, manifest: PluginManifest, scriptText: string, dispatch: Function) {
|
||||
this.baseDir_ = shim.fsDriver().resolve(baseDir);
|
||||
this.manifest_ = manifest;
|
||||
this.scriptText_ = scriptText;
|
||||
this.logger_ = logger;
|
||||
this.dispatch_ = dispatch;
|
||||
this.eventEmitter_ = new EventEmitter();
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.id_;
|
||||
return this.manifest.id;
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this.enabled_;
|
||||
public get devMode(): boolean {
|
||||
return this.devMode_;
|
||||
}
|
||||
|
||||
public set enabled(v: boolean) {
|
||||
this.enabled_ = v;
|
||||
public set devMode(v: boolean) {
|
||||
this.devMode_ = v;
|
||||
}
|
||||
|
||||
public get manifest(): PluginManifest {
|
||||
@@ -91,7 +89,7 @@ export default class Plugin {
|
||||
|
||||
this.contentScripts_[type].push({ id, path: absolutePath });
|
||||
|
||||
this.logger_.debug(`Plugin: ${this.id}: Registered content script: ${type}: ${id}: ${absolutePath}`);
|
||||
logger.debug(`"${this.id}": Registered content script: ${type}: ${id}: ${absolutePath}`);
|
||||
|
||||
this.dispatch_({
|
||||
type: 'PLUGIN_CONTENT_SCRIPTS_ADD',
|
||||
@@ -119,7 +117,7 @@ export default class Plugin {
|
||||
}
|
||||
|
||||
public deprecationNotice(goneInVersion: string, message: string) {
|
||||
this.logger_.warn(`Plugin: ${this.id}: DEPRECATION NOTICE: ${message} This will stop working in version ${goneInVersion}.`);
|
||||
logger.warn(`"${this.id}": DEPRECATION NOTICE: ${message} This will stop working in version ${goneInVersion}.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,17 +4,45 @@ import Global from './api/Global';
|
||||
import BasePluginRunner from './BasePluginRunner';
|
||||
import BaseService from '../BaseService';
|
||||
import shim from '../../shim';
|
||||
import { rtrimSlashes } from '../../path-utils';
|
||||
import { filename, dirname, rtrimSlashes } from '../../path-utils';
|
||||
import Setting from '../../models/Setting';
|
||||
import Logger from '../../Logger';
|
||||
const compareVersions = require('compare-versions');
|
||||
const { filename, dirname } = require('../../path-utils');
|
||||
const uslug = require('uslug');
|
||||
const md5File = require('md5-file/promise');
|
||||
|
||||
interface Plugins {
|
||||
const logger = Logger.create('PluginService');
|
||||
|
||||
// Plugin data is split into two:
|
||||
//
|
||||
// - First there's the service `plugins` property, which contains the
|
||||
// plugin static data, as loaded from the plugin file or directory. For
|
||||
// example, the plugin ID, the manifest, the script files, etc.
|
||||
//
|
||||
// - Secondly, there's the `PluginSettings` data, which is dynamic and is
|
||||
// used for example to enable or disable a plugin. Its state is saved to
|
||||
// the user's settings.
|
||||
|
||||
export interface Plugins {
|
||||
[key: string]: Plugin;
|
||||
}
|
||||
|
||||
export interface PluginSetting {
|
||||
enabled: boolean;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
export function defaultPluginSetting(): PluginSetting {
|
||||
return {
|
||||
enabled: true,
|
||||
deleted: false,
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginSettings {
|
||||
[pluginId: string]: PluginSetting;
|
||||
}
|
||||
|
||||
function makePluginId(source: string): string {
|
||||
// https://www.npmjs.com/package/slug#options
|
||||
return uslug(source).substr(0,32);
|
||||
@@ -49,15 +77,42 @@ export default class PluginService extends BaseService {
|
||||
return this.plugins_;
|
||||
}
|
||||
|
||||
private setPluginAt(pluginId: string, plugin: Plugin) {
|
||||
this.plugins_ = {
|
||||
...this.plugins_,
|
||||
[pluginId]: plugin,
|
||||
};
|
||||
}
|
||||
|
||||
private deletePluginAt(pluginId: string) {
|
||||
if (!this.plugins_[pluginId]) return;
|
||||
|
||||
this.plugins_ = { ...this.plugins_ };
|
||||
delete this.plugins_[pluginId];
|
||||
}
|
||||
|
||||
public pluginById(id: string): Plugin {
|
||||
if (!this.plugins_[id]) throw new Error(`Plugin not found: ${id}`);
|
||||
|
||||
return this.plugins_[id];
|
||||
}
|
||||
|
||||
// public allPluginIds(): string[] {
|
||||
// return Object.keys(this.plugins_);
|
||||
// }
|
||||
public unserializePluginSettings(settings: any): PluginSettings {
|
||||
const output = { ...settings };
|
||||
|
||||
for (const pluginId in output) {
|
||||
output[pluginId] = {
|
||||
...defaultPluginSetting(),
|
||||
...output[pluginId],
|
||||
};
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public serializePluginSettings(settings: PluginSettings): any {
|
||||
return JSON.stringify(settings);
|
||||
}
|
||||
|
||||
private async parsePluginJsBundle(jsBundleString: string) {
|
||||
const scriptText = jsBundleString;
|
||||
@@ -146,7 +201,7 @@ export default class PluginService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPluginFromPath(path: string): Promise<Plugin> {
|
||||
public async loadPluginFromPath(path: string): Promise<Plugin> {
|
||||
path = rtrimSlashes(path);
|
||||
|
||||
const fsDriver = shim.fsDriver();
|
||||
@@ -161,7 +216,7 @@ export default class PluginService extends BaseService {
|
||||
distPath = `${path}/dist`;
|
||||
}
|
||||
|
||||
this.logger().info(`PluginService: Loading plugin from ${path}`);
|
||||
logger.info(`Loading plugin from ${path}`);
|
||||
|
||||
const scriptText = await fsDriver.readFile(`${distPath}/index.js`);
|
||||
const manifestText = await fsDriver.readFile(`${distPath}/manifest.json`);
|
||||
@@ -189,79 +244,145 @@ export default class PluginService extends BaseService {
|
||||
}
|
||||
|
||||
const manifest = manifestFromObject(manifestObj);
|
||||
const pluginId = manifest.id;
|
||||
|
||||
// After transforming the plugin path to an ID, multiple plugins might end up with the same ID. For
|
||||
// example "MyPlugin" and "myplugin" would have the same ID. Technically it's possible to have two
|
||||
// such folders but to keep things sane we disallow it.
|
||||
if (this.plugins_[pluginId]) throw new Error(`There is already a plugin with this ID: ${pluginId}`);
|
||||
|
||||
const plugin = new Plugin(pluginId, baseDir, manifest, scriptText, this.logger(), (action: any) => this.store_.dispatch(action));
|
||||
|
||||
if (compareVersions(this.appVersion_, manifest.app_min_version) < 0) {
|
||||
this.logger().info(`PluginService: Plugin "${pluginId}" was disabled because it requires a newer version of Joplin.`, manifest);
|
||||
plugin.enabled = false;
|
||||
} else {
|
||||
this.store_.dispatch({
|
||||
type: 'PLUGIN_ADD',
|
||||
plugin: {
|
||||
id: pluginId,
|
||||
views: {},
|
||||
contentScripts: {},
|
||||
},
|
||||
});
|
||||
}
|
||||
const plugin = new Plugin(baseDir, manifest, scriptText, (action: any) => this.store_.dispatch(action));
|
||||
|
||||
for (const msg of deprecationNotices) {
|
||||
plugin.deprecationNotice('1.5', msg);
|
||||
}
|
||||
|
||||
// Sanity check, although at that point the plugin ID should have
|
||||
// been set, either automatically, or because it was defined in the
|
||||
// manifest.
|
||||
if (!plugin.id) throw new Error('Could not load plugin: ID is not set');
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public async loadAndRunPlugins(pluginDirOrPaths: string | string[]) {
|
||||
private pluginEnabled(settings: PluginSettings, pluginId: string): boolean {
|
||||
if (!settings[pluginId]) return true;
|
||||
return settings[pluginId].enabled !== false;
|
||||
}
|
||||
|
||||
public async loadAndRunPlugins(pluginDirOrPaths: string | string[], settings: PluginSettings, devMode: boolean = false) {
|
||||
let pluginPaths = [];
|
||||
|
||||
if (Array.isArray(pluginDirOrPaths)) {
|
||||
pluginPaths = pluginDirOrPaths;
|
||||
} else {
|
||||
pluginPaths = (await shim.fsDriver().readDirStats(pluginDirOrPaths))
|
||||
.filter((stat: any) => (stat.isDirectory() || stat.path.toLowerCase().endsWith('.js')))
|
||||
.filter((stat: any) => {
|
||||
if (stat.isDirectory()) return true;
|
||||
if (stat.path.toLowerCase().endsWith('.js')) return true;
|
||||
if (stat.path.toLowerCase().endsWith('.jpl')) return true;
|
||||
return false;
|
||||
})
|
||||
.map((stat: any) => `${pluginDirOrPaths}/${stat.path}`);
|
||||
}
|
||||
|
||||
for (const pluginPath of pluginPaths) {
|
||||
if (pluginPath.indexOf('_') === 0) {
|
||||
this.logger().info(`PluginService: Plugin name starts with "_" and has not been loaded: ${pluginPath}`);
|
||||
logger.info(`Plugin name starts with "_" and has not been loaded: ${pluginPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const plugin = await this.loadPluginFromPath(pluginPath);
|
||||
|
||||
// After transforming the plugin path to an ID, multiple plugins might end up with the same ID. For
|
||||
// example "MyPlugin" and "myplugin" would have the same ID. Technically it's possible to have two
|
||||
// such folders but to keep things sane we disallow it.
|
||||
if (this.plugins_[plugin.id]) throw new Error(`There is already a plugin with this ID: ${plugin.id}`);
|
||||
|
||||
this.setPluginAt(plugin.id, plugin);
|
||||
|
||||
if (!this.pluginEnabled(settings, plugin.id)) {
|
||||
logger.info(`Not running disabled plugin: "${plugin.id}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
plugin.devMode = devMode;
|
||||
|
||||
await this.runPlugin(plugin);
|
||||
} catch (error) {
|
||||
this.logger().error(`PluginService: Could not load plugin: ${pluginPath}`, error);
|
||||
logger.error(`Could not load plugin: ${pluginPath}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async runPlugin(plugin: Plugin) {
|
||||
this.plugins_[plugin.id] = plugin;
|
||||
const pluginApi = new Global(this.logger(), this.platformImplementation_, plugin, this.store_);
|
||||
if (compareVersions(this.appVersion_, plugin.manifest.app_min_version) < 0) {
|
||||
throw new Error(`Plugin "${plugin.id}" was disabled because it requires Joplin version ${plugin.manifest.app_min_version} and current version is ${this.appVersion_}.`);
|
||||
} else {
|
||||
this.store_.dispatch({
|
||||
type: 'PLUGIN_ADD',
|
||||
plugin: {
|
||||
id: plugin.id,
|
||||
views: {},
|
||||
contentScripts: {},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const pluginApi = new Global(this.platformImplementation_, plugin, this.store_);
|
||||
return this.runner_.run(plugin, pluginApi);
|
||||
}
|
||||
|
||||
// public async handleDisabledPlugins() {
|
||||
// const enabledPlugins = this.allPluginIds();
|
||||
// const v = await this.kvStore_.value<string>('pluginService.lastEnabledPlugins');
|
||||
// const lastEnabledPlugins = v ? JSON.parse(v) : [];
|
||||
public async installPlugin(jplPath: string): Promise<Plugin> {
|
||||
logger.info(`Installing plugin: "${jplPath}"`);
|
||||
|
||||
// const disabledPlugins = [];
|
||||
// for (const id of lastEnabledPlugins) {
|
||||
// if (!enabledPlugins.includes(id)) disabledPlugins.push(id);
|
||||
// }
|
||||
// Before moving the plugin to the profile directory, we load it
|
||||
// from where it is now to check that it is valid and to retrieve
|
||||
// the plugin ID.
|
||||
const preloadedPlugin = await this.loadPluginFromPath(jplPath);
|
||||
|
||||
// await this.kvStore_.setValue('pluginService.lastEnabledPlugins', JSON.stringify(enabledPlugins));
|
||||
// }
|
||||
const destPath = `${Setting.value('pluginDir')}/${preloadedPlugin.id}.jpl`;
|
||||
await shim.fsDriver().copy(jplPath, destPath);
|
||||
|
||||
// Now load it from the profile directory
|
||||
const plugin = await this.loadPluginFromPath(destPath);
|
||||
if (!this.plugins_[plugin.id]) this.setPluginAt(plugin.id, plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private async pluginPath(pluginId: string) {
|
||||
const stats = await shim.fsDriver().readDirStats(Setting.value('pluginDir'), { recursive: false });
|
||||
|
||||
for (const stat of stats) {
|
||||
if (filename(stat.path) === pluginId) {
|
||||
return `${Setting.value('pluginDir')}/${stat.path}`;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async uninstallPlugin(pluginId: string) {
|
||||
logger.info(`Uninstalling plugin: "${pluginId}"`);
|
||||
|
||||
const path = await this.pluginPath(pluginId);
|
||||
if (!path) {
|
||||
// Plugin might have already been deleted
|
||||
logger.error(`Could not find plugin path to uninstall - nothing will be done: ${pluginId}`);
|
||||
} else {
|
||||
await shim.fsDriver().remove(path);
|
||||
}
|
||||
|
||||
this.deletePluginAt(pluginId);
|
||||
}
|
||||
|
||||
public async uninstallPlugins(settings: PluginSettings): Promise<PluginSettings> {
|
||||
let newSettings = settings;
|
||||
|
||||
for (const pluginId in settings) {
|
||||
if (settings[pluginId].deleted) {
|
||||
await this.uninstallPlugin(pluginId);
|
||||
newSettings = { ...settings };
|
||||
delete newSettings[pluginId];
|
||||
}
|
||||
}
|
||||
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Plugin from '../Plugin';
|
||||
import Joplin from './Joplin';
|
||||
import Logger from '../../../Logger';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
@@ -16,8 +15,8 @@ export default class Global {
|
||||
private requireWhiteList_: string[] = null;
|
||||
// private consoleWrapper_:any = null;
|
||||
|
||||
constructor(logger: Logger, implementation: any, plugin: Plugin, store: any) {
|
||||
this.joplin_ = new Joplin(logger, implementation.joplin, plugin, store);
|
||||
constructor(implementation: any, plugin: Plugin, store: any) {
|
||||
this.joplin_ = new Joplin(implementation.joplin, plugin, store);
|
||||
// this.consoleWrapper_ = this.createConsoleWrapper(plugin.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import JoplinCommands from './JoplinCommands';
|
||||
import JoplinViews from './JoplinViews';
|
||||
import JoplinInterop from './JoplinInterop';
|
||||
import JoplinSettings from './JoplinSettings';
|
||||
import Logger from '../../../Logger';
|
||||
|
||||
/**
|
||||
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
|
||||
@@ -35,9 +34,9 @@ export default class Joplin {
|
||||
private interop_: JoplinInterop = null;
|
||||
private settings_: JoplinSettings = null;
|
||||
|
||||
constructor(logger: Logger, implementation: any, plugin: Plugin, store: any) {
|
||||
constructor(implementation: any, plugin: Plugin, store: any) {
|
||||
this.data_ = new JoplinData();
|
||||
this.plugins_ = new JoplinPlugins(logger, plugin);
|
||||
this.plugins_ = new JoplinPlugins(plugin);
|
||||
this.workspace_ = new JoplinWorkspace(implementation.workspace, store);
|
||||
this.filters_ = new JoplinFilters();
|
||||
this.commands_ = new JoplinCommands();
|
||||
|
||||
@@ -2,16 +2,16 @@ import Plugin from '../Plugin';
|
||||
import Logger from '../../../Logger';
|
||||
import { ContentScriptType, Script } from './types';
|
||||
|
||||
const logger = Logger.create('joplin.plugins');
|
||||
|
||||
/**
|
||||
* This class provides access to plugin-related features.
|
||||
*/
|
||||
export default class JoplinPlugins {
|
||||
|
||||
private logger: Logger;
|
||||
private plugin: Plugin;
|
||||
|
||||
public constructor(logger: Logger, plugin: Plugin) {
|
||||
this.logger = logger;
|
||||
public constructor(plugin: Plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class JoplinPlugins {
|
||||
if (script.onStart) {
|
||||
const startTime = Date.now();
|
||||
|
||||
this.logger.info(`Starting plugin: ${this.plugin.id}`);
|
||||
logger.info(`Starting plugin: ${this.plugin.id}`);
|
||||
|
||||
// We don't use `await` when calling onStart because the plugin might be awaiting
|
||||
// in that call too (for example, when opening a dialog on startup) so we don't
|
||||
@@ -42,9 +42,9 @@ export default class JoplinPlugins {
|
||||
// be handled correctly by loggers, etc.
|
||||
const newError: Error = new Error(error.message);
|
||||
newError.stack = error.stack;
|
||||
this.logger.error(`Uncaught exception in plugin "${this.plugin.id}":`, newError);
|
||||
logger.error(`Uncaught exception in plugin "${this.plugin.id}":`, newError);
|
||||
}).then(() => {
|
||||
this.logger.info(`Finished running onStart handler: ${this.plugin.id} (Took ${Date.now() - startTime}ms)`);
|
||||
logger.info(`Finished running onStart handler: ${this.plugin.id} (Took ${Date.now() - startTime}ms)`);
|
||||
this.plugin.emit('started');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ const gunzipFile = function(source, destination) {
|
||||
});
|
||||
};
|
||||
|
||||
function shimInit(sharp = null, keytar = null) {
|
||||
function shimInit(sharp = null, keytar = null, React = null) {
|
||||
keytar = (shim.isWindows() || shim.isMac()) && !shim.isPortable() ? keytar : null;
|
||||
|
||||
shim.fsDriver = () => {
|
||||
@@ -67,6 +67,12 @@ function shimInit(sharp = null, keytar = null) {
|
||||
return shim.fsDriver_;
|
||||
};
|
||||
|
||||
if (React) {
|
||||
shim.react = () => {
|
||||
return React;
|
||||
};
|
||||
}
|
||||
|
||||
shim.randomBytes = async count => {
|
||||
const buffer = require('crypto').randomBytes(count);
|
||||
return Array.from(buffer);
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import { ResourceEntity } from './services/database/types';
|
||||
|
||||
let isTestingEnv_ = false;
|
||||
|
||||
// We need to ensure that there's only one instance of React being used by
|
||||
// all the packages. In particular, the lib might need React to define
|
||||
// generic hooks, but it shouldn't have React in its dependencies as that
|
||||
// would cause the following error:
|
||||
//
|
||||
// https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react
|
||||
//
|
||||
// So instead, the **applications** include React as a dependency, then
|
||||
// pass it to any other packages using the shim. Essentially, only one
|
||||
// package should require React, and in our case that should be one of the
|
||||
// applications (app-desktop, app-mobile, etc.) since we are sure they
|
||||
// won't be dependency to other packages (unlike the lib which can be
|
||||
// included anywhere).
|
||||
|
||||
let react_: any = null;
|
||||
|
||||
const shim = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const htmlUtils = require('./htmlUtils');
|
||||
const utils = require('./utils');
|
||||
// const noteStyle = require('./noteStyle').default;
|
||||
import htmlUtils from './htmlUtils';
|
||||
import linkReplacement from './MdToHtml/linkReplacement';
|
||||
import utils from './utils';
|
||||
|
||||
// TODO: fix
|
||||
// const Setting = require('@joplin/lib/models/Setting').default;
|
||||
@@ -13,9 +13,45 @@ const md5 = require('md5');
|
||||
// relatively small.
|
||||
const inMemoryCache = new InMemoryCache(10);
|
||||
|
||||
class HtmlToHtml {
|
||||
constructor(options) {
|
||||
if (!options) options = {};
|
||||
interface FsDriver {
|
||||
writeFile: Function;
|
||||
exists: Function;
|
||||
cacheCssToFile: Function;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
ResourceModel: any;
|
||||
resourceBaseUrl?: string;
|
||||
fsDriver?: FsDriver;
|
||||
}
|
||||
|
||||
interface RenderOptions {
|
||||
splitted: boolean;
|
||||
bodyOnly: boolean;
|
||||
externalAssetsOnly: boolean;
|
||||
resources: any;
|
||||
postMessageSyntax: string;
|
||||
enableLongPress: boolean;
|
||||
}
|
||||
|
||||
interface RenderResult {
|
||||
html: string;
|
||||
pluginAssets: any[];
|
||||
}
|
||||
|
||||
export default class HtmlToHtml {
|
||||
|
||||
private resourceBaseUrl_;
|
||||
private ResourceModel_;
|
||||
private cache_;
|
||||
private fsDriver_:any;
|
||||
|
||||
constructor(options:Options = null) {
|
||||
options = {
|
||||
ResourceModel: null,
|
||||
...options
|
||||
};
|
||||
|
||||
this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null;
|
||||
this.ResourceModel_ = options.ResourceModel;
|
||||
this.cache_ = inMemoryCache;
|
||||
@@ -36,7 +72,7 @@ class HtmlToHtml {
|
||||
return this.fsDriver_;
|
||||
}
|
||||
|
||||
splitHtml(html) {
|
||||
splitHtml(html:string) {
|
||||
const trimmedHtml = html.trimStart();
|
||||
if (trimmedHtml.indexOf('<style>') !== 0) return { html: html, css: '' };
|
||||
|
||||
@@ -49,17 +85,20 @@ class HtmlToHtml {
|
||||
};
|
||||
}
|
||||
|
||||
async allAssets(/* theme*/) {
|
||||
async allAssets(/* theme*/):Promise<any[]> {
|
||||
return []; // TODO
|
||||
}
|
||||
|
||||
// Note: the "theme" variable is ignored and instead the light theme is
|
||||
// always used for HTML notes.
|
||||
// See: https://github.com/laurent22/joplin/issues/3698
|
||||
async render(markup, _theme, options) {
|
||||
options = Object.assign({}, {
|
||||
async render(markup:string, _theme:any, options:RenderOptions):Promise<RenderResult> {
|
||||
options = {
|
||||
splitted: false,
|
||||
}, options);
|
||||
postMessageSyntax: 'postMessage',
|
||||
enableLongPress: false,
|
||||
...options,
|
||||
};
|
||||
|
||||
const cacheKey = md5(escape(markup));
|
||||
let html = this.cache_.value(cacheKey);
|
||||
@@ -67,7 +106,7 @@ class HtmlToHtml {
|
||||
if (!html) {
|
||||
html = htmlUtils.sanitizeHtml(markup);
|
||||
|
||||
html = htmlUtils.processImageTags(html, data => {
|
||||
html = htmlUtils.processImageTags(html, (data:any) => {
|
||||
if (!data.src) return null;
|
||||
|
||||
const r = utils.imageReplacement(this.ResourceModel_, data.src, options.resources, this.resourceBaseUrl_);
|
||||
@@ -85,6 +124,24 @@ class HtmlToHtml {
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
html = htmlUtils.processAnchorTags(html, (data:any) => {
|
||||
if (!data.href) return null;
|
||||
|
||||
const r = linkReplacement(data.href, {
|
||||
resources: options.resources,
|
||||
ResourceModel: this.ResourceModel_,
|
||||
postMessageSyntax: options.postMessageSyntax,
|
||||
enableLongPress: options.enableLongPress,
|
||||
});
|
||||
|
||||
if (!r) return null;
|
||||
|
||||
return {
|
||||
type: 'replaceElement',
|
||||
html: r,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
this.cache_.setValue(cacheKey, html, 1000 * 60 * 10);
|
||||
@@ -98,13 +155,13 @@ class HtmlToHtml {
|
||||
|
||||
// const lightTheme = themeStyle(Setting.THEME_LIGHT);
|
||||
// let cssStrings = noteStyle(lightTheme);
|
||||
let cssStrings = [];
|
||||
let cssStrings:string[] = [];
|
||||
|
||||
if (options.splitted) {
|
||||
const splitted = this.splitHtml(html);
|
||||
cssStrings = [splitted.css].concat(cssStrings);
|
||||
|
||||
const output = {
|
||||
const output:RenderResult = {
|
||||
html: splitted.html,
|
||||
pluginAssets: [],
|
||||
};
|
||||
@@ -124,5 +181,3 @@ class HtmlToHtml {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlToHtml;
|
||||
@@ -1,6 +1,6 @@
|
||||
import MdToHtml from './MdToHtml';
|
||||
const HtmlToHtml = require('./HtmlToHtml');
|
||||
const htmlUtils = require('./htmlUtils');
|
||||
import HtmlToHtml from './HtmlToHtml';
|
||||
import htmlUtils from './htmlUtils';
|
||||
const MarkdownIt = require('markdown-it');
|
||||
|
||||
export enum MarkupLanguage {
|
||||
|
||||
@@ -68,9 +68,9 @@ function slugify(s: string): string {
|
||||
const inMemoryCache = new InMemoryCache(20);
|
||||
|
||||
export interface ExtraRendererRule {
|
||||
id: string,
|
||||
module: any,
|
||||
assetPath: string,
|
||||
id: string;
|
||||
module: any;
|
||||
assetPath: string;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
@@ -138,10 +138,6 @@ export interface RuleOptions {
|
||||
// to display a context menu. Used in `image.ts` and `link_open.ts`
|
||||
enableLongPress?: boolean;
|
||||
|
||||
// Used in mobile app when enableLongPress = true. Tells for how long
|
||||
// the resource should be pressed before the menu is shown.
|
||||
longPressDelay?: number;
|
||||
|
||||
// Use by `link_open` rule.
|
||||
// linkRenderingType = 1 is the regular rendering and clicking on it is handled via embedded JS (in onclick attribute)
|
||||
// linkRenderingType = 2 gives a plain link with no JS. Caller needs to handle clicking on the link.
|
||||
|
||||
53
packages/renderer/MdToHtml/linkReplacement.test.ts
Normal file
53
packages/renderer/MdToHtml/linkReplacement.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import linkReplacement from './linkReplacement';
|
||||
|
||||
describe('linkReplacement', () => {
|
||||
|
||||
test('should handle non-resource links', () => {
|
||||
const r = linkReplacement('https://example.com/test');
|
||||
expect(r).toBe(`<a data-from-md href='https://example.com/test' onclick='postMessage("https://example.com/test", { resourceId: "" }); return false;'>`);
|
||||
});
|
||||
|
||||
test('should handle non-resource links - simple rendering', () => {
|
||||
const r = linkReplacement('https://example.com/test', { linkRenderingType: 2 });
|
||||
expect(r).toBe(`<a data-from-md href='https://example.com/test'>`);
|
||||
});
|
||||
|
||||
test('should handle resource links - downloaded status', () => {
|
||||
const resourceId = 'f6afba55bdf74568ac94f8d1e3578d2c';
|
||||
|
||||
const r = linkReplacement(':/' + resourceId, {
|
||||
ResourceModel: {},
|
||||
resources: {
|
||||
[resourceId]: {
|
||||
item: {},
|
||||
localState: {
|
||||
fetch_status: 2, // FETCH_STATUS_DONE
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(r).toBe(`<a data-from-md data-resource-id='${resourceId}' href='#' onclick='postMessage("joplin://${resourceId}", { resourceId: "${resourceId}" }); return false;'><span class="resource-icon fa-joplin"></span>`);
|
||||
});
|
||||
|
||||
test('should handle resource links - idle status', () => {
|
||||
const resourceId = 'f6afba55bdf74568ac94f8d1e3578d2c';
|
||||
|
||||
const r = linkReplacement(':/' + resourceId, {
|
||||
ResourceModel: {},
|
||||
resources: {
|
||||
[resourceId]: {
|
||||
item: {},
|
||||
localState: {
|
||||
fetch_status: 0, // FETCH_STATUS_IDLE
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Since the icon is embedded as SVG, we only check for the prefix
|
||||
const expectedPrefix = `<a class="not-loaded-resource resource-status-notDownloaded" data-resource-id="${resourceId}"><img src="data:image/svg+xml;utf8`
|
||||
expect(r.indexOf(expectedPrefix)).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
104
packages/renderer/MdToHtml/linkReplacement.ts
Normal file
104
packages/renderer/MdToHtml/linkReplacement.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import utils from '../utils';
|
||||
const Entities = require('html-entities').AllHtmlEntities;
|
||||
const htmlentities = new Entities().encode;
|
||||
const urlUtils = require('../urlUtils.js');
|
||||
const { getClassNameForMimeType } = require('font-awesome-filetypes');
|
||||
|
||||
export interface Options {
|
||||
title?:string,
|
||||
resources?:any,
|
||||
ResourceModel?:any,
|
||||
linkRenderingType?:number,
|
||||
plainResourceRendering?:boolean,
|
||||
postMessageSyntax?:string,
|
||||
enableLongPress?:boolean,
|
||||
}
|
||||
|
||||
export default function(href:string, options:Options = null) {
|
||||
options = {
|
||||
title: '',
|
||||
resources: {},
|
||||
ResourceModel: null,
|
||||
linkRenderingType: 1,
|
||||
plainResourceRendering: false,
|
||||
postMessageSyntax: 'postMessage',
|
||||
enableLongPress: false,
|
||||
...options,
|
||||
};
|
||||
|
||||
const resourceHrefInfo = urlUtils.parseResourceUrl(href);
|
||||
const isResourceUrl = options.resources && !!resourceHrefInfo;
|
||||
let title = options.title;
|
||||
|
||||
let resourceIdAttr = '';
|
||||
let icon = '';
|
||||
let hrefAttr = '#';
|
||||
let mime = '';
|
||||
let resourceId = '';
|
||||
if (isResourceUrl) {
|
||||
resourceId = resourceHrefInfo.itemId;
|
||||
|
||||
const result = options.resources[resourceId];
|
||||
const resourceStatus = utils.resourceStatus(options.ResourceModel, result);
|
||||
|
||||
if (result && result.item) {
|
||||
if (!title) title = result.item.title;
|
||||
mime = result.item.mime;
|
||||
}
|
||||
|
||||
if (result && resourceStatus !== 'ready' && !options.plainResourceRendering) {
|
||||
const icon = utils.resourceStatusFile(resourceStatus);
|
||||
return `<a class="not-loaded-resource resource-status-${resourceStatus}" data-resource-id="${resourceId}">` + `<img src="data:image/svg+xml;utf8,${htmlentities(icon)}"/>`;
|
||||
} else {
|
||||
href = `joplin://${resourceId}`;
|
||||
if (resourceHrefInfo.hash) href += `#${resourceHrefInfo.hash}`;
|
||||
resourceIdAttr = `data-resource-id='${resourceId}'`;
|
||||
|
||||
let iconType = mime ? getClassNameForMimeType(mime) : 'fa-joplin';
|
||||
|
||||
// Icons are defined in lib/renderers/noteStyle using inline svg
|
||||
// The icons are taken from fork-awesome but use the font-awesome naming scheme in order
|
||||
// to be more compatible with the getClass library
|
||||
icon = `<span class="resource-icon ${iconType}"></span>`;
|
||||
}
|
||||
} else {
|
||||
// If the link is a plain URL (as opposed to a resource link), set the href to the actual
|
||||
// link. This allows the link to be exported too when exporting to PDF.
|
||||
hrefAttr = href;
|
||||
}
|
||||
|
||||
// A single quote is valid in a URL but we don't want any because the
|
||||
// href is already enclosed in single quotes.
|
||||
// https://github.com/laurent22/joplin/issues/2030
|
||||
href = href.replace(/'/g, '%27');
|
||||
|
||||
let js = `${options.postMessageSyntax}(${JSON.stringify(href)}, { resourceId: ${JSON.stringify(resourceId)} }); return false;`;
|
||||
if (options.enableLongPress && !!resourceId) {
|
||||
const onClick = `${options.postMessageSyntax}(${JSON.stringify(href)})`;
|
||||
const onLongClick = `${options.postMessageSyntax}("longclick:${resourceId}")`;
|
||||
const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${utils.longPressDelay});`;
|
||||
const cancel = 'if (!!t) {clearTimeout(t); t=null;';
|
||||
const touchEnd = `${cancel} ${onClick};}`;
|
||||
js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}' ontouchcancel='${cancel} ontouchmove="${cancel}'`;
|
||||
} else {
|
||||
js = `onclick='${js}'`;
|
||||
}
|
||||
|
||||
if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place
|
||||
|
||||
const attrHtml = [];
|
||||
attrHtml.push('data-from-md');
|
||||
if (resourceIdAttr) attrHtml.push(resourceIdAttr);
|
||||
if (title) attrHtml.push(`title='${htmlentities(title)}'`);
|
||||
if (mime) attrHtml.push(`type='${htmlentities(mime)}'`);
|
||||
|
||||
if (options.plainResourceRendering || options.linkRenderingType === 2) {
|
||||
icon = '';
|
||||
attrHtml.push(`href='${htmlentities(href)}'`);
|
||||
} else {
|
||||
attrHtml.push(`href='${hrefAttr}'`);
|
||||
if (js) attrHtml.push(js);
|
||||
}
|
||||
|
||||
return `<a ${attrHtml.join(' ')}>${icon}`;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RuleOptions } from '../../MdToHtml';
|
||||
|
||||
const htmlUtils = require('../../htmlUtils.js');
|
||||
const utils = require('../../utils');
|
||||
import htmlUtils from '../../htmlUtils';
|
||||
import utils from '../../utils';
|
||||
|
||||
function renderImageHtml(before: string, src: string, after: string, ruleOptions: RuleOptions) {
|
||||
const r = utils.imageReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RuleOptions } from '../../MdToHtml';
|
||||
|
||||
const utils = require('../../utils');
|
||||
const htmlUtils = require('../../htmlUtils.js');
|
||||
import htmlUtils from '../../htmlUtils';
|
||||
import utils from '../../utils';
|
||||
|
||||
function plugin(markdownIt: any, ruleOptions: RuleOptions) {
|
||||
const defaultRender = markdownIt.renderer.rules.image;
|
||||
@@ -23,7 +22,7 @@ function plugin(markdownIt: any, ruleOptions: RuleOptions) {
|
||||
const id = r['data-resource-id'];
|
||||
|
||||
const longPressHandler = `${ruleOptions.postMessageSyntax}('longclick:${id}')`;
|
||||
const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${ruleOptions.longPressDelay});`;
|
||||
const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${utils.longPressDelay});`;
|
||||
const cancel = 'if (!!t) clearTimeout(t); t=null';
|
||||
|
||||
js = ` ontouchstart="${touchStart}" ontouchend="${cancel}" ontouchcancel="${cancel}" ontouchmove="${cancel}"`;
|
||||
|
||||
@@ -1,95 +1,26 @@
|
||||
import { RuleOptions } from '../../MdToHtml';
|
||||
import linkReplacement from '../linkReplacement';
|
||||
import utils from '../../utils';
|
||||
|
||||
const Entities = require('html-entities').AllHtmlEntities;
|
||||
const htmlentities = new Entities().encode;
|
||||
const utils = require('../../utils');
|
||||
const urlUtils = require('../../urlUtils.js');
|
||||
const { getClassNameForMimeType } = require('font-awesome-filetypes');
|
||||
|
||||
function plugin(markdownIt: any, ruleOptions: RuleOptions) {
|
||||
markdownIt.renderer.rules.link_open = function(tokens: any[], idx: number) {
|
||||
const token = tokens[idx];
|
||||
let href = utils.getAttr(token.attrs, 'href');
|
||||
const href = utils.getAttr(token.attrs, 'href');
|
||||
const resourceHrefInfo = urlUtils.parseResourceUrl(href);
|
||||
const isResourceUrl = ruleOptions.resources && !!resourceHrefInfo;
|
||||
let title = utils.getAttr(token.attrs, 'title', isResourceUrl ? '' : href);
|
||||
const title = utils.getAttr(token.attrs, 'title', isResourceUrl ? '' : href);
|
||||
|
||||
let resourceIdAttr = '';
|
||||
let icon = '';
|
||||
let hrefAttr = '#';
|
||||
let mime = '';
|
||||
let resourceId = '';
|
||||
if (isResourceUrl) {
|
||||
resourceId = resourceHrefInfo.itemId;
|
||||
|
||||
const result = ruleOptions.resources[resourceId];
|
||||
const resourceStatus = utils.resourceStatus(ruleOptions.ResourceModel, result);
|
||||
|
||||
if (result && result.item) {
|
||||
title = utils.getAttr(token.attrs, 'title', result.item.title);
|
||||
mime = result.item.mime;
|
||||
}
|
||||
|
||||
if (result && resourceStatus !== 'ready' && !ruleOptions.plainResourceRendering) {
|
||||
const icon = utils.resourceStatusFile(resourceStatus);
|
||||
return `<a class="not-loaded-resource resource-status-${resourceStatus}" data-resource-id="${resourceId}">` + `<img src="data:image/svg+xml;utf8,${htmlentities(icon)}"/>`;
|
||||
} else {
|
||||
href = `joplin://${resourceId}`;
|
||||
if (resourceHrefInfo.hash) href += `#${resourceHrefInfo.hash}`;
|
||||
resourceIdAttr = `data-resource-id='${resourceId}'`;
|
||||
|
||||
let iconType = getClassNameForMimeType(mime);
|
||||
if (!mime) {
|
||||
iconType = 'fa-joplin';
|
||||
}
|
||||
// Icons are defined in lib/renderers/noteStyle using inline svg
|
||||
// The icons are taken from fork-awesome but use the font-awesome naming scheme in order
|
||||
// to be more compatible with the getClass library
|
||||
icon = `<span class="resource-icon ${iconType}"></span>`;
|
||||
}
|
||||
} else {
|
||||
// If the link is a plain URL (as opposed to a resource link), set the href to the actual
|
||||
// link. This allows the link to be exported too when exporting to PDF.
|
||||
hrefAttr = href;
|
||||
}
|
||||
|
||||
// A single quote is valid in a URL but we don't want any because the
|
||||
// href is already enclosed in single quotes.
|
||||
// https://github.com/laurent22/joplin/issues/2030
|
||||
href = href.replace(/'/g, '%27');
|
||||
|
||||
let js = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)}, { resourceId: ${JSON.stringify(resourceId)} }); return false;`;
|
||||
if (ruleOptions.enableLongPress && !!resourceId) {
|
||||
const onClick = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)})`;
|
||||
const onLongClick = `${ruleOptions.postMessageSyntax}("longclick:${resourceId}")`;
|
||||
const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${ruleOptions.longPressDelay});`;
|
||||
const cancel = 'if (!!t) {clearTimeout(t); t=null;';
|
||||
const touchEnd = `${cancel} ${onClick};}`;
|
||||
js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}' ontouchcancel='${cancel} ontouchmove="${cancel}'`;
|
||||
} else {
|
||||
js = `onclick='${js}'`;
|
||||
}
|
||||
|
||||
if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place
|
||||
|
||||
const attrHtml = [];
|
||||
attrHtml.push('data-from-md');
|
||||
if (resourceIdAttr) attrHtml.push(resourceIdAttr);
|
||||
if (title) attrHtml.push(`title='${htmlentities(title)}'`);
|
||||
if (mime) attrHtml.push(`type='${htmlentities(mime)}'`);
|
||||
|
||||
if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) {
|
||||
icon = '';
|
||||
attrHtml.push(`href='${htmlentities(href)}'`);
|
||||
|
||||
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
|
||||
} else {
|
||||
attrHtml.push(`href='${hrefAttr}'`);
|
||||
if (js) attrHtml.push(js);
|
||||
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
|
||||
}
|
||||
|
||||
return `<a ${attrHtml.join(' ')}>${icon}`;
|
||||
return linkReplacement(href, {
|
||||
title,
|
||||
resources: ruleOptions.resources,
|
||||
ResourceModel: ruleOptions.ResourceModel,
|
||||
linkRenderingType: ruleOptions.linkRenderingType,
|
||||
plainResourceRendering: ruleOptions.plainResourceRendering,
|
||||
postMessageSyntax: ruleOptions.postMessageSyntax,
|
||||
enableLongPress: ruleOptions.enableLongPress,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RuleOptions } from '../../MdToHtml';
|
||||
import htmlUtils from '../../htmlUtils';
|
||||
|
||||
const md5 = require('md5');
|
||||
const htmlUtils = require('../../htmlUtils');
|
||||
|
||||
export default {
|
||||
plugin: function(markdownIt: any, ruleOptions: RuleOptions) {
|
||||
|
||||
@@ -6,6 +6,8 @@ const htmlparser2 = require('@joplin/fork-htmlparser2');
|
||||
// https://stackoverflow.com/a/16119722/561309
|
||||
const imageRegex = /<img([\s\S]*?)src=["']([\s\S]*?)["']([\s\S]*?)>/gi;
|
||||
|
||||
const anchorRegex = /<a([\s\S]*?)href=["']([\s\S]*?)["']([\s\S]*?)>/gi;
|
||||
|
||||
const selfClosingElements = [
|
||||
'area',
|
||||
'base',
|
||||
@@ -30,7 +32,7 @@ const selfClosingElements = [
|
||||
|
||||
class HtmlUtils {
|
||||
|
||||
attributesHtml(attr) {
|
||||
attributesHtml(attr:any) {
|
||||
const output = [];
|
||||
|
||||
for (const n in attr) {
|
||||
@@ -41,10 +43,10 @@ class HtmlUtils {
|
||||
return output.join(' ');
|
||||
}
|
||||
|
||||
processImageTags(html, callback) {
|
||||
processImageTags(html:string, callback:Function) {
|
||||
if (!html) return '';
|
||||
|
||||
return html.replace(imageRegex, (v, before, src, after) => {
|
||||
return html.replace(imageRegex, (_v, before, src, after) => {
|
||||
const action = callback({ src: src });
|
||||
|
||||
if (!action) return `<img${before}src="${src}"${after}>`;
|
||||
@@ -66,15 +68,40 @@ class HtmlUtils {
|
||||
});
|
||||
}
|
||||
|
||||
isSelfClosingTag(tagName) {
|
||||
processAnchorTags(html:string, callback:Function) {
|
||||
if (!html) return '';
|
||||
|
||||
return html.replace(anchorRegex, (_v, before, href, after) => {
|
||||
const action = callback({ href: href });
|
||||
|
||||
if (!action) return `<a${before}href="${href}"${after}>`;
|
||||
|
||||
if (action.type === 'replaceElement') {
|
||||
return action.html;
|
||||
}
|
||||
|
||||
if (action.type === 'replaceSource') {
|
||||
return `<img${before}href="${action.href}"${after}>`;
|
||||
}
|
||||
|
||||
if (action.type === 'setAttributes') {
|
||||
const attrHtml = this.attributesHtml(action.attrs);
|
||||
return `<img${before}${attrHtml}${after}>`;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid action: ${action.type}`);
|
||||
});
|
||||
}
|
||||
|
||||
isSelfClosingTag(tagName:string) {
|
||||
return selfClosingElements.includes(tagName.toLowerCase());
|
||||
}
|
||||
|
||||
// TODO: copied from @joplin/lib
|
||||
stripHtml(html) {
|
||||
const output = [];
|
||||
stripHtml(html:string) {
|
||||
const output:string[] = [];
|
||||
|
||||
const tagStack = [];
|
||||
const tagStack:string[] = [];
|
||||
|
||||
const currentTag = () => {
|
||||
if (!tagStack.length) return '';
|
||||
@@ -85,16 +112,16 @@ class HtmlUtils {
|
||||
|
||||
const parser = new htmlparser2.Parser({
|
||||
|
||||
onopentag: (name) => {
|
||||
onopentag: (name:string) => {
|
||||
tagStack.push(name.toLowerCase());
|
||||
},
|
||||
|
||||
ontext: (decodedText) => {
|
||||
ontext: (decodedText:string) => {
|
||||
if (disallowedTags.includes(currentTag())) return;
|
||||
output.push(decodedText);
|
||||
},
|
||||
|
||||
onclosetag: (name) => {
|
||||
onclosetag: (name:string) => {
|
||||
if (currentTag() === name.toLowerCase()) tagStack.pop();
|
||||
},
|
||||
|
||||
@@ -106,16 +133,16 @@ class HtmlUtils {
|
||||
return output.join('').replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
sanitizeHtml(html, options = null) {
|
||||
sanitizeHtml(html:string, options:any = null) {
|
||||
options = Object.assign({}, {
|
||||
// If true, adds a "jop-noMdConv" class to all the tags.
|
||||
// It can be used afterwards to restore HTML tags in Markdown.
|
||||
addNoMdConvClass: false,
|
||||
}, options);
|
||||
|
||||
const output = [];
|
||||
const output:string[] = [];
|
||||
|
||||
const tagStack = [];
|
||||
const tagStack:string[] = [];
|
||||
|
||||
const currentTag = () => {
|
||||
if (!tagStack.length) return '';
|
||||
@@ -135,7 +162,7 @@ class HtmlUtils {
|
||||
|
||||
const parser = new htmlparser2.Parser({
|
||||
|
||||
onopentag: (name, attrs) => {
|
||||
onopentag: (name:string, attrs:any) => {
|
||||
tagStack.push(name.toLowerCase());
|
||||
|
||||
if (disallowedTags.includes(currentTag())) return;
|
||||
@@ -171,7 +198,7 @@ class HtmlUtils {
|
||||
output.push(`<${name}${attrHtml}${closingSign}`);
|
||||
},
|
||||
|
||||
ontext: (decodedText) => {
|
||||
ontext: (decodedText:string) => {
|
||||
if (disallowedTags.includes(currentTag())) return;
|
||||
|
||||
if (currentTag() === 'style') {
|
||||
@@ -184,7 +211,7 @@ class HtmlUtils {
|
||||
}
|
||||
},
|
||||
|
||||
onclosetag: (name) => {
|
||||
onclosetag: (name:string) => {
|
||||
const current = currentTag();
|
||||
|
||||
if (current === name.toLowerCase()) tagStack.pop();
|
||||
@@ -206,6 +233,4 @@ class HtmlUtils {
|
||||
|
||||
}
|
||||
|
||||
const htmlUtils = new HtmlUtils();
|
||||
|
||||
module.exports = htmlUtils;
|
||||
export default new HtmlUtils();
|
||||
@@ -1,9 +1,9 @@
|
||||
import MarkupToHtml, { MarkupLanguage } from './MarkupToHtml';
|
||||
import MdToHtml from './MdToHtml';
|
||||
const HtmlToHtml = require('./HtmlToHtml');
|
||||
import HtmlToHtml from './HtmlToHtml';
|
||||
import utils from './utils';
|
||||
const setupLinkify = require('./MdToHtml/setupLinkify');
|
||||
const assetsToHeaders = require('./assetsToHeaders');
|
||||
const utils = require('./utils');
|
||||
|
||||
export {
|
||||
MarkupToHtml,
|
||||
|
||||
191
packages/renderer/jest.config.js
Normal file
191
packages/renderer/jest.config.js
Normal file
@@ -0,0 +1,191 @@
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/tmp/jest_rs",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: undefined,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: 'v8',
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: 'node',
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
testMatch: [
|
||||
'**/*.test.js',
|
||||
],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/",
|
||||
// "\\.pnp\\.[^\\/]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
||||
5746
packages/renderer/package-lock.json
generated
5746
packages/renderer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,17 +12,20 @@
|
||||
"buildAssets": "node Tools/buildAssets.js",
|
||||
"prepublishOnly": "npm run buildAssets",
|
||||
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
||||
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json"
|
||||
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
||||
"test": "jest",
|
||||
"test-ci": "test"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.14.6",
|
||||
"jasmine": "^3.5.0",
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.8",
|
||||
"@types/jest": "^26.0.15",
|
||||
"font-awesome-filetypes": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"highlight.js": "^10.2.1",
|
||||
|
||||
@@ -9,9 +9,9 @@ const FetchStatuses = {
|
||||
FETCH_STATUS_ERROR: 3,
|
||||
};
|
||||
|
||||
const utils = {};
|
||||
const utils:any = {};
|
||||
|
||||
utils.getAttr = function(attrs, name, defaultValue = null) {
|
||||
utils.getAttr = function(attrs:string[], name:string, defaultValue:string = null) {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i][0] === name) return attrs[i].length > 1 ? attrs[i][1] : null;
|
||||
}
|
||||
@@ -63,12 +63,12 @@ utils.loaderImage = function() {
|
||||
`;
|
||||
};
|
||||
|
||||
utils.resourceStatusImage = function(status) {
|
||||
utils.resourceStatusImage = function(status:string) {
|
||||
if (status === 'notDownloaded') return utils.notDownloadedResource();
|
||||
return utils.resourceStatusFile(status);
|
||||
};
|
||||
|
||||
utils.resourceStatusFile = function(status) {
|
||||
utils.resourceStatusFile = function(status:string) {
|
||||
if (status === 'notDownloaded') return utils.notDownloadedResource();
|
||||
if (status === 'downloading') return utils.loaderImage();
|
||||
if (status === 'encrypted') return utils.loaderImage();
|
||||
@@ -77,7 +77,7 @@ utils.resourceStatusFile = function(status) {
|
||||
throw new Error(`Unknown status: ${status}`);
|
||||
};
|
||||
|
||||
utils.resourceStatusIndex = function(status) {
|
||||
utils.resourceStatusIndex = function(status:string) {
|
||||
if (status === 'error') return -1;
|
||||
if (status === 'notDownloaded') return 0;
|
||||
if (status === 'downloading') return 1;
|
||||
@@ -87,7 +87,7 @@ utils.resourceStatusIndex = function(status) {
|
||||
throw new Error(`Unknown status: ${status}`);
|
||||
};
|
||||
|
||||
utils.resourceStatusName = function(index) {
|
||||
utils.resourceStatusName = function(index:number) {
|
||||
if (index === -1) return 'error';
|
||||
if (index === 0) return 'notDownloaded';
|
||||
if (index === 1) return 'downloading';
|
||||
@@ -97,7 +97,7 @@ utils.resourceStatusName = function(index) {
|
||||
throw new Error(`Unknown index: ${index}`);
|
||||
};
|
||||
|
||||
utils.resourceStatus = function(ResourceModel, resourceInfo) {
|
||||
utils.resourceStatus = function(ResourceModel:any, resourceInfo:any) {
|
||||
if (!ResourceModel) return 'ready';
|
||||
|
||||
let resourceStatus = 'ready';
|
||||
@@ -122,7 +122,7 @@ utils.resourceStatus = function(ResourceModel, resourceInfo) {
|
||||
return resourceStatus;
|
||||
};
|
||||
|
||||
utils.imageReplacement = function(ResourceModel, src, resources, resourceBaseUrl) {
|
||||
utils.imageReplacement = function(ResourceModel:any, src:string, resources:any, resourceBaseUrl:string) {
|
||||
if (!ResourceModel || !resources) return null;
|
||||
|
||||
if (!ResourceModel.isResourceUrl(src)) return null;
|
||||
@@ -151,4 +151,8 @@ utils.imageReplacement = function(ResourceModel, src, resources, resourceBaseUrl
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = utils;
|
||||
// Used in mobile app when enableLongPress = true. Tells for how long
|
||||
// the resource should be pressed before the menu is shown.
|
||||
utils.longPressDelay = 500;
|
||||
|
||||
export default utils;
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
// Dependencies:
|
||||
//
|
||||
// sudo apt install gettext
|
||||
// sudo apt install translate-toolkit
|
||||
// sudo apt install gettext sudo apt install translate-toolkit
|
||||
//
|
||||
// gettext v21+ is required as versions before that have bugs when parsing
|
||||
// JavaScript template strings which means we would lose translations.
|
||||
|
||||
const rootDir = `${__dirname}/../..`;
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ function platformFromTag(tagName) {
|
||||
if (tagName.indexOf('ios') >= 0) return 'ios';
|
||||
if (tagName.indexOf('clipper') === 0) return 'clipper';
|
||||
if (tagName.indexOf('cli') === 0) return 'cli';
|
||||
if (tagName.indexOf('plugin-generator') === 0) return 'plugin-generator';
|
||||
throw new Error(`Could not determine platform from tag: ${tagName}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ With plugins you can directly modify Joplin by adding new features to the applic
|
||||
- Create a module to export or import data into Joplin
|
||||
- Define new settings and setting sections, and get/set them from the plugin
|
||||
- Create a new Markdown plugin to render custom markup.
|
||||
- Create an editor plugin to modify low-level the behaviour of the Markdown editor (CodeMirror)
|
||||
- Create an editor plugin to modify, at a low-level, the behaviour of the Markdown editor (CodeMirror)
|
||||
|
||||
To get started with the plugin API, check the [Get Started](https://github.com/laurent22/joplin/blob/dev/readme/api/get_started/plugins.md) page or have a look at the [TOC tutorial](https://github.com/laurent22/joplin/blob/dev/readme/api/tutorials/toc_plugin.md).
|
||||
|
||||
Once you are familiar with the API, you can have a look at the [plugin API reference](https://github.com/laurent22/joplin/blob/dev/readme/api/get_started/plugins.md) for a detailed documentation about each supported feature.
|
||||
Once you are familiar with the API, you can have a look at the [plugin API reference](https://joplinapp.org/api/references/plugin_api/classes/joplin.html) for a detailed documentation about each supported feature.
|
||||
|
||||
Reference in New Issue
Block a user