1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-29 23:48:19 +02:00

Compare commits

...

59 Commits

Author SHA1 Message Date
Laurent Cozic
c3854ec350 ios-v10.6.1 2021-01-02 14:54:31 +00:00
Laurent Cozic
c2c737541d ios release script 2021-01-02 14:53:30 +00:00
Laurent Cozic
5b295d5f6f Plugins: Add support for hiding and showing panels 2021-01-02 13:32:15 +00:00
Helmut K. C. Tessarek
0be8cdf760 Update translations 2021-01-01 21:00:35 -05:00
Po-chiang Chao
545940f545 All: Translation: Update zh_TW.po (#4278)
Include new strings and fuzzy translated strings
2021-01-01 19:18:52 -05:00
Laurent Cozic
d58f39823a Doc: Added link to new specs, and added more details to delta sync spec 2021-01-01 13:25:05 +00:00
Laurent Cozic
f9fb1b8a81 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-01-01 13:04:30 +00:00
Laurent Cozic
56ded0062a All: Add more log info when a revision cannot be deleted due to still-encrypted itel 2021-01-01 13:04:15 +00:00
Laurent Cozic
83b29d7c51 macOS: Fixed paste as text in Rich Text editor 2021-01-01 12:38:17 +00:00
Arda Kılıçdağı
1ec0746263 All: Translation: Update tr_TR.po (#4274)
Hello,

You can find the updated Turkish translations in this pull request.

Also, happy new year! 🎉
2020-12-31 12:08:09 -05:00
Laurent Cozic
568d11bddf Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-12-31 16:04:06 +00:00
Laurent Cozic
97b25ac99d Doc: Add spec for Joplin Server delta sync 2020-12-31 15:58:38 +00:00
Laurent Cozic
7f1d3d8a5d Doc: Added spec for server URL formats 2020-12-31 12:38:22 +00:00
Elaborendum
fde201fbe9 All: Translation: Update es_ES.po (#4260)
* Updated es_ES.po

* Reverting some changes

* Redid translation using current .po file
2020-12-31 00:59:25 -05:00
Laurent Cozic
694a1b4ede Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-12-30 23:53:20 +00:00
Laurent Cozic
20d126b39d Server: Fixed access control on user list and clean up 2020-12-30 23:50:44 +00:00
Helmut K. C. Tessarek
8ca9c3092a Clipper release v1.6.1 2020-12-30 14:50:47 -05:00
Laurent Cozic
d2771029a3 Server: Support for notifications and clean up 2020-12-30 18:35:18 +00:00
Laurent Cozic
4128c53fcf Update French translation 2020-12-30 13:07:41 +00:00
Laurent Cozic
a14410b28c Android release v1.6.4 2020-12-30 12:04:25 +00:00
Laurent Cozic
d1f8520e6e Mobile: Fixed package import issue 2020-12-30 11:51:43 +00:00
Laurent Cozic
d76746b8e4 Android release v1.6.3 2020-12-30 11:05:04 +00:00
Laurent Cozic
89d173b460 Mobile: Add support for Joplin Server 2020-12-30 10:54:00 +00:00
Laurent Cozic
81aba8b8b0 Server: Fixed pagination 2020-12-30 02:24:29 +00:00
Laurent Cozic
f48697572d Server: Fixed file count issue for PostgreSQL 2020-12-29 23:48:34 +00:00
Laurent Cozic
e61e8b7b94 Server: Fixed URLs 2020-12-29 23:22:57 +00:00
Laurent Cozic
1deab7e8d1 Android release v1.6.2 2020-12-29 19:42:30 +00:00
Laurent Cozic
86b28b5ecf Desktop release v1.6.1 2020-12-29 18:30:38 +00:00
Laurent Cozic
938e723434 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-12-29 18:07:02 +00:00
Laurent Cozic
ee2ec28cd4 Server: Add basic file manager 2020-12-29 18:04:57 +00:00
Helmut K. C. Tessarek
9dc505e85b fix typo in source and all translations 2020-12-29 12:10:52 -05:00
Helmut K. C. Tessarek
3df31584af update en_US.po 2020-12-29 11:50:31 -05:00
Zhang YANG
a7e3b381cb All: Translation: Update zh_CN.po (#4263) 2020-12-29 11:23:33 -05:00
Laurent Cozic
70381a233b Merge branch 'release-1.5' into dev 2020-12-29 16:17:18 +00:00
Laurent Cozic
2966fe0df2 Desktop release v1.5.13 2020-12-29 16:14:27 +00:00
Laurent Cozic
143f0b4bc5 Desktop, Cli: Improve support for SVG images when importing ENEX files 2020-12-29 15:58:20 +00:00
Helmut K. C. Tessarek
24ec3b8897 Update translations (for new server code) 2020-12-28 15:17:17 -05:00
Beowulf2
01aa4f9d5e All: Translation: Update de_DE.po (#4258) 2020-12-28 14:39:58 -05:00
Manuel Tassi
520efdcb39 All: Translation: Update it_IT.po (#4256)
* Update it_IT.po

* Update it_IT.po

* Update it_IT.po
2020-12-28 14:39:38 -05:00
Laurent Cozic
34a99f738c linter 2020-12-28 17:26:15 +00:00
Laurent Cozic
0e0de1207f Server: Adding basic file manager 2020-12-28 16:37:12 +00:00
Laurent Cozic
2fda067034 Server: Improved logging and error handling 2020-12-28 15:15:30 +00:00
Laurent Cozic
29177330b0 Merge branch 'release-1.5' into dev 2020-12-28 14:31:37 +00:00
Laurent Cozic
66a5490b54 Desktop release v1.5.12 2020-12-28 14:29:37 +00:00
Laurent Cozic
469cd19ec1 Desktop: Fix issue when importing ENEX file that contains invalid list elements 2020-12-28 14:29:02 +00:00
Laurent
41684a64ef Server: Add Joplin Server package (#1872) 2020-12-28 11:48:47 +00:00
Laurent Cozic
2cd7839552 Increase version num to 1.6 2020-12-27 22:46:27 +00:00
Laurent Cozic
c3d4617612 Update website 2020-12-27 22:41:47 +00:00
Laurent Cozic
158fafc4a0 Merge branch 'release-1.5' into dev 2020-12-27 22:40:26 +00:00
Laurent Cozic
0f59731c06 Plugin Generator release v1.5.3 2020-12-27 22:28:16 +00:00
Laurent Cozic
d0f1a67d96 Plugins: Updated types 2020-12-27 22:26:17 +00:00
Laurent Cozic
2a1434f987 Generator: Fixed publish logic 2020-12-27 22:25:17 +00:00
Laurent Cozic
1ee177880d Plugin Generator release v1.5.2 2020-12-27 22:23:18 +00:00
Laurent Cozic
f6d899eb29 Generator: Fixed publish script 2020-12-27 22:22:29 +00:00
Laurent Cozic
a97f25fd61 Update website 2020-12-27 19:55:34 +00:00
Laurent Cozic
325a5ab08f ios-v10.5.1 2020-12-27 19:47:44 +00:00
Laurent Cozic
c158878b66 Desktop release v1.5.11 2020-12-27 19:19:13 +00:00
Laurent Cozic
0f0f9c1161 macOS: Update app icon 2020-12-26 13:49:00 +00:00
Laurent Cozic
79612163b2 Android release v1.5.1 2020-12-26 00:57:31 +00:00
296 changed files with 33772 additions and 13136 deletions

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
**/node_modules
Assets/
.git/
_releases/
packages/app-desktop
packages/app-cli
packages/app-mobile
packages/app-clipper
packages/generator-joplin

9
.env-sample Normal file
View File

@@ -0,0 +1,9 @@
# Example of local config, for development:
#
# JOPLIN_BASE_URL=http://localhost:22300
# JOPLIN_PORT=22300
# Example of config for production:
#
# JOPLIN_BASE_URL=https://example.com/joplin
# JOPLIN_PORT=22300

View File

@@ -6,6 +6,7 @@ _releases/
**/node_modules/
Assets/
docs/
packages/server/dist/
highlight.pack.js
Modules/TinyMCE/IconPack/postinstall.js
Modules/TinyMCE/JoplinLists/
@@ -802,6 +803,9 @@ packages/app-mobile/components/screens/Note.js.map
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
packages/app-mobile/root.d.ts
packages/app-mobile/root.js
packages/app-mobile/root.js.map
packages/app-mobile/services/AlarmServiceDriver.android.d.ts
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.android.js.map
@@ -889,12 +893,18 @@ packages/lib/InMemoryCache.js.map
packages/lib/JoplinServerApi.d.ts
packages/lib/JoplinServerApi.js
packages/lib/JoplinServerApi.js.map
packages/lib/JoplinServerApi2.d.ts
packages/lib/JoplinServerApi2.js
packages/lib/JoplinServerApi2.js.map
packages/lib/Logger.d.ts
packages/lib/Logger.js
packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map
packages/lib/Synchronizer.d.ts
packages/lib/Synchronizer.js
packages/lib/Synchronizer.js.map
@@ -916,12 +926,18 @@ packages/lib/errorUtils.js.map
packages/lib/eventManager.d.ts
packages/lib/eventManager.js
packages/lib/eventManager.js.map
packages/lib/file-api-driver-joplinServer.d.ts
packages/lib/file-api-driver-joplinServer.js
packages/lib/file-api-driver-joplinServer.js.map
packages/lib/fs-driver-base.d.ts
packages/lib/fs-driver-base.js
packages/lib/fs-driver-base.js.map
packages/lib/fs-driver-node.d.ts
packages/lib/fs-driver-node.js
packages/lib/fs-driver-node.js.map
packages/lib/import-enex-md-gen.d.ts
packages/lib/import-enex-md-gen.js
packages/lib/import-enex-md-gen.js.map
packages/lib/locale.d.ts
packages/lib/locale.js
packages/lib/locale.js.map
@@ -1387,4 +1403,238 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts
packages/renderer/utils.js
packages/renderer/utils.js.map
packages/server/src/app.d.ts
packages/server/src/app.js
packages/server/src/app.js.map
packages/server/src/config-base.d.ts
packages/server/src/config-base.js
packages/server/src/config-base.js.map
packages/server/src/config-buildTypes.d.ts
packages/server/src/config-buildTypes.js
packages/server/src/config-buildTypes.js.map
packages/server/src/config-dev.d.ts
packages/server/src/config-dev.js
packages/server/src/config-dev.js.map
packages/server/src/config-prod.d.ts
packages/server/src/config-prod.js
packages/server/src/config-prod.js.map
packages/server/src/config-tests.d.ts
packages/server/src/config-tests.js
packages/server/src/config-tests.js.map
packages/server/src/config.d.ts
packages/server/src/config.js
packages/server/src/config.js.map
packages/server/src/controllers/BaseController.d.ts
packages/server/src/controllers/BaseController.js
packages/server/src/controllers/BaseController.js.map
packages/server/src/controllers/api/FileController.d.ts
packages/server/src/controllers/api/FileController.js
packages/server/src/controllers/api/FileController.js.map
packages/server/src/controllers/api/FileController.test.d.ts
packages/server/src/controllers/api/FileController.test.js
packages/server/src/controllers/api/FileController.test.js.map
packages/server/src/controllers/api/OAuthController.d.ts
packages/server/src/controllers/api/OAuthController.js
packages/server/src/controllers/api/OAuthController.js.map
packages/server/src/controllers/api/SessionController.d.ts
packages/server/src/controllers/api/SessionController.js
packages/server/src/controllers/api/SessionController.js.map
packages/server/src/controllers/api/SessionController.test.d.ts
packages/server/src/controllers/api/SessionController.test.js
packages/server/src/controllers/api/SessionController.test.js.map
packages/server/src/controllers/api/UserController.d.ts
packages/server/src/controllers/api/UserController.js
packages/server/src/controllers/api/UserController.js.map
packages/server/src/controllers/api/UserController.test.d.ts
packages/server/src/controllers/api/UserController.test.js
packages/server/src/controllers/api/UserController.test.js.map
packages/server/src/controllers/factory.d.ts
packages/server/src/controllers/factory.js
packages/server/src/controllers/factory.js.map
packages/server/src/controllers/index/FileController.d.ts
packages/server/src/controllers/index/FileController.js
packages/server/src/controllers/index/FileController.js.map
packages/server/src/controllers/index/HomeController.d.ts
packages/server/src/controllers/index/HomeController.js
packages/server/src/controllers/index/HomeController.js.map
packages/server/src/controllers/index/LoginController.d.ts
packages/server/src/controllers/index/LoginController.js
packages/server/src/controllers/index/LoginController.js.map
packages/server/src/controllers/index/NotificationController.d.ts
packages/server/src/controllers/index/NotificationController.js
packages/server/src/controllers/index/NotificationController.js.map
packages/server/src/controllers/index/ProfileController.d.ts
packages/server/src/controllers/index/ProfileController.js
packages/server/src/controllers/index/ProfileController.js.map
packages/server/src/controllers/index/UserController.d.ts
packages/server/src/controllers/index/UserController.js
packages/server/src/controllers/index/UserController.js.map
packages/server/src/db.d.ts
packages/server/src/db.js
packages/server/src/db.js.map
packages/server/src/middleware/notificationHandler.d.ts
packages/server/src/middleware/notificationHandler.js
packages/server/src/middleware/notificationHandler.js.map
packages/server/src/middleware/ownerHandler.d.ts
packages/server/src/middleware/ownerHandler.js
packages/server/src/middleware/ownerHandler.js.map
packages/server/src/middleware/routeHandler.d.ts
packages/server/src/middleware/routeHandler.js
packages/server/src/middleware/routeHandler.js.map
packages/server/src/migrations/20190913171451_create.d.ts
packages/server/src/migrations/20190913171451_create.js
packages/server/src/migrations/20190913171451_create.js.map
packages/server/src/migrations/20203012152842_notifications.d.ts
packages/server/src/migrations/20203012152842_notifications.js
packages/server/src/migrations/20203012152842_notifications.js.map
packages/server/src/models/ApiClientModel.d.ts
packages/server/src/models/ApiClientModel.js
packages/server/src/models/ApiClientModel.js.map
packages/server/src/models/BaseModel.d.ts
packages/server/src/models/BaseModel.js
packages/server/src/models/BaseModel.js.map
packages/server/src/models/ChangeModel.d.ts
packages/server/src/models/ChangeModel.js
packages/server/src/models/ChangeModel.js.map
packages/server/src/models/ChangeModel.test.d.ts
packages/server/src/models/ChangeModel.test.js
packages/server/src/models/ChangeModel.test.js.map
packages/server/src/models/FileModel.d.ts
packages/server/src/models/FileModel.js
packages/server/src/models/FileModel.js.map
packages/server/src/models/FileModel.test.d.ts
packages/server/src/models/FileModel.test.js
packages/server/src/models/FileModel.test.js.map
packages/server/src/models/NotificationModel.d.ts
packages/server/src/models/NotificationModel.js
packages/server/src/models/NotificationModel.js.map
packages/server/src/models/NotificationModel.test.d.ts
packages/server/src/models/NotificationModel.test.js
packages/server/src/models/NotificationModel.test.js.map
packages/server/src/models/PermissionModel.d.ts
packages/server/src/models/PermissionModel.js
packages/server/src/models/PermissionModel.js.map
packages/server/src/models/SessionModel.d.ts
packages/server/src/models/SessionModel.js
packages/server/src/models/SessionModel.js.map
packages/server/src/models/UserModel.d.ts
packages/server/src/models/UserModel.js
packages/server/src/models/UserModel.js.map
packages/server/src/models/factory.d.ts
packages/server/src/models/factory.js
packages/server/src/models/factory.js.map
packages/server/src/models/utils/pagination.d.ts
packages/server/src/models/utils/pagination.js
packages/server/src/models/utils/pagination.js.map
packages/server/src/models/utils/pagination.test.d.ts
packages/server/src/models/utils/pagination.test.js
packages/server/src/models/utils/pagination.test.js.map
packages/server/src/routes/api/files.d.ts
packages/server/src/routes/api/files.js
packages/server/src/routes/api/files.js.map
packages/server/src/routes/api/index.d.ts
packages/server/src/routes/api/index.js
packages/server/src/routes/api/index.js.map
packages/server/src/routes/api/ping.d.ts
packages/server/src/routes/api/ping.js
packages/server/src/routes/api/ping.js.map
packages/server/src/routes/api/sessions.d.ts
packages/server/src/routes/api/sessions.js
packages/server/src/routes/api/sessions.js.map
packages/server/src/routes/default.d.ts
packages/server/src/routes/default.js
packages/server/src/routes/default.js.map
packages/server/src/routes/index/files.d.ts
packages/server/src/routes/index/files.js
packages/server/src/routes/index/files.js.map
packages/server/src/routes/index/home.d.ts
packages/server/src/routes/index/home.js
packages/server/src/routes/index/home.js.map
packages/server/src/routes/index/login.d.ts
packages/server/src/routes/index/login.js
packages/server/src/routes/index/login.js.map
packages/server/src/routes/index/logout.d.ts
packages/server/src/routes/index/logout.js
packages/server/src/routes/index/logout.js.map
packages/server/src/routes/index/notifications.d.ts
packages/server/src/routes/index/notifications.js
packages/server/src/routes/index/notifications.js.map
packages/server/src/routes/index/profile.d.ts
packages/server/src/routes/index/profile.js
packages/server/src/routes/index/profile.js.map
packages/server/src/routes/index/user.d.ts
packages/server/src/routes/index/user.js
packages/server/src/routes/index/user.js.map
packages/server/src/routes/index/users.d.ts
packages/server/src/routes/index/users.js
packages/server/src/routes/index/users.js.map
packages/server/src/routes/oauth2/authorize.d.ts
packages/server/src/routes/oauth2/authorize.js
packages/server/src/routes/oauth2/authorize.js.map
packages/server/src/routes/routes.d.ts
packages/server/src/routes/routes.js
packages/server/src/routes/routes.js.map
packages/server/src/services/MustacheService.d.ts
packages/server/src/services/MustacheService.js
packages/server/src/services/MustacheService.js.map
packages/server/src/tools/db-migrate.d.ts
packages/server/src/tools/db-migrate.js
packages/server/src/tools/db-migrate.js.map
packages/server/src/tools/dbTools.d.ts
packages/server/src/tools/dbTools.js
packages/server/src/tools/dbTools.js.map
packages/server/src/tools/generate-types.d.ts
packages/server/src/tools/generate-types.js
packages/server/src/tools/generate-types.js.map
packages/server/src/utils/TransactionHandler.d.ts
packages/server/src/utils/TransactionHandler.js
packages/server/src/utils/TransactionHandler.js.map
packages/server/src/utils/auth.d.ts
packages/server/src/utils/auth.js
packages/server/src/utils/auth.js.map
packages/server/src/utils/base64.d.ts
packages/server/src/utils/base64.js
packages/server/src/utils/base64.js.map
packages/server/src/utils/cache.d.ts
packages/server/src/utils/cache.js
packages/server/src/utils/cache.js.map
packages/server/src/utils/defaultView.d.ts
packages/server/src/utils/defaultView.js
packages/server/src/utils/defaultView.js.map
packages/server/src/utils/errors.d.ts
packages/server/src/utils/errors.js
packages/server/src/utils/errors.js.map
packages/server/src/utils/htmlUtils.d.ts
packages/server/src/utils/htmlUtils.js
packages/server/src/utils/htmlUtils.js.map
packages/server/src/utils/koaIf.d.ts
packages/server/src/utils/koaIf.js
packages/server/src/utils/koaIf.js.map
packages/server/src/utils/requestUtils.d.ts
packages/server/src/utils/requestUtils.js
packages/server/src/utils/requestUtils.js.map
packages/server/src/utils/routeUtils.d.ts
packages/server/src/utils/routeUtils.js
packages/server/src/utils/routeUtils.js.map
packages/server/src/utils/routeUtils.test.d.ts
packages/server/src/utils/routeUtils.test.js
packages/server/src/utils/routeUtils.test.js.map
packages/server/src/utils/testUtils.d.ts
packages/server/src/utils/testUtils.js
packages/server/src/utils/testUtils.js.map
packages/server/src/utils/testing/testRouters.d.ts
packages/server/src/utils/testing/testRouters.js
packages/server/src/utils/testing/testRouters.js.map
packages/server/src/utils/time.d.ts
packages/server/src/utils/time.js
packages/server/src/utils/time.js.map
packages/server/src/utils/types.d.ts
packages/server/src/utils/types.js
packages/server/src/utils/types.js.map
packages/server/src/utils/urlUtils.d.ts
packages/server/src/utils/urlUtils.js
packages/server/src/utils/urlUtils.js.map
packages/server/src/utils/uuidgen.d.ts
packages/server/src/utils/uuidgen.js
packages/server/src/utils/uuidgen.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@@ -1,4 +1,5 @@
module.exports = {
'root': true,
'env': {
'browser': true,
'es6': true,
@@ -34,6 +35,9 @@ module.exports = {
'chrome': 'readonly',
'browser': 'readonly',
// Server admin UI global variables
'onDocumentReady': 'readonly',
'tinymce': 'readonly',
},
'parserOptions': {
@@ -61,7 +65,12 @@ module.exports = {
'no-var': ['error'],
'no-new-func': ['error'],
'import/prefer-default-export': ['error'],
'import/first': ['error'],
// This rule should not be enabled since it matters in what order
// imports are done, in particular in relation to the shim.setReact
// call, which should be done first, but this rule might move it down.
// 'import/first': ['error'],
'no-array-constructor': ['error'],
'radix': ['error'],

250
.gitignore vendored
View File

@@ -48,6 +48,7 @@ TODO.md
packages/tools/commit_hook.txt
packages/tools/github_oauth_token.txt
lerna-debug.log
.env
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
packages/app-cli/app/LinkSelector.d.ts
@@ -791,6 +792,9 @@ packages/app-mobile/components/screens/Note.js.map
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.d.ts
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
packages/app-mobile/root.d.ts
packages/app-mobile/root.js
packages/app-mobile/root.js.map
packages/app-mobile/services/AlarmServiceDriver.android.d.ts
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.android.js.map
@@ -878,12 +882,18 @@ packages/lib/InMemoryCache.js.map
packages/lib/JoplinServerApi.d.ts
packages/lib/JoplinServerApi.js
packages/lib/JoplinServerApi.js.map
packages/lib/JoplinServerApi2.d.ts
packages/lib/JoplinServerApi2.js
packages/lib/JoplinServerApi2.js.map
packages/lib/Logger.d.ts
packages/lib/Logger.js
packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map
packages/lib/Synchronizer.d.ts
packages/lib/Synchronizer.js
packages/lib/Synchronizer.js.map
@@ -905,12 +915,18 @@ packages/lib/errorUtils.js.map
packages/lib/eventManager.d.ts
packages/lib/eventManager.js
packages/lib/eventManager.js.map
packages/lib/file-api-driver-joplinServer.d.ts
packages/lib/file-api-driver-joplinServer.js
packages/lib/file-api-driver-joplinServer.js.map
packages/lib/fs-driver-base.d.ts
packages/lib/fs-driver-base.js
packages/lib/fs-driver-base.js.map
packages/lib/fs-driver-node.d.ts
packages/lib/fs-driver-node.js
packages/lib/fs-driver-node.js.map
packages/lib/import-enex-md-gen.d.ts
packages/lib/import-enex-md-gen.js
packages/lib/import-enex-md-gen.js.map
packages/lib/locale.d.ts
packages/lib/locale.js
packages/lib/locale.js.map
@@ -1376,4 +1392,238 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts
packages/renderer/utils.js
packages/renderer/utils.js.map
packages/server/src/app.d.ts
packages/server/src/app.js
packages/server/src/app.js.map
packages/server/src/config-base.d.ts
packages/server/src/config-base.js
packages/server/src/config-base.js.map
packages/server/src/config-buildTypes.d.ts
packages/server/src/config-buildTypes.js
packages/server/src/config-buildTypes.js.map
packages/server/src/config-dev.d.ts
packages/server/src/config-dev.js
packages/server/src/config-dev.js.map
packages/server/src/config-prod.d.ts
packages/server/src/config-prod.js
packages/server/src/config-prod.js.map
packages/server/src/config-tests.d.ts
packages/server/src/config-tests.js
packages/server/src/config-tests.js.map
packages/server/src/config.d.ts
packages/server/src/config.js
packages/server/src/config.js.map
packages/server/src/controllers/BaseController.d.ts
packages/server/src/controllers/BaseController.js
packages/server/src/controllers/BaseController.js.map
packages/server/src/controllers/api/FileController.d.ts
packages/server/src/controllers/api/FileController.js
packages/server/src/controllers/api/FileController.js.map
packages/server/src/controllers/api/FileController.test.d.ts
packages/server/src/controllers/api/FileController.test.js
packages/server/src/controllers/api/FileController.test.js.map
packages/server/src/controllers/api/OAuthController.d.ts
packages/server/src/controllers/api/OAuthController.js
packages/server/src/controllers/api/OAuthController.js.map
packages/server/src/controllers/api/SessionController.d.ts
packages/server/src/controllers/api/SessionController.js
packages/server/src/controllers/api/SessionController.js.map
packages/server/src/controllers/api/SessionController.test.d.ts
packages/server/src/controllers/api/SessionController.test.js
packages/server/src/controllers/api/SessionController.test.js.map
packages/server/src/controllers/api/UserController.d.ts
packages/server/src/controllers/api/UserController.js
packages/server/src/controllers/api/UserController.js.map
packages/server/src/controllers/api/UserController.test.d.ts
packages/server/src/controllers/api/UserController.test.js
packages/server/src/controllers/api/UserController.test.js.map
packages/server/src/controllers/factory.d.ts
packages/server/src/controllers/factory.js
packages/server/src/controllers/factory.js.map
packages/server/src/controllers/index/FileController.d.ts
packages/server/src/controllers/index/FileController.js
packages/server/src/controllers/index/FileController.js.map
packages/server/src/controllers/index/HomeController.d.ts
packages/server/src/controllers/index/HomeController.js
packages/server/src/controllers/index/HomeController.js.map
packages/server/src/controllers/index/LoginController.d.ts
packages/server/src/controllers/index/LoginController.js
packages/server/src/controllers/index/LoginController.js.map
packages/server/src/controllers/index/NotificationController.d.ts
packages/server/src/controllers/index/NotificationController.js
packages/server/src/controllers/index/NotificationController.js.map
packages/server/src/controllers/index/ProfileController.d.ts
packages/server/src/controllers/index/ProfileController.js
packages/server/src/controllers/index/ProfileController.js.map
packages/server/src/controllers/index/UserController.d.ts
packages/server/src/controllers/index/UserController.js
packages/server/src/controllers/index/UserController.js.map
packages/server/src/db.d.ts
packages/server/src/db.js
packages/server/src/db.js.map
packages/server/src/middleware/notificationHandler.d.ts
packages/server/src/middleware/notificationHandler.js
packages/server/src/middleware/notificationHandler.js.map
packages/server/src/middleware/ownerHandler.d.ts
packages/server/src/middleware/ownerHandler.js
packages/server/src/middleware/ownerHandler.js.map
packages/server/src/middleware/routeHandler.d.ts
packages/server/src/middleware/routeHandler.js
packages/server/src/middleware/routeHandler.js.map
packages/server/src/migrations/20190913171451_create.d.ts
packages/server/src/migrations/20190913171451_create.js
packages/server/src/migrations/20190913171451_create.js.map
packages/server/src/migrations/20203012152842_notifications.d.ts
packages/server/src/migrations/20203012152842_notifications.js
packages/server/src/migrations/20203012152842_notifications.js.map
packages/server/src/models/ApiClientModel.d.ts
packages/server/src/models/ApiClientModel.js
packages/server/src/models/ApiClientModel.js.map
packages/server/src/models/BaseModel.d.ts
packages/server/src/models/BaseModel.js
packages/server/src/models/BaseModel.js.map
packages/server/src/models/ChangeModel.d.ts
packages/server/src/models/ChangeModel.js
packages/server/src/models/ChangeModel.js.map
packages/server/src/models/ChangeModel.test.d.ts
packages/server/src/models/ChangeModel.test.js
packages/server/src/models/ChangeModel.test.js.map
packages/server/src/models/FileModel.d.ts
packages/server/src/models/FileModel.js
packages/server/src/models/FileModel.js.map
packages/server/src/models/FileModel.test.d.ts
packages/server/src/models/FileModel.test.js
packages/server/src/models/FileModel.test.js.map
packages/server/src/models/NotificationModel.d.ts
packages/server/src/models/NotificationModel.js
packages/server/src/models/NotificationModel.js.map
packages/server/src/models/NotificationModel.test.d.ts
packages/server/src/models/NotificationModel.test.js
packages/server/src/models/NotificationModel.test.js.map
packages/server/src/models/PermissionModel.d.ts
packages/server/src/models/PermissionModel.js
packages/server/src/models/PermissionModel.js.map
packages/server/src/models/SessionModel.d.ts
packages/server/src/models/SessionModel.js
packages/server/src/models/SessionModel.js.map
packages/server/src/models/UserModel.d.ts
packages/server/src/models/UserModel.js
packages/server/src/models/UserModel.js.map
packages/server/src/models/factory.d.ts
packages/server/src/models/factory.js
packages/server/src/models/factory.js.map
packages/server/src/models/utils/pagination.d.ts
packages/server/src/models/utils/pagination.js
packages/server/src/models/utils/pagination.js.map
packages/server/src/models/utils/pagination.test.d.ts
packages/server/src/models/utils/pagination.test.js
packages/server/src/models/utils/pagination.test.js.map
packages/server/src/routes/api/files.d.ts
packages/server/src/routes/api/files.js
packages/server/src/routes/api/files.js.map
packages/server/src/routes/api/index.d.ts
packages/server/src/routes/api/index.js
packages/server/src/routes/api/index.js.map
packages/server/src/routes/api/ping.d.ts
packages/server/src/routes/api/ping.js
packages/server/src/routes/api/ping.js.map
packages/server/src/routes/api/sessions.d.ts
packages/server/src/routes/api/sessions.js
packages/server/src/routes/api/sessions.js.map
packages/server/src/routes/default.d.ts
packages/server/src/routes/default.js
packages/server/src/routes/default.js.map
packages/server/src/routes/index/files.d.ts
packages/server/src/routes/index/files.js
packages/server/src/routes/index/files.js.map
packages/server/src/routes/index/home.d.ts
packages/server/src/routes/index/home.js
packages/server/src/routes/index/home.js.map
packages/server/src/routes/index/login.d.ts
packages/server/src/routes/index/login.js
packages/server/src/routes/index/login.js.map
packages/server/src/routes/index/logout.d.ts
packages/server/src/routes/index/logout.js
packages/server/src/routes/index/logout.js.map
packages/server/src/routes/index/notifications.d.ts
packages/server/src/routes/index/notifications.js
packages/server/src/routes/index/notifications.js.map
packages/server/src/routes/index/profile.d.ts
packages/server/src/routes/index/profile.js
packages/server/src/routes/index/profile.js.map
packages/server/src/routes/index/user.d.ts
packages/server/src/routes/index/user.js
packages/server/src/routes/index/user.js.map
packages/server/src/routes/index/users.d.ts
packages/server/src/routes/index/users.js
packages/server/src/routes/index/users.js.map
packages/server/src/routes/oauth2/authorize.d.ts
packages/server/src/routes/oauth2/authorize.js
packages/server/src/routes/oauth2/authorize.js.map
packages/server/src/routes/routes.d.ts
packages/server/src/routes/routes.js
packages/server/src/routes/routes.js.map
packages/server/src/services/MustacheService.d.ts
packages/server/src/services/MustacheService.js
packages/server/src/services/MustacheService.js.map
packages/server/src/tools/db-migrate.d.ts
packages/server/src/tools/db-migrate.js
packages/server/src/tools/db-migrate.js.map
packages/server/src/tools/dbTools.d.ts
packages/server/src/tools/dbTools.js
packages/server/src/tools/dbTools.js.map
packages/server/src/tools/generate-types.d.ts
packages/server/src/tools/generate-types.js
packages/server/src/tools/generate-types.js.map
packages/server/src/utils/TransactionHandler.d.ts
packages/server/src/utils/TransactionHandler.js
packages/server/src/utils/TransactionHandler.js.map
packages/server/src/utils/auth.d.ts
packages/server/src/utils/auth.js
packages/server/src/utils/auth.js.map
packages/server/src/utils/base64.d.ts
packages/server/src/utils/base64.js
packages/server/src/utils/base64.js.map
packages/server/src/utils/cache.d.ts
packages/server/src/utils/cache.js
packages/server/src/utils/cache.js.map
packages/server/src/utils/defaultView.d.ts
packages/server/src/utils/defaultView.js
packages/server/src/utils/defaultView.js.map
packages/server/src/utils/errors.d.ts
packages/server/src/utils/errors.js
packages/server/src/utils/errors.js.map
packages/server/src/utils/htmlUtils.d.ts
packages/server/src/utils/htmlUtils.js
packages/server/src/utils/htmlUtils.js.map
packages/server/src/utils/koaIf.d.ts
packages/server/src/utils/koaIf.js
packages/server/src/utils/koaIf.js.map
packages/server/src/utils/requestUtils.d.ts
packages/server/src/utils/requestUtils.js
packages/server/src/utils/requestUtils.js.map
packages/server/src/utils/routeUtils.d.ts
packages/server/src/utils/routeUtils.js
packages/server/src/utils/routeUtils.js.map
packages/server/src/utils/routeUtils.test.d.ts
packages/server/src/utils/routeUtils.test.js
packages/server/src/utils/routeUtils.test.js.map
packages/server/src/utils/testUtils.d.ts
packages/server/src/utils/testUtils.js
packages/server/src/utils/testUtils.js.map
packages/server/src/utils/testing/testRouters.d.ts
packages/server/src/utils/testing/testRouters.js
packages/server/src/utils/testing/testRouters.js.map
packages/server/src/utils/time.d.ts
packages/server/src/utils/time.js
packages/server/src/utils/time.js.map
packages/server/src/utils/types.d.ts
packages/server/src/utils/types.js
packages/server/src/utils/types.js.map
packages/server/src/utils/urlUtils.d.ts
packages/server/src/utils/urlUtils.js
packages/server/src/utils/urlUtils.js.map
packages/server/src/utils/uuidgen.d.ts
packages/server/src/utils/uuidgen.js
packages/server/src/utils/uuidgen.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

Binary file not shown.

3
Dockerfile.db Normal file
View File

@@ -0,0 +1,3 @@
FROM postgres:13.1
EXPOSE 5432

71
Dockerfile.server Normal file
View File

@@ -0,0 +1,71 @@
# https://versatile.nl/blog/deploying-lerna-web-apps-with-docker
FROM node:12
RUN apt-get update
RUN apt-get --yes install vim
ARG user=joplin
RUN useradd --create-home --shell /bin/bash $user
USER $user
ENV NODE_ENV development
WORKDIR /home/$user
RUN mkdir /home/$user/logs
# To take advantage of the Docker cache, we first copy all the package.json
# and package-lock.json files, as they rarely change? and then bootstrap
# all the packages.
#
# Note that bootstrapping the packages will run all the postinstall
# scripts, which means that for packages that have such scripts, we need to
# copy all the files.
#
# We can't run boostrap with "--ignore-scripts" because that would
# prevent certain sub-packages, such as sqlite3, from being built
COPY --chown=$user:$user package*.json ./
# Install the root scripts but don't run postinstall (which would bootstrap
# and build TypeScript files, but we don't have the TypeScript files at
# this point)
RUN npm install --ignore-scripts
COPY --chown=$user:$user packages/fork-sax/package*.json ./packages/fork-sax/
COPY --chown=$user:$user packages/lib/package*.json ./packages/lib/
COPY --chown=$user:$user packages/renderer/package*.json ./packages/renderer/
COPY --chown=$user:$user packages/tools/package*.json ./packages/tools/
COPY --chown=$user:$user packages/server/package*.json ./packages/server/
COPY --chown=$user:$user lerna.json .
COPY --chown=$user:$user tsconfig.json .
# The following have postinstall scripts so we need to copy all the files.
# Since they should rarely change this is not an issue
COPY --chown=$user:$user packages/turndown ./packages/turndown
COPY --chown=$user:$user packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm
COPY --chown=$user:$user packages/fork-htmlparser2 ./packages/fork-htmlparser2
RUN ls -la /home/$user
# Then bootstrap only, without compiling the TypeScript files
RUN npm run bootstrap
COPY --chown=$user:$user packages/fork-sax ./packages/fork-sax
COPY --chown=$user:$user packages/lib ./packages/lib
COPY --chown=$user:$user packages/renderer ./packages/renderer
COPY --chown=$user:$user packages/tools ./packages/tools
COPY --chown=$user:$user packages/server ./packages/server
# Finally build everything, in particular the TypeScript files.
RUN npm run build
EXPOSE ${JOPLIN_PORT}
CMD [ "npm", "--prefix", "packages/server", "start" ]

View File

@@ -20,9 +20,9 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
Operating System | Download | Alternative
---|---|---
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/Joplin-Setup-1.4.19.exe'><img alt='Get it on Windows' width="134px" src='https://joplinapp.org/images/BadgeWindows.png'/></a> | Or get the <a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/JoplinPortable.exe'>Portable version</a><br><br>The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/Joplin-1.4.19.dmg'><img alt='Get it on macOS' width="134px" src='https://joplinapp.org/images/BadgeMacOS.png'/></a> | -
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/Joplin-1.4.19.AppImage'><img alt='Get it on Linux' width="134px" src='https://joplinapp.org/images/BadgeLinux.png'/></a> | The recommended way is to use the following installation script as it will handle the desktop icon too:<br><br> `wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh \| bash`
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/Joplin-Setup-1.5.11.exe'><img alt='Get it on Windows' width="134px" src='https://joplinapp.org/images/BadgeWindows.png'/></a> | Or get the <a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/JoplinPortable.exe'>Portable version</a><br><br>The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/Joplin-1.5.11.dmg'><img alt='Get it on macOS' width="134px" src='https://joplinapp.org/images/BadgeMacOS.png'/></a> | -
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/Joplin-1.5.11.AppImage'><img alt='Get it on Linux' width="134px" src='https://joplinapp.org/images/BadgeLinux.png'/></a> | The recommended way is to use the following installation script as it will handle the desktop icon too:<br><br> `wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh \| bash`
## Mobile applications
@@ -109,6 +109,8 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
- [Search Sorting spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/search_sorting.md)
- [Server: File URL Format](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_file_url_format.md)
- [Server: Delta Sync](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_delta_sync.md)
- Google Summer of Code 2020
@@ -436,45 +438,45 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [أحمد باشا إبراهيم](mailto:fi_ahmed_bacha@esi.dz) | 77%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 33%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 79%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 64%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 92%
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [أحمد باشا إبراهيم](mailto:fi_ahmed_bacha@esi.dz) | 75%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 32%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 77%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 62%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 89%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Hrvoje Mandić](mailto:trbuhom@net.hr) | 26%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 96%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 79%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 63%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 93%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 77%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Ettore Atalan](mailto:atalanttore@users.noreply.github.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 61%
![](https://joplinapp.org/images/flags/country-4x3/gb.png) | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/us.png) | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 96%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 36%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | | 97%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 96%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 42%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 88%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 33%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 85%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 79%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | | 95%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 93%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [João Duarte](mailto:jduar@protonmail.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 74%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | | 41%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 68%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 50%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 82%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 92%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 92%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 69%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 91%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 97%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 99%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 35%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | | 94%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 40%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Fathy AR](mailto:16875937+fathyar@users.noreply.github.com) | 85%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 32%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 93%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 82%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 77%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | | 92%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 91%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [João Duarte](mailto:jduar@protonmail.com) | 92%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 72%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | | 40%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 66%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 49%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 99%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 67%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [WhiredPlanck](mailto:fungdaat31@outlook.com) | 99%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -0,0 +1,28 @@
# For development, the easiest might be to only start the Postgres container and
# run the app directly with `npm start`. Or use sqlite3.
version: '3'
services:
# app:
# build:
# context: .
# dockerfile: Dockerfile.server-dev
# ports:
# - "22300:22300"
# # volumes:
# # - ./packages/server/:/var/www/joplin/packages/server/
# # - /var/www/joplin/packages/server/node_modules/
db:
build:
context: .
dockerfile: Dockerfile.db
ports:
- "5432:5432"
environment:
# TODO: Considering the database is only exposed to the
# application, and not to the outside world, is there a need to
# pick a secure password?
- POSTGRES_PASSWORD=joplin
- POSTGRES_USER=joplin
- POSTGRES_DB=joplin

40
docker-compose.server.yml Normal file
View File

@@ -0,0 +1,40 @@
version: '3'
services:
app:
environment:
- JOPLIN_BASE_URL=${JOPLIN_BASE_URL}
- JOPLIN_PORT=${JOPLIN_PORT}
restart: unless-stopped
build:
context: .
dockerfile: Dockerfile.server
ports:
- "${JOPLIN_PORT}:${JOPLIN_PORT}"
# volumes:
# # Mount the server directory so that it's possible to edit file
# # while the container is running. However don't mount the
# # node_modules directory which will be specific to the Docker
# # image (eg native modules will be built for Ubuntu, while the
# # container might be running in Windows)
# # https://stackoverflow.com/a/37898591/561309
# - ./packages/server:/home/joplin/packages/server
# - /home/joplin/packages/server/node_modules/
db:
restart: unless-stopped
# By default, the Postgres image saves the data to a Docker volume,
# so it persists whenever the server is restarted using
# `docker-compose up`. Note that it would however be deleted when
# running `docker-compose down`.
build:
context: .
dockerfile: Dockerfile.db
ports:
- "5432:5432"
environment:
# TODO: Considering the database is only exposed to the
# application, and not to the outside world, is there a need to
# pick a secure password?
- POSTGRES_PASSWORD=joplin
- POSTGRES_USER=joplin
- POSTGRES_DB=joplin

File diff suppressed because one or more lines are too long

View File

@@ -150,13 +150,21 @@
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Registers a new content script. Unlike regular plugin code, which runs in a separate process, content scripts run within the main process code
and thus allow improved performances and more customisations in specific cases. It can be used for example to load a Markdown or editor plugin.</p>
<p>Registers a new content script. Unlike regular plugin code, which
runs in a separate process, content scripts run within the main
process code and thus allow improved performances and more
customisations in specific cases. It can be used for example to load
a Markdown or editor plugin.</p>
</div>
<p>Note that registering a content script in itself will do nothing - it will only be loaded in specific cases by the relevant app modules
(eg. the Markdown renderer or the code editor). So it is not a way to inject and run arbitrary code in the app, which for safety and performance reasons is not supported.</p>
<p><a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script">View the renderer demo plugin</a>
<a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script">View the editor demo plugin</a></p>
<p>Note that registering a content script in itself will do nothing -
it will only be loaded in specific cases by the relevant app modules
(eg. the Markdown renderer or the code editor). So it is not a way
to inject and run arbitrary code in the app, which for safety and
performance reasons is not supported.</p>
<ul>
<li><a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script">View the renderer demo plugin</a></li>
<li><a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script">View the editor demo plugin</a></li>
</ul>
</div>
<h4 class="tsd-parameters-title">Parameters</h4>
<ul class="tsd-parameters">

View File

@@ -124,7 +124,7 @@
<p>Gets a global setting value, including app-specific settings and those set by other plugins.</p>
</div>
<p>The list of available settings is not documented yet, but can be found by looking at the source code:</p>
<p><a href="https://github.com/laurent22/joplin/blob/3539a452a359162c461d2849829d2d42973eab50/packages/app-mobile/lib/models/Setting.ts#L142">https://github.com/laurent22/joplin/blob/3539a452a359162c461d2849829d2d42973eab50/packages/app-mobile/lib/models/Setting.ts#L142</a></p>
<p><a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142">https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142</a></p>
</div>
<h4 class="tsd-parameters-title">Parameters</h4>
<ul class="tsd-parameters">

View File

@@ -88,7 +88,8 @@
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Registers a new CodeMirror plugin, which should follow the template below.</p>
<p>Registers a new CodeMirror plugin, which should follow the template
below.</p>
</div>
<pre><code class="language-javascript"><span class="hljs-built_in">module</span>.exports = {
<span class="hljs-attr">default</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">context</span>) </span>{
@@ -98,8 +99,8 @@
},
<span class="hljs-attr">codeMirrorResources</span>: [],
<span class="hljs-attr">codeMirrorOptions</span>: {
<span class="hljs-comment">// ...</span>
},
<span class="hljs-comment">// ...</span>
},
<span class="hljs-attr">assets</span>: {
<span class="hljs-comment">// ...</span>
},
@@ -107,19 +108,42 @@
}
}</code></pre>
<ul>
<li><p>The <code>context</code> argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate.</p>
<li><p>The <code>context</code> argument is currently unused but could be used later
on to provide access to your own plugin so that the content script
and plugin can communicate.</p>
</li>
<li><p>The <code>plugin</code> key is your CodeMirror plugin. This is where you can register new commands with CodeMirror or interact with the CodeMirror instance as needed.</p>
<li><p>The <code>plugin</code> key is your CodeMirror plugin. This is where you can
register new commands with CodeMirror or interact with the
CodeMirror instance as needed.</p>
</li>
<li><p>The <code>codeMirrorResources</code> key is an array of CodeMirror resources that will be loaded and attached to the CodeMirror module. These are made up of addons, keymaps, and modes. For example, for a plugin that want&#39;s to enable clojure highlighting in code blocks. <code>codeMirrorResources</code> would be set to <code>[&#39;mode/clojure/clojure&#39;]</code>.</p>
<li><p>The <code>codeMirrorResources</code> key is an array of CodeMirror resources
that will be loaded and attached to the CodeMirror module. These
are made up of addons, keymaps, and modes. For example, for a
plugin that want&#39;s to enable clojure highlighting in code blocks.
<code>codeMirrorResources</code> would be set to <code>[&#39;mode/clojure/clojure&#39;]</code>.</p>
</li>
<li><p>The <code>codeMirrorOptions</code> key contains all the <a href="https://codemirror.net/doc/manual.html#config">CodeMirror</a> options that will be set or changed by this plugin. New options can alse be declared via <a href="https://codemirror.net/doc/manual.html#defineOption"><code>CodeMirror.defineOption</code></a>, and then have their value set here. For example, a plugin that enables line numbers would set <code>codeMirrorOptions</code> to <code>{&#39;lineNumbers&#39;: true}</code>.</p>
<li><p>The <code>codeMirrorOptions</code> key contains all the
<a href="https://codemirror.net/doc/manual.html#config">CodeMirror</a>
options that will be set or changed by this plugin. New options
can alse be declared via
<a href="https://codemirror.net/doc/manual.html#defineOption"><code>CodeMirror.defineOption</code></a>,
and then have their value set here. For example, a plugin that
enables line numbers would set <code>codeMirrorOptions</code> to
<code>{&#39;lineNumbers&#39;: true}</code>.</p>
</li>
<li><p>Using the <strong>optional</strong> <code>assets</code> key you may specify <strong>only</strong> CSS assets that should be loaded in the rendered HTML document. Check for example the Joplin <a href="https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/joplin-renderer/MdToHtml/rules/mermaid.ts">Mermaid plugin</a> to see how the data should be structured.</p>
<li><p>Using the <strong>optional</strong> <code>assets</code> key you may specify <strong>only</strong> CSS
assets that should be loaded in the rendered HTML document. Check
for example the Joplin [Mermaid
plugin](<a href="https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts">https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts</a>)
to see how the data should be structured.</p>
</li>
</ul>
<p>One of the <code>plugin</code>, <code>codeMirrorResources</code>, or <code>codeMirrorOptions</code> keys must be provided for the plugin to be valid. Having multiple or all provided is also okay.</p>
<p>See the <a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script">demo plugin</a> for an example of all these keys being used in one plugin.</p>
<p>One of the <code>plugin</code>, <code>codeMirrorResources</code>, or <code>codeMirrorOptions</code>
keys must be provided for the plugin to be valid. Having multiple or
all provided is also okay.</p>
<p>See the <a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script">demo
plugin</a>
for an example of all these keys being used in one plugin.</p>
</div>
</section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
@@ -130,7 +154,8 @@
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Registers a new Markdown-It plugin, which should follow the template below.</p>
<p>Registers a new Markdown-It plugin, which should follow the template
below.</p>
</div>
<pre><code class="language-javascript"><span class="hljs-built_in">module</span>.exports = {
<span class="hljs-attr">default</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">context</span>) </span>{
@@ -144,15 +169,50 @@
}
}
}</code></pre>
<p>See <a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script">the
demo</a>
for a simple Markdown-it plugin example.</p>
<a href="#exported-members" id="exported-members" style="color: inherit; text-decoration: none;">
<h2>Exported members</h2>
</a>
<ul>
<li><p>The <code>context</code> argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate.</p>
<li><p>The <code>context</code> argument is currently unused but could be used later
on to provide access to your own plugin so that the content script
and plugin can communicate.</p>
</li>
<li><p>The <strong>required</strong> <code>plugin</code> key is the actual Markdown-It plugin - check the <a href="https://github.com/markdown-it/markdown-it">official doc</a> for more information. The <code>options</code> parameter is of type <a href="https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/joplin-renderer/MdToHtml.ts">RuleOptions</a>, which contains a number of options, mostly useful for Joplin&#39;s internal code.</p>
<li><p>The <strong>required</strong> <code>plugin</code> key is the actual Markdown-It plugin -
check the [official
doc](<a href="https://github.com/markdown-it/markdown-it">https://github.com/markdown-it/markdown-it</a>) for more
information. The <code>options</code> parameter is of type
<a href="https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml.ts">RuleOptions</a>,
which contains a number of options, mostly useful for Joplin&#39;s
internal code.</p>
</li>
<li><p>Using the <strong>optional</strong> <code>assets</code> key you may specify assets such as JS or CSS that should be loaded in the rendered HTML document. Check for example the Joplin <a href="https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/joplin-renderer/MdToHtml/rules/mermaid.ts">Mermaid plugin</a> to see how the data should be structured.</p>
<li><p>Using the <strong>optional</strong> <code>assets</code> key you may specify assets such as
JS or CSS that should be loaded in the rendered HTML document.
Check for example the Joplin [Mermaid
plugin](<a href="https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts">https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts</a>)
to see how the data should be structured.</p>
</li>
</ul>
<p>To include a regular Markdown-It plugin, that doesn&#39;t make use of any Joplin-specific features, you would simply create a file such as this:</p>
<a href="#passing-messages-from-the-content-script-to-your-plugin" id="passing-messages-from-the-content-script-to-your-plugin" style="color: inherit; text-decoration: none;">
<h2>Passing messages from the content script to your plugin</h2>
</a>
<p>The application provides the following function to allow executing
commands from the rendered HTML code:</p>
<p><code>webviewApi.executeCommand(commandName, ...args)</code></p>
<p>So you can use this mechanism to pass messages from the note viewer
to your own plugin. To do so you would define a command, using
<code>joplin.commands.register</code>, then you would call this command using
the <code>webviewApi</code> object. See again <a href="https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script">the
demo</a>
to see how this can be done.</p>
<a href="#registering-an-existing-markdown-it-plugin" id="registering-an-existing-markdown-it-plugin" style="color: inherit; text-decoration: none;">
<h2>Registering an existing Markdown-it plugin</h2>
</a>
<p>To include a regular Markdown-It plugin, that doesn&#39;t make use of
any Joplin-specific features, you would simply create a file such as
this:</p>
<pre><code class="language-javascript"><span class="hljs-built_in">module</span>.exports = {
<span class="hljs-attr">default</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">context</span>) </span>{
<span class="hljs-keyword">return</span> {

View File

@@ -75,9 +75,11 @@
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#edit" class="tsd-kind-icon">Edit</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#editorcontextmenu" class="tsd-kind-icon">Editor<wbr>Context<wbr>Menu</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#file" class="tsd-kind-icon">File</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#foldercontextmenu" class="tsd-kind-icon">Folder<wbr>Context<wbr>Menu</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#help" class="tsd-kind-icon">Help</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#note" class="tsd-kind-icon">Note</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#notelistcontextmenu" class="tsd-kind-icon">Note<wbr>List<wbr>Context<wbr>Menu</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#tagcontextmenu" class="tsd-kind-icon">Tag<wbr>Context<wbr>Menu</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#tools" class="tsd-kind-icon">Tools</a></li>
<li class="tsd-kind-enum-member tsd-parent-kind-enum"><a href="menuitemlocation.html#view" class="tsd-kind-icon">View</a></li>
</ul>
@@ -122,6 +124,22 @@
<aside class="tsd-sources">
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="foldercontextmenu" class="tsd-anchor"></a>
<h3>Folder<wbr>Context<wbr>Menu</h3>
<div class="tsd-signature tsd-kind-icon">Folder<wbr>Context<wbr>Menu<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;folderContextMenu&quot;</span></div>
<aside class="tsd-sources">
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>When a command is called from a folder context menu, the
command will receive the following arguments:</p>
</div>
<ul>
<li><code>folderId:string</code>: ID of the folder that was right-clicked on</li>
</ul>
</div>
</section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="help" class="tsd-anchor"></a>
<h3>Help</h3>
@@ -142,6 +160,31 @@
<div class="tsd-signature tsd-kind-icon">Note<wbr>List<wbr>Context<wbr>Menu<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;noteListContextMenu&quot;</span></div>
<aside class="tsd-sources">
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>When a command is called from the note list context menu, the
command will receive the following arguments:</p>
</div>
<ul>
<li><code>noteIds:string[]</code>: IDs of the notes that were right-clicked on.</li>
</ul>
</div>
</section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="tagcontextmenu" class="tsd-anchor"></a>
<h3>Tag<wbr>Context<wbr>Menu</h3>
<div class="tsd-signature tsd-kind-icon">Tag<wbr>Context<wbr>Menu<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol"> = &quot;tagContextMenu&quot;</span></div>
<aside class="tsd-sources">
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>When a command is called from a tag context menu, the
command will receive the following arguments:</p>
</div>
<ul>
<li><code>tagId:string</code>: ID of the tag that was right-clicked on</li>
</ul>
</div>
</section>
<section class="tsd-panel tsd-member tsd-kind-enum-member tsd-parent-kind-enum">
<a name="tools" class="tsd-anchor"></a>
@@ -188,6 +231,9 @@
<li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="menuitemlocation.html#file" class="tsd-kind-icon">File</a>
</li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="menuitemlocation.html#foldercontextmenu" class="tsd-kind-icon">Folder<wbr>Context<wbr>Menu</a>
</li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="menuitemlocation.html#help" class="tsd-kind-icon">Help</a>
</li>
@@ -197,6 +243,9 @@
<li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="menuitemlocation.html#notelistcontextmenu" class="tsd-kind-icon">Note<wbr>List<wbr>Context<wbr>Menu</a>
</li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="menuitemlocation.html#tagcontextmenu" class="tsd-kind-icon">Tag<wbr>Context<wbr>Menu</a>
</li>
<li class=" tsd-kind-enum-member tsd-parent-kind-enum">
<a href="menuitemlocation.html#tools" class="tsd-kind-icon">Tools</a>
</li>

View File

@@ -133,6 +133,12 @@
<li class="tsd-kind-variable"><a href="globals.html#logger" class="tsd-kind-icon">logger</a></li>
</ul>
</section>
<section class="tsd-index-section ">
<h3>Functions</h3>
<ul class="tsd-index-list">
<li class="tsd-kind-function"><a href="globals.html#iscontextmenuitemlocation" class="tsd-kind-icon">is<wbr>Context<wbr>Menu<wbr>Item<wbr>Location</a></li>
</ul>
</section>
</div>
</section>
</section>
@@ -187,9 +193,11 @@
<div class="lead">
<p>An array of at least one element and at most three elements.</p>
</div>
<p>[0]: Resource name (eg. &quot;notes&quot;, &quot;folders&quot;, &quot;tags&quot;, etc.)
[1]: (Optional) Resource ID.
[2]: (Optional) Resource link.</p>
<ul>
<li><strong>[0]</strong>: Resource name (eg. &quot;notes&quot;, &quot;folders&quot;, &quot;tags&quot;, etc.)</li>
<li><strong>[1]</strong>: (Optional) Resource ID.</li>
<li><strong>[2]</strong>: (Optional) Resource link.</li>
</ul>
</div>
</section>
<section class="tsd-panel tsd-member tsd-kind-type-alias">
@@ -242,6 +250,33 @@
</aside>
</section>
</section>
<section class="tsd-panel-group tsd-member-group ">
<h2>Functions</h2>
<section class="tsd-panel tsd-member tsd-kind-function">
<a name="iscontextmenuitemlocation" class="tsd-anchor"></a>
<h3>is<wbr>Context<wbr>Menu<wbr>Item<wbr>Location</h3>
<ul class="tsd-signatures tsd-kind-function">
<li class="tsd-signature tsd-kind-icon">is<wbr>Context<wbr>Menu<wbr>Item<wbr>Location<span class="tsd-signature-symbol">(</span>location<span class="tsd-signature-symbol">: </span><a href="enums/menuitemlocation.html" class="tsd-signature-type">MenuItemLocation</a><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">boolean</span></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
<aside class="tsd-sources">
</aside>
<h4 class="tsd-parameters-title">Parameters</h4>
<ul class="tsd-parameters">
<li>
<h5>location: <a href="enums/menuitemlocation.html" class="tsd-signature-type">MenuItemLocation</a></h5>
</li>
</ul>
<!-- JOPLINCHANGE
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">boolean</span></h4>
-->
</li>
</ul>
</section>
</section>
</div>
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
<!--
@@ -381,6 +416,9 @@
<li class=" tsd-kind-type-alias">
<a href="globals.html#viewhandle" class="tsd-kind-icon">ViewHandle</a>
</li>
<li class=" tsd-kind-function">
<a href="globals.html#iscontextmenuitemlocation" class="tsd-kind-icon">isContextMenuItemLocation</a>
</li>
</ul>
</nav>
</div>

View File

@@ -206,6 +206,9 @@
<li class=" tsd-kind-type-alias">
<a href="globals.html#viewhandle" class="tsd-kind-icon">ViewHandle</a>
</li>
<li class=" tsd-kind-function">
<a href="globals.html#iscontextmenuitemlocation" class="tsd-kind-icon">isContextMenuItemLocation</a>
</li>
</ul>
</nav>
</div>

View File

@@ -142,7 +142,7 @@
<td>&quot;oneNoteSelected &amp;&amp; !inConflictFolder&quot;</td>
</tr>
</tbody></table>
<p>Currently the supported context variables aren&#39;t documented, but you can <a href="https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/lib/services/commands/stateToWhenClauseContext.ts">find the list here</a>.</p>
<p>Currently the supported context variables aren&#39;t documented, but you can <a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">find the list here</a>.</p>
<p>Note: Commands are enabled by default unless you use this property.</p>
</div>
</section>

View File

@@ -400,6 +400,46 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog.md
<div class="main">
<h1>Joplin changelog<a name="joplin-changelog" href="#joplin-changelog" class="heading-anchor">🔗</a></h1>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.11">v1.5.11</a> - 2020-12-27T19:54:07Z<a name="v1-5-11-https-github-com-laurent22-joplin-releases-tag-v1-5-11-2020-12-27t19-54-07z" href="#v1-5-11-https-github-com-laurent22-joplin-releases-tag-v1-5-11-2020-12-27t19-54-07z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add support for media players (video, audio and PDF)</li>
<li>New: Add table captions when importing ENEX files</li>
<li>New: Added doc about Rich Text editor and added way to dismiss warning banner</li>
<li>New: MacOS: Notarize application</li>
<li>New: Plugins: Add support for content script asset files, for Markdown-it plugins</li>
<li>New: Plugins: Add support for context menu items on notebooks and tags</li>
<li>New: Plugins: Add support for workspace.onSyncStart event</li>
<li>New: Plugins: Added a way to execute commands from Markdown-it content scripts</li>
<li>Fixed: Fix End key behavior with Codemirror spellcheck (<a href="https://github.com/laurent22/joplin/issues/4215">#4215</a> by Caleb John)</li>
<li>Fixed: Fixed basic search when executing a query in Chinese (<a href="https://github.com/laurent22/joplin/issues/4034">#4034</a> by Naveen M V)</li>
<li>Fixed: Fixed context menu when the UI is zoomed in or out (<a href="https://github.com/laurent22/joplin/issues/4201">#4201</a>)</li>
<li>Fixed: Fixed importing certain code blocks from ENEX</li>
<li>Fixed: Fixed importing ENEX files that contain empty resources</li>
<li>Fixed: Fixed importing ENEX files that contain resources with invalid mime type</li>
<li>Fixed: Fixed issue when searching for text that contains diacritic (<a href="https://github.com/laurent22/joplin/issues/4152">#4152</a>) (<a href="https://github.com/laurent22/joplin/issues/4025">#4025</a> by Roman Musin)</li>
<li>Fixed: Fixed issue with attachment paths being invalid when user has spaces in home directory path.</li>
<li>Fixed: Fixed issue with note not being saved when a column is added or remove from Rich Text editor</li>
<li>Fixed: Fixed issues when importing hidden tables within hidden sections in Enex files</li>
<li>Fixed: Fixed numbered list bug in markdown editor (<a href="https://github.com/laurent22/joplin/issues/4116">#4116</a>) (<a href="https://github.com/laurent22/joplin/issues/3917">#3917</a> by <a href="https://github.com/MichBoi">@MichBoi</a>)</li>
<li>Fixed: Fixed potential crash when watching note files or resources</li>
<li>Fixed: Fixed title input field width on small windows</li>
<li>Fixed: Focus editor after pressing toolbar buttons (<a href="https://github.com/laurent22/joplin/issues/4037">#4037</a>) (<a href="https://github.com/laurent22/joplin/issues/4036">#4036</a> by <a href="https://github.com/CalebJohn">@CalebJohn</a>)</li>
<li>Fixed: Plugins: Fixed disabling plugin files that start with &quot;_&quot;</li>
<li>Fixed: Prevent double paste when using Shift+Ctrl+V (<a href="https://github.com/laurent22/joplin/issues/4243">#4243</a>)</li>
<li>Fixed: Prevents crash when invalid spell checker language is selected, and provide fallback for invalid language codes (<a href="https://github.com/laurent22/joplin/issues/4146">#4146</a>)</li>
<li>Fixed: Register Markdown editor commands with the Keyboard Shortcut editor (<a href="https://github.com/laurent22/joplin/issues/4136">#4136</a>) (<a href="https://github.com/laurent22/joplin/issues/4130">#4130</a> by Caleb John)</li>
<li>Improved: Display Katex parsing errors</li>
<li>Improved: Improved warning banner colors</li>
<li>Improved: Plugins: Commands would not show up in keymap editor when no shortcut was associated with them</li>
<li>Improved: Plugins: Improved note change event handling.</li>
<li>Improved: Removed warning for Markdown editor spell checking</li>
<li>Improved: Restrict auto-detection of links, and added option to toggle linkify (<a href="https://github.com/laurent22/joplin/issues/4205">#4205</a>)</li>
<li>Improved: Rich Text: Do not converts to markdown links URLs that would be linkified</li>
<li>Improved: Translation: Update zh_CN (<a href="https://github.com/laurent22/joplin/issues/4195">#4195</a> by Zhang YANG)</li>
<li>Improved: Update macOS icon for macOS Big Sur</li>
<li>Improved: Update Mermaid: 8.8.1 -&gt; 8.8.4 (<a href="https://github.com/laurent22/joplin/issues/4193">#4193</a> by Helmut K. C. Tessarek)</li>
<li>Improved: Use plugins whenever printing or exporting notes</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.19">v1.4.19</a> - 2020-12-01T11:11:16Z<a name="v1-4-19-https-github-com-laurent22-joplin-releases-tag-v1-4-19-2020-12-01t11-11-16z" href="#v1-4-19-https-github-com-laurent22-joplin-releases-tag-v1-4-19-2020-12-01t11-11-16z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Improved: Disable soft-break by default in Markdown rendering</li>

View File

@@ -400,6 +400,22 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog_cli.md
<div class="main">
<h1>Joplin terminal app changelog<a name="joplin-terminal-app-changelog" href="#joplin-terminal-app-changelog" class="heading-anchor">🔗</a></h1>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/cli-v1.5.1">cli-v1.5.1</a> - 2020-12-26T00:46:31Z<a name="cli-v1-5-1-https-github-com-laurent22-joplin-releases-tag-cli-v1-5-1-2020-12-26t00-46-31z" href="#cli-v1-5-1-https-github-com-laurent22-joplin-releases-tag-cli-v1-5-1-2020-12-26t00-46-31z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add table captions when importing ENEX files</li>
<li>Improved: Allow exporting conflict notes (#4095)</li>
<li>Improved: Allow lowercase filters when doing search</li>
<li>Improved: Improved error handling when importing ENEX files</li>
<li>Improved: Partially reverts #3975 (link rendering)</li>
<li>Fixed: Fix sorting by title in a case insensitive way</li>
<li>Fixed: Fixed basic search when executing a query in Chinese (#4034 by Naveen M V)</li>
<li>Fixed: Fixed importing ENEX files that contain empty resources</li>
<li>Fixed: Fixed importing ENEX files that contain resources with invalid mime type</li>
<li>Fixed: Fixed importing certain ENEX files that contain invalid dates</li>
<li>Fixed: Fixed importing certain code blocks from ENEX</li>
<li>Fixed: Fixed issue when searching for text that contains diacritic (#4152) (#4025 by Roman Musin)</li>
<li>Fixed: Fixed issues when importing hidden tables within hidden sections in Enex files</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/cli-v1.4.9">cli-v1.4.9</a> - 2020-11-26T15:00:37Z<a name="cli-v1-4-9-https-github-com-laurent22-joplin-releases-tag-cli-v1-4-9-2020-11-26t15-00-37z" href="#cli-v1-4-9-https-github-com-laurent22-joplin-releases-tag-cli-v1-4-9-2020-11-26t15-00-37z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Improved: Allow exporting conflict notes (#4095)</li>

View File

@@ -411,6 +411,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/faq.md
</ul>
<p>Now try to install again and it should work.</p>
<p>More info there: <a href="https://github.com/electron-userland/electron-builder/issues/4057">https://github.com/electron-userland/electron-builder/issues/4057</a></p>
<h2>How can I pass arguments to the Linux installation script?<a name="how-can-i-pass-arguments-to-the-linux-installation-script" href="#how-can-i-pass-arguments-to-the-linux-installation-script" class="heading-anchor">🔗</a></h2>
<p>You can pass <a href="https://github.com/laurent22/joplin/blob/dev/Joplin_install_and_update.sh#L37">arguments</a> to the installation script by using this command.</p>
<p><code>wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh \| bash -s -- --argument1 --argument2</code></p>
<h2>How can I edit my note in an external text editor?<a name="how-can-i-edit-my-note-in-an-external-text-editor" href="#how-can-i-edit-my-note-in-an-external-text-editor" class="heading-anchor">🔗</a></h2>
<p>The editor command (may include arguments) defines which editor will be used to open a note. If none is provided it will try to auto-detect the default editor. If this does nothing or you want to change it for Joplin, you need to configure it in the Preferences -&gt; Text editor command.</p>
<p>Some example configurations are: (comments after #)</p>

View File

@@ -420,17 +420,17 @@ https://github.com/laurent22/joplin/blob/dev/README.md
<tbody>
<tr>
<td>Windows (32 and 64-bit)</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/Joplin-Setup-1.4.19.exe'><img alt='Get it on Windows' width="134px" src='https://joplinapp.org/images/BadgeWindows.png'/></a></td>
<td>Or get the <a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/JoplinPortable.exe'>Portable version</a><br><br>The <a href="https://en.wikipedia.org/wiki/Portable_application">portable application</a> allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called &quot;JoplinProfile&quot; next to the executable file.</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/Joplin-Setup-1.5.11.exe'><img alt='Get it on Windows' width="134px" src='https://joplinapp.org/images/BadgeWindows.png'/></a></td>
<td>Or get the <a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/JoplinPortable.exe'>Portable version</a><br><br>The <a href="https://en.wikipedia.org/wiki/Portable_application">portable application</a> allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called &quot;JoplinProfile&quot; next to the executable file.</td>
</tr>
<tr>
<td>macOS</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/Joplin-1.4.19.dmg'><img alt='Get it on macOS' width="134px" src='https://joplinapp.org/images/BadgeMacOS.png'/></a></td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/Joplin-1.5.11.dmg'><img alt='Get it on macOS' width="134px" src='https://joplinapp.org/images/BadgeMacOS.png'/></a></td>
<td>-</td>
</tr>
<tr>
<td>Linux</td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v1.4.19/Joplin-1.4.19.AppImage'><img alt='Get it on Linux' width="134px" src='https://joplinapp.org/images/BadgeLinux.png'/></a></td>
<td><a href='https://github.com/laurent22/joplin/releases/download/v1.5.11/Joplin-1.5.11.AppImage'><img alt='Get it on Linux' width="134px" src='https://joplinapp.org/images/BadgeLinux.png'/></a></td>
<td>The recommended way is to use the following installation script as it will handle the desktop icon too:<br><br> <code>wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh | bash</code></td>
</tr>
</tbody>

View File

@@ -400,6 +400,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/nextcloud_app.md
<div class="main">
<h1>Joplin Web API for Nextcloud<a name="joplin-web-api-for-nextcloud" href="#joplin-web-api-for-nextcloud" class="heading-anchor">🔗</a></h1>
<hr>
<p><strong>IMPORTANT: THIS APPLICATION IS DEPRECATED AND WILL NO LONGER BE SUPPORTED FROM JOPLIN 1.6.x</strong></p>
<p>It is deprecated in favour of <a href="https://discourse.joplinapp.org/t/joplin-web-api-for-nextcloud/4491/72?u=laurent">Joplin Server</a>, so if you are relying on it please do not upgrade to Joplin 1.6.x till you are ready to migrate to Joplin Server or some other alternative.</p>
<hr>
<p><strong>This is a beta feature, not yet completed. More info coming soon!</strong></p>
<p>The app can be downloaded from there: <a href="https://apps.nextcloud.com/apps/joplin">https://apps.nextcloud.com/apps/joplin</a></p>
<p>The Joplin Web API for Nextcloud is a helper application that enables certain features that are not possible otherwise. In particular:</p>

File diff suppressed because it is too large Load Diff

View File

@@ -116,6 +116,7 @@
"**/_vieux/": true,
"**/.DS_Store": true,
"**/*.base64": true,
"**/*~": true,
"**/*.bundle.js": true,
"**/*.eot": true,
"**/*.icns": true,
@@ -309,6 +310,10 @@
"packages/renderer/MdToHtml/rules/sanitize_html.js": true,
"packages/app-mobile/lib/rnInjectedJs/": true,
"packages/app-mobile/lib/sql-extensions/spellfix.so": true,
"packages/server/dist/": true,
"packages/server/db-*.sqlite": true,
"packages/server/test.pid": true,
"packages/server/temp": true,
"packages/generator-joplin/generators/app/templates/api/": true,
"packages/app-mobile/node_modules/": true,
"phpunit.xml": true,

146
package-lock.json generated
View File

@@ -289,6 +289,75 @@
"dedent": "^0.7.0",
"npmlog": "^4.1.2",
"yargs": "^14.2.2"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yargs": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
"integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^15.0.1"
}
},
"yargs-parser": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
"integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"@lerna/collect-uncommitted": {
@@ -2183,6 +2252,14 @@
"ssri": "^6.0.1",
"unique-filename": "^1.1.1",
"y18n": "^4.0.0"
},
"dependencies": {
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
}
}
},
"cache-base": {
@@ -10578,80 +10655,11 @@
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"yargs": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
"integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^15.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"yargs-parser": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
"integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}

View File

@@ -20,12 +20,16 @@
"buildTranslationsNoTsc": "node packages/tools/build-translation.js",
"buildWebsite": "npm run buildApiDoc && node ./packages/tools/build-website.js && npm run buildPluginDoc",
"clean": "lerna clean -y && lerna run clean",
"circularDependencyCheck": "npx madge --warning --circular --extensions js ./",
"generateDatabaseTypes": "node packages/tools/generate-database-types",
"linkChecker": "linkchecker https://joplinapp.org",
"linter-ci": "./node_modules/.bin/eslint --resolve-plugins-relative-to . --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"linter-precommit": "./node_modules/.bin/eslint --resolve-plugins-relative-to . --fix --ext .js --ext .jsx --ext .ts --ext .tsx",
"linter": "./node_modules/.bin/eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"postinstall": "lerna bootstrap --no-ci && npm run tsc",
"bootstrap": "lerna bootstrap --no-ci",
"bootstrapIgnoreScripts": "lerna bootstrap --ignore-scripts --no-ci",
"postinstall": "npm run bootstrap --no-ci && npm run build",
"build": "lerna run build && npm run tsc",
"publishAll": "git pull && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
"releaseAndroid": "node packages/tools/release-android.js",
"releaseCli": "node packages/tools/release-cli.js",
@@ -39,7 +43,8 @@
"updateIgnored": "gulp updateIgnoredTypeScriptBuild",
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
"watch": "lerna run watch --stream --parallel",
"i": "lerna add --no-bootstrap --scope"
"i": "lerna add --no-bootstrap --scope",
"server-start-dev": "docker-compose --file docker-compose.server-dev.yml up"
},
"husky": {
"hooks": {

View File

@@ -1,6 +1,7 @@
const gulp = require('gulp');
const fs = require('fs-extra');
const utils = require('@joplin/tools/gulp/utils');
const { setPackagePrivateField } = require('@joplin/tools/tool-utils');
const tasks = {
// compileExtensions: {
// fn: require('../Tools/gulp/tasks/compileExtensions.js'),
@@ -10,12 +11,12 @@ const tasks = {
// updateIgnoredTypeScriptBuild: require('../Tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
};
async function makePackagePublic(filePath) {
const text = await fs.readFile(filePath, 'utf8');
const obj = JSON.parse(text);
delete obj.private;
await fs.writeFile(filePath, JSON.stringify(obj), 'utf8');
}
// async function makePackagePublic(filePath) {
// const text = await fs.readFile(filePath, 'utf8');
// const obj = JSON.parse(text);
// delete obj.private;
// await fs.writeFile(filePath, JSON.stringify(obj), 'utf8');
// }
tasks.prepareBuild = {
fn: async () => {
@@ -25,7 +26,8 @@ tasks.prepareBuild = {
});
await utils.copyFile(`${__dirname}/package.json`, `${buildDir}/package.json`);
await makePackagePublic(`${buildDir}/package.json`);
// await makePackagePublic(`${buildDir}/package.json`);
await setPackagePrivateField(`${buildDir}/package.json`, false);
await utils.copyFile(`${__dirname}/package-lock.json`, `${buildDir}/package-lock.json`);
await utils.copyFile(`${__dirname}/gulpfile.js`, `${buildDir}/gulpfile.js`);

View File

@@ -31,7 +31,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.5.1",
"version": "1.6.0",
"bin": {
"joplin": "./main.js"
},

View File

@@ -1,7 +1,7 @@
import Setting from '@joplin/lib/models/Setting';
import { allNotesFolders, remoteNotesAndFolders, localNotesFoldersSameAsRemote } from './test-utils-synchronizer';
const { syncTargetName, synchronizerStart, setupDatabaseAndSynchronizer, synchronizer, sleep, switchClient, syncTargetId, fileApi } = require('./test-utils.js');
const { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSynchronizer, synchronizer, sleep, switchClient, syncTargetId, fileApi } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const BaseItem = require('@joplin/lib/models/BaseItem.js');
@@ -16,6 +16,10 @@ describe('Synchronizer.basics', function() {
done();
});
afterAll(async () => {
await afterAllCleanUp();
});
it('should create remote items', (async () => {
const folder = await Folder.save({ title: 'folder1' });
await Note.save({ title: 'un', parent_id: folder.id });
@@ -123,10 +127,6 @@ describe('Synchronizer.basics', function() {
}));
it('should delete local notes', (async () => {
// For these tests we pass the context around for each user. This is to make sure that the "deletedItemsProcessed"
// property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared
// it means items will no longer be deleted locally via sync.
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'un', parent_id: folder1.id });
const note2 = await Note.save({ title: 'deux', parent_id: folder1.id });

View File

@@ -0,0 +1 @@
<img src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='16px' height='16px' viewBox='0 0 24 24' data-evernote-id='0' class='js-evernote-checked'%3e%3cg transform='translate(0%2c 0)' data-evernote-id='18' class='js-evernote-checked'%3e%3cpolygon fill='none' stroke='%23343434' stroke-width='2' stroke-linecap='square' stroke-miterlimit='10' points='12%2c2.6 15%2c9 21.4%2c9 16.7%2c13.9 18.6%2c21.4 12%2c17.6 5.4%2c21.4 7.3%2c13.9 2.6%2c9 9%2c9 ' stroke-linejoin='miter' data-evernote-id='19' class='js-evernote-checked'%3e%3c/polygon%3e%3c/g%3e%3c/svg%3e"/>

View File

@@ -0,0 +1 @@
![](data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20x='0px'%20y='0px'%20width='16px'%20height='16px'%20viewBox='0%200%2024%2024'%20data-evernote-id='0'%20class='js-evernote-checked'%3e%3cg%20transform='translate%280%2c%200%29'%20data-evernote-id='18'%20class='js-evernote-checked'%3e%3cpolygon%20fill='none'%20stroke='%23343434'%20stroke-width='2'%20stroke-linecap='square'%20stroke-miterlimit='10'%20points='12%2c2.6%2015%2c9%2021.4%2c9%2016.7%2c13.9%2018.6%2c21.4%2012%2c17.6%205.4%2c21.4%207.3%2c13.9%202.6%2c9%209%2c9%20'%20stroke-linejoin='miter'%20data-evernote-id='19'%20class='js-evernote-checked'%3e%3c/polygon%3e%3c/g%3e%3c/svg%3e)

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -1,2 +1,3 @@
dist/*
node_modules/
*.jpl

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -20,4 +20,4 @@
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
}
}
}

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -1,4 +1,5 @@
import joplin from 'api';
import { ToolbarButtonLocation } from 'api/types';
const uslug = require('uslug');
@@ -91,6 +92,18 @@ joplin.plugins.register({
updateTocView();
});
await joplin.commands.register({
name: 'toggleToc',
label: 'Toggle TOC',
iconName: 'fas fa-drum',
execute: async () => {
const isVisible = await (panels as any).visible(view);
(panels as any).show(view, !isVisible);
},
});
await joplin.views.toolbarButtons.create('toggleToc', 'toggleToc', ToolbarButtonLocation.NoteToolbar);
updateTocView();
},
});

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -208,7 +208,7 @@ export enum MenuItemLocation {
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
@@ -223,7 +223,7 @@ export enum MenuItemLocation {
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
@@ -243,7 +243,7 @@ export enum MenuItemLocation {
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location:MenuItemLocation):boolean {
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,

View File

@@ -15,6 +15,7 @@ import KeychainServiceDriver from '@joplin/lib/services/keychain/KeychainService
import KeychainServiceDriverDummy from '@joplin/lib/services/keychain/KeychainServiceDriver.dummy';
import PluginRunner from '../app/services/plugins/PluginRunner';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import FileApiDriverJoplinServer from '@joplin/lib/file-api-driver-joplinServer';
const fs = require('fs-extra');
const { JoplinDatabase } = require('@joplin/lib/joplin-database.js');
@@ -43,12 +44,14 @@ const SyncTargetOneDrive = require('@joplin/lib/SyncTargetOneDrive.js');
const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js');
const SyncTargetDropbox = require('@joplin/lib/SyncTargetDropbox.js');
const SyncTargetAmazonS3 = require('@joplin/lib/SyncTargetAmazonS3.js');
const SyncTargetJoplinServer = require('@joplin/lib/SyncTargetJoplinServer').default;
const EncryptionService = require('@joplin/lib/services/EncryptionService.js');
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker.js');
const RevisionService = require('@joplin/lib/services/RevisionService.js');
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js');
const WebDavApi = require('@joplin/lib/WebDavApi');
const DropboxApi = require('@joplin/lib/DropboxApi');
const JoplinServerApi = require('@joplin/lib/JoplinServerApi2').default;
const { OneDriveApi } = require('@joplin/lib/onedrive-api');
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
const md5 = require('md5');
@@ -116,6 +119,7 @@ SyncTargetRegistry.addClass(SyncTargetOneDrive);
SyncTargetRegistry.addClass(SyncTargetNextcloud);
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
let syncTargetName_ = '';
let syncTargetId_: number = null;
@@ -132,7 +136,7 @@ function setSyncTargetName(name: string) {
syncTargetName_ = name;
syncTargetId_ = SyncTargetRegistry.nameToId(syncTargetName_);
sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;// 400;
isNetworkSyncTarget_ = ['nextcloud', 'dropbox', 'onedrive', 'amazon_s3'].includes(syncTargetName_);
isNetworkSyncTarget_ = ['nextcloud', 'dropbox', 'onedrive', 'amazon_s3', 'joplinServer'].includes(syncTargetName_);
synchronizers_ = [];
return previousName;
}
@@ -142,6 +146,7 @@ setSyncTargetName('memory');
// setSyncTargetName('dropbox');
// setSyncTargetName('onedrive');
// setSyncTargetName('amazon_s3');
// setSyncTargetName('joplinServer');
// console.info(`Testing with sync target: ${syncTargetName_}`);
@@ -214,6 +219,16 @@ async function afterEachCleanUp() {
KeymapService.destroyInstance();
}
async function afterAllCleanUp() {
if (fileApi()) {
try {
await fileApi().clearRoot();
} catch (error) {
console.warn('Could not clear sync target root:', error);
}
}
}
async function switchClient(id: number, options: any = null) {
options = Object.assign({}, { keychainEnabled: false }, options);
@@ -346,7 +361,7 @@ async function setupDatabaseAndSynchronizer(id: number, options: any = null) {
if (!synchronizers_[id]) {
const SyncTargetClass = SyncTargetRegistry.classById(syncTargetId_);
const syncTarget = new SyncTargetClass(db(id));
await initFileApi();
await initFileApi(suiteName_);
syncTarget.setFileApi(fileApi());
syncTarget.setLogger(logger);
synchronizers_[id] = await syncTarget.synchronizer();
@@ -361,6 +376,7 @@ async function setupDatabaseAndSynchronizer(id: number, options: any = null) {
resourceFetchers_[id] = new ResourceFetcher(() => { return synchronizers_[id].api(); });
kvStores_[id] = new KvStore();
await fileApi().initialize();
await fileApi().clearRoot();
}
@@ -440,7 +456,7 @@ async function loadEncryptionMasterKey(id: number = null, useExisting = false) {
return masterKey;
}
async function initFileApi() {
async function initFileApi(suiteName: string) {
if (fileApis_[syncTargetId_]) return;
let fileApi = null;
@@ -482,7 +498,6 @@ async function initFileApi() {
if (!process.argv.includes('--runInBand')) {
throw new Error('OneDrive tests must be run sequentially, with the --runInBand arg. eg `npm test -- --runInBand`');
}
const { parameters, setEnvOverride } = require('@joplin/lib/parameters.js');
@@ -506,6 +521,16 @@ async function initFileApi() {
if (!amazonS3Creds || !amazonS3Creds.accessKeyId) throw new Error(`AWS auth JSON missing in ${amazonS3CredsPath} format should be: { "accessKeyId": "", "secretAccessKey": "", "bucket": "mybucket"}`);
const api = new S3({ accessKeyId: amazonS3Creds.accessKeyId, secretAccessKey: amazonS3Creds.secretAccessKey, s3UseArnRegion: true });
fileApi = new FileApi('', new FileApiDriverAmazonS3(api, amazonS3Creds.bucket));
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('joplinServer')) {
// Note that to test the API in parallel mode, you need to use Postgres
// as database, as the SQLite database is not reliable when being
// read/write from multiple processes at the same time.
const api = new JoplinServerApi({
baseUrl: () => 'http://localhost:22300',
username: () => 'admin@localhost',
password: () => 'admin',
});
fileApi = new FileApi(`root:/Apps/Joplin-${suiteName}`, new FileApiDriverJoplinServer(api));
}
fileApi.setLogger(logger);
@@ -743,18 +768,18 @@ class TestApp extends BaseApplication {
private middlewareCalls_: any[];
private logger_: LoggerWrapper;
constructor(hasGui = true) {
public constructor(hasGui = true) {
super();
this.hasGui_ = hasGui;
this.middlewareCalls_ = [];
this.logger_ = super.logger();
}
hasGui() {
public hasGui() {
return this.hasGui_;
}
async start(argv: any[]) {
public async start(argv: any[]) {
this.logger_.info('Test app starting...');
if (!argv.includes('--profile')) {
@@ -775,7 +800,7 @@ class TestApp extends BaseApplication {
this.logger_.info('Test app started...');
}
async generalMiddleware(store: any, next: any, action: any) {
public async generalMiddleware(store: any, next: any, action: any) {
this.middlewareCalls_.push(true);
try {
await super.generalMiddleware(store, next, action);
@@ -784,7 +809,7 @@ class TestApp extends BaseApplication {
}
}
async wait() {
public async wait() {
return new Promise((resolve) => {
const iid = shim.setInterval(() => {
if (!this.middlewareCalls_.length) {
@@ -795,11 +820,11 @@ class TestApp extends BaseApplication {
});
}
async profileDir() {
public async profileDir() {
return Setting.value('profileDir');
}
async destroy() {
public async destroy() {
this.logger_.info('Test app stopping...');
await this.wait();
await ItemChange.waitForAllSaved();
@@ -809,4 +834,4 @@ class TestApp extends BaseApplication {
}
}
module.exports = { exportDir, newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
module.exports = { afterAllCleanUp, exportDir, newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper [DEV]",
"version": "1.5.0",
"version": "1.6.1",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",

View File

@@ -22,6 +22,9 @@ import { LayoutItem } from './gui/ResizableLayout/utils/types';
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
import ResourceService from '@joplin/lib/services/ResourceService';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import produce from 'immer';
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
const MasterKey = require('@joplin/lib/models/MasterKey');
@@ -247,6 +250,29 @@ class Application extends BaseApplication {
};
break;
case 'MAIN_LAYOUT_SET_ITEM_PROP':
{
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
if (item.key === action.itemKey) {
(item as any)[action.propName] = action.propValue;
return false;
}
return true;
});
});
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
newState = {
...state,
mainLayout: newLayout,
};
}
break;
case 'NOTE_FILE_WATCHER_ADD':
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {

View File

@@ -214,7 +214,7 @@ class MainScreenComponent extends React.Component<Props, State> {
let output = null;
try {
output = loadLayout(userLayout, defaultLayout, rootLayoutSize);
output = loadLayout(Object.keys(userLayout).length ? userLayout : null, defaultLayout, rootLayoutSize);
if (!findItemByKey(output, 'sideBar') || !findItemByKey(output, 'noteList') || !findItemByKey(output, 'editor')) {
throw new Error('"sideBar", "noteList" and "editor" must be present in the layout');

View File

@@ -19,7 +19,7 @@ const taboverride = require('taboverride');
const { reg } = require('@joplin/lib/registry.js');
const BaseItem = require('@joplin/lib/models/BaseItem');
const { themeStyle } = require('@joplin/lib/theme');
// const { clipboard } = require('electron');
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
function markupRenderOptions(override: any = null) {
@@ -1015,19 +1015,22 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
}
function onKeyDown(_event: any) {
// It seems "paste as text" is now handled automatically by
// either Chrome, Electron and/or TinyMCE so the code below
// should not longer be necessary. Also fixes
function onKeyDown(event: any) {
// It seems "paste as text" is handled automatically by
// on Windows so the code below so we need to run the below
// code only on macOS (and maybe Linux). If we were to run
// this on Windows we would have this double-paste issue:
// https://github.com/laurent22/joplin/issues/4243
// Handle "paste as text". Note that when pressing CtrlOrCmd+Shift+V it's going
// to trigger the "keydown" event but not the "paste" event, so it's ok to process
// it here and we don't need to do anything special in onPaste
// if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
// const pastedText = clipboard.readText();
// if (pastedText) editor.insertContent(pastedText);
// }
if (!shim.isWindows()) {
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
const pastedText = clipboard.readText();
if (pastedText) editor.insertContent(pastedText);
}
}
}
editor.on('keyup', onKeyUp);

View File

@@ -16,8 +16,4 @@ export default function findItemByKey(layout: LayoutItem, key: string): LayoutIt
}
return recurseFind(layout);
// const output = recurseFind(layout);
// if (!output) throw new Error(`Could not find item "${key}"`);
// return output;
}

View File

@@ -2,6 +2,8 @@ import { LayoutItem } from './types';
type ItemItemCallback = (itemIndex: number, item: LayoutItem, parent: LayoutItem)=> boolean;
// Callback should return `true` if iteration should continue, or `false` if it
// should stop
export default function iterateItems(layout: LayoutItem, callback: ItemItemCallback) {
const result = callback(0, layout, null);
if (result === false) return;

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.5.10",
"version": "1.6.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.5.10",
"version": "1.6.1",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -4,5 +4,5 @@
# It could be used to develop plugins too.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/register_command"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/toc"
npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH"

View File

@@ -138,8 +138,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097613
versionName "1.5.0"
versionCode 2097618
versionName "1.6.4"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -7,7 +7,7 @@
// So there's basically still a one way flux: React => SQLite => Redux => React
import { LogBox, AppRegistry } from 'react-native';
const { Root } = require('./root.js');
const Root = require('./root').default;
// Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed
// or don't really matter. Because we want important warnings to actually be fixed, we disable

View File

@@ -338,13 +338,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 57;
CURRENT_PROJECT_VERSION = 59;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.5.0;
MARKETING_VERSION = 10.6.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -365,12 +365,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 57;
CURRENT_PROJECT_VERSION = 59;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.5.0;
MARKETING_VERSION = 10.6.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",

View File

@@ -1,24 +1,43 @@
import setUpQuickActions from './setUpQuickActions';
import PluginAssetsLoader from './PluginAssetsLoader';
const React = require('react');
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
const shim = require('@joplin/lib/shim').default;
import shim from '@joplin/lib/shim';
shim.setReact(React);
import setUpQuickActions from './setUpQuickActions';
import PluginAssetsLoader from './PluginAssetsLoader';
import AlarmService from '@joplin/lib/services/AlarmService';
import Alarm from '@joplin/lib/models/Alarm';
import time from '@joplin/lib/time';
import Logger, { TargetType } from '@joplin/lib/Logger';
import BaseModel from '@joplin/lib/BaseModel';
import BaseService from '@joplin/lib/services/BaseService';
import ResourceService from '@joplin/lib/services/ResourceService';
import KvStore from '@joplin/lib/services/KvStore';
import NoteScreen from './components/screens/Note';
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen';
import Setting from '@joplin/lib/models/Setting';
import RNFetchBlob from 'rn-fetch-blob';
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
import reducer from '@joplin/lib/reducer';
import ShareExtension from './utils/ShareExtension';
import handleShared from './utils/shareHandler';
import uuid from '@joplin/lib/uuid';
import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtils';
import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile';
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
const DropdownAlert = require('react-native-dropdownalert').default;
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
const SafeAreaView = require('./components/SafeAreaView');
const { connect, Provider } = require('react-redux');
const { BackButtonService } = require('./services/back-button.js');
const NavService = require('@joplin/lib/services/NavService.js');
const AlarmService = require('@joplin/lib/services/AlarmService.js').default;
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
const Alarm = require('@joplin/lib/models/Alarm').default;
const { createStore, applyMiddleware } = require('redux');
const reduxSharedMiddleware = require('@joplin/lib/components/shared/reduxSharedMiddleware');
const { shimInit } = require('./utils/shim-init-react.js');
const time = require('@joplin/lib/time').default;
const { AppNav } = require('./components/app-nav.js');
const Logger = require('@joplin/lib/Logger').default;
const Note = require('@joplin/lib/models/Note.js');
const Folder = require('@joplin/lib/models/Folder.js');
const BaseSyncTarget = require('@joplin/lib/BaseSyncTarget.js');
@@ -29,16 +48,11 @@ const NoteTag = require('@joplin/lib/models/NoteTag.js');
const BaseItem = require('@joplin/lib/models/BaseItem.js');
const MasterKey = require('@joplin/lib/models/MasterKey.js');
const Revision = require('@joplin/lib/models/Revision.js');
const BaseModel = require('@joplin/lib/BaseModel').default;
const BaseService = require('@joplin/lib/services/BaseService').default;
const ResourceService = require('@joplin/lib/services/ResourceService').default;
const RevisionService = require('@joplin/lib/services/RevisionService');
const KvStore = require('@joplin/lib/services/KvStore').default;
const { JoplinDatabase } = require('@joplin/lib/joplin-database.js');
const { Database } = require('@joplin/lib/database.js');
const { NotesScreen } = require('./components/screens/notes.js');
const { TagsScreen } = require('./components/screens/tags.js');
const NoteScreen = require('./components/screens/Note').default;
const { ConfigScreen } = require('./components/screens/config.js');
const { FolderScreen } = require('./components/screens/folder.js');
const { LogScreen } = require('./components/screens/log.js');
@@ -47,31 +61,18 @@ const { SearchScreen } = require('./components/screens/search.js');
const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js');
const { EncryptionConfigScreen } = require('./components/screens/encryption-config.js');
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
const UpgradeSyncTargetScreen = require('./components/screens/UpgradeSyncTargetScreen').default;
const Setting = require('@joplin/lib/models/Setting').default;
const { MenuContext } = require('react-native-popup-menu');
const { SideMenu } = require('./components/side-menu.js');
const { SideMenuContent } = require('./components/side-menu-content.js');
const { SideMenuContentNote } = require('./components/side-menu-content-note.js');
const { DatabaseDriverReactNative } = require('./utils/database-driver-react-native');
const { reg } = require('@joplin/lib/registry.js');
const { setLocale, closestSupportedLocale, defaultLocale } = require('@joplin/lib/locale');
const RNFetchBlob = require('rn-fetch-blob').default;
const PoorManIntervals = require('@joplin/lib/PoorManIntervals').default;
const reducer = require('@joplin/lib/reducer').default;
const { defaultState } = require('@joplin/lib/reducer');
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local.js');
const DropdownAlert = require('react-native-dropdownalert').default;
const ShareExtension = require('./utils/ShareExtension.js').default;
const handleShared = require('./utils/shareHandler').default;
const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher');
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine');
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
const { themeStyle } = require('./components/global-style.js');
const uuid = require('@joplin/lib/uuid').default;
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
const KeychainServiceDriverMobile = require('@joplin/lib/services/keychain/KeychainServiceDriver.mobile').default;
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js');
const SyncTargetOneDrive = require('@joplin/lib/SyncTargetOneDrive.js');
@@ -87,15 +88,16 @@ SyncTargetRegistry.addClass(SyncTargetWebDAV);
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
const FsDriverRN = require('./utils/fs-driver-rn.js').FsDriverRN;
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker');
const EncryptionService = require('@joplin/lib/services/EncryptionService');
const MigrationService = require('@joplin/lib/services/MigrationService');
let storeDispatch = function() {};
let storeDispatch = function(_action: any) {};
const logReducerAction = function(action) {
const logReducerAction = function(action: any) {
if (['SIDE_MENU_OPEN_PERCENT', 'SYNC_REPORT_UPDATE'].indexOf(action.type) >= 0) return;
const msg = [action.type];
@@ -104,7 +106,7 @@ const logReducerAction = function(action) {
// reg.logger().debug('Reducer action', msg.join(', '));
};
const generalMiddleware = store => next => async (action) => {
const generalMiddleware = (store: any) => (next: any) => async (action: any) => {
logReducerAction(action);
PoorManIntervals.update(); // This function needs to be called regularly so put it here
@@ -167,9 +169,9 @@ const generalMiddleware = store => next => async (action) => {
return result;
};
const navHistory = [];
const navHistory: any[] = [];
function historyCanGoBackTo(route) {
function historyCanGoBackTo(route: any) {
if (route.routeName === 'Note') return false;
if (route.routeName === 'Folder') return false;
@@ -194,13 +196,14 @@ const appDefaultState = Object.assign({}, defaultState, {
noteSideMenuOptions: null,
});
const appReducer = (state = appDefaultState, action) => {
const appReducer = (state = appDefaultState, action: any) => {
let newState = state;
let historyGoingBack = false;
try {
switch (action.type) {
// @ts-ignore
case 'NAV_BACK':
{
@@ -224,7 +227,7 @@ const appReducer = (state = appDefaultState, action) => {
{
const currentRoute = state.route;
if (!historyGoingBack && historyCanGoBackTo(currentRoute, action)) {
if (!historyGoingBack && historyCanGoBackTo(currentRoute)) {
// If the route *name* is the same (even if the other parameters are different), we
// overwrite the last route in the history with the current one. If the route name
// is different, we push a new history entry.
@@ -368,7 +371,7 @@ const appReducer = (state = appDefaultState, action) => {
const store = createStore(appReducer, applyMiddleware(generalMiddleware));
storeDispatch = store.dispatch;
function resourceFetcher_downloadComplete(event) {
function resourceFetcher_downloadComplete(event: any) {
if (event.encrypted) {
DecryptionWorker.instance().scheduleStart();
}
@@ -378,9 +381,10 @@ function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
ResourceFetcher.instance().scheduleAutoAddResources();
}
async function initialize(dispatch) {
async function initialize(dispatch: Function) {
shimInit();
// @ts-ignore
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
Setting.setConstant('appType', 'mobile');
@@ -391,18 +395,18 @@ async function initialize(dispatch) {
await logDatabase.exec(Logger.databaseCreateTableSql());
const mainLogger = new Logger();
mainLogger.addTarget('database', { database: logDatabase, source: 'm' });
mainLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
mainLogger.setLevel(Logger.LEVEL_INFO);
if (Setting.value('env') == 'dev') {
mainLogger.addTarget('console');
mainLogger.addTarget(TargetType.Console);
mainLogger.setLevel(Logger.LEVEL_DEBUG);
}
Logger.initializeGlobalLogger(mainLogger);
reg.setLogger(mainLogger);
reg.setShowErrorMessageBoxHandler((message) => { alert(message); });
reg.setShowErrorMessageBoxHandler((message: string) => { alert(message); });
BaseService.logger_ = mainLogger;
// require('@joplin/lib/ntpDate').setLogger(reg.logger());
@@ -411,9 +415,9 @@ async function initialize(dispatch) {
reg.logger().info(`Starting application ${Setting.value('appId')} (${Setting.value('env')})`);
const dbLogger = new Logger();
dbLogger.addTarget('database', { database: logDatabase, source: 'm' });
dbLogger.addTarget(TargetType.Database, { database: logDatabase, source: 'm' });
if (Setting.value('env') == 'dev') {
dbLogger.addTarget('console');
dbLogger.addTarget(TargetType.Console);
dbLogger.setLevel(Logger.LEVEL_INFO); // Set to LEVEL_DEBUG for full SQL queries
} else {
dbLogger.setLevel(Logger.LEVEL_INFO);
@@ -452,7 +456,7 @@ async function initialize(dispatch) {
if (Setting.value('env') == 'prod') {
await db.open({ name: 'joplin.sqlite' });
} else {
await db.open({ name: 'joplin-76.sqlite' });
await db.open({ name: 'joplin-100.sqlite' });
// await db.clearForTesting();
}
@@ -562,7 +566,7 @@ async function initialize(dispatch) {
reg.setupRecurrentSync();
PoorManIntervals.setTimeout(() => {
AlarmService.garbageCollect();
void AlarmService.garbageCollect();
}, 1000 * 60 * 60);
ResourceService.runInBackground();
@@ -584,7 +588,7 @@ async function initialize(dispatch) {
reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
AlarmService.updateAllNotifications();
void AlarmService.updateAllNotifications();
DecryptionWorker.instance().scheduleStart();
});
@@ -654,7 +658,7 @@ class AppComponent extends React.Component {
BackButtonService.initialize(this.backButtonHandler_);
AlarmService.setInAppNotificationHandler(async (alarmId) => {
AlarmService.setInAppNotificationHandler(async (alarmId: string) => {
const alarm = await Alarm.load(alarmId);
const notification = await Alarm.makeNotification(alarm);
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
@@ -666,7 +670,7 @@ class AppComponent extends React.Component {
if (sharedData) {
reg.logger().info('Received shared data');
if (this.props.selectedFolderId) {
handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
await handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
} else {
reg.logger.info('Cannot handle share - default folder id is not set');
}
@@ -677,7 +681,7 @@ class AppComponent extends React.Component {
AppState.removeEventListener('change', this.onAppStateChange_);
}
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: any) {
if (this.props.showSideMenu !== prevProps.showSideMenu) {
Animated.timing(this.state.sideMenuContentOpacity, {
toValue: this.props.showSideMenu ? 0.5 : 0,
@@ -707,14 +711,14 @@ class AppComponent extends React.Component {
return false;
}
UNSAFE_componentWillReceiveProps(newProps) {
UNSAFE_componentWillReceiveProps(newProps: any) {
if (newProps.syncStarted != this.lastSyncStarted_) {
if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders();
this.lastSyncStarted_ = newProps.syncStarted;
}
}
sideMenu_change(isOpen) {
sideMenu_change(isOpen: boolean) {
// Make sure showSideMenu property of state is updated
// when the menu is open/closed.
this.props.dispatch({
@@ -759,8 +763,8 @@ class AppComponent extends React.Component {
menu={sideMenuContent}
edgeHitWidth={5}
menuPosition={menuPosition}
onChange={(isOpen) => this.sideMenu_change(isOpen)}
onSliding={(percent) => {
onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)}
onSliding={(percent: number) => {
this.props.dispatch({
type: 'SIDE_MENU_OPEN_PERCENT',
value: percent,
@@ -773,7 +777,7 @@ class AppComponent extends React.Component {
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<AppNav screens={appNavInit} />
</View>
<DropdownAlert ref={ref => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
<DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
</SafeAreaView>
</MenuContext>
@@ -783,7 +787,7 @@ class AppComponent extends React.Component {
}
}
const mapStateToProps = (state) => {
const mapStateToProps = (state: any) => {
return {
historyCanGoBack: state.historyCanGoBack,
showSideMenu: state.showSideMenu,
@@ -799,7 +803,7 @@ const mapStateToProps = (state) => {
const App = connect(mapStateToProps)(AppComponent);
class Root extends React.Component {
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
@@ -808,5 +812,3 @@ class Root extends React.Component {
);
}
}
module.exports = { Root };

View File

@@ -24,9 +24,9 @@ export default async (sharedData: SharedData, folderId: string, dispatch: Functi
// below will do nothing (because routeName wouldn't change)
// Then we wait a bit for the state to be set correctly, and
// finally we go to the new note.
await dispatch({ type: 'NAV_BACK' });
dispatch({ type: 'NAV_BACK' });
await dispatch({ type: 'SIDE_MENU_CLOSE' });
dispatch({ type: 'SIDE_MENU_CLOSE' });
const newNote = await Note.save({
parent_id: folderId,

View File

@@ -30,4 +30,16 @@ export default class JoplinViewsPanels {
* Called when a message is sent from the webview (using postMessage).
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"id": "<%= pluginId %>",
"app_min_version": "1.5",
"app_min_version": "1.6",
"version": "1.0.0",
"name": "<%= pluginName %>",
"description": "<%= pluginDescription %>",

View File

@@ -1,6 +1,6 @@
{
"name": "generator-joplin",
"version": "1.5.0",
"version": "1.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,9 +1,8 @@
{
"name": "generator-joplin",
"version": "1.5.0",
"version": "1.6.0",
"description": "Scaffolds out a new Joplin plugin",
"homepage": "https://joplinapp.org",
"private": true,
"author": {
"name": "Laurent Cozic",
"url": "https://joplinapp.org"
@@ -25,5 +24,6 @@
"yosay": "^2.0.1"
},
"repository": "https://github.com/laurent22/generator-joplin",
"license": "MIT"
"license": "MIT",
"private": true
}

View File

@@ -6,6 +6,7 @@ import reducer from './reducer';
import KeychainServiceDriver from './services/keychain/KeychainServiceDriver.node';
import { _, setLocale } from './locale';
import KvStore from './services/KvStore';
import SyncTargetJoplinServer from './SyncTargetJoplinServer';
const { createStore, applyMiddleware } = require('redux');
const { defaultState, stateUtils } = require('./reducer');
@@ -681,6 +682,7 @@ export default class BaseApplication {
SyncTargetRegistry.addClass(SyncTargetWebDAV);
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
try {
await shim.fsDriver().remove(tempDir);

View File

@@ -66,7 +66,7 @@ class BaseModel {
public static TYPE_SMART_FILTER = ModelType.SmartFilter;
public static TYPE_COMMAND = ModelType.Command;
protected static dispatch: Function = function() {};
public static dispatch: Function = function() {};
private static saveMutexes_: any = {};
private static db_: any;

View File

@@ -0,0 +1,174 @@
import shim from './shim';
const { rtrimSlashes } = require('./path-utils.js');
const JoplinError = require('./JoplinError');
const { stringify } = require('query-string');
interface Options {
baseUrl(): string;
username(): string;
password(): string;
}
enum ExecOptionsResponseFormat {
Json = 'json',
Text = 'text',
}
enum ExecOptionsTarget {
String = 'string',
File = 'file',
}
interface ExecOptions {
responseFormat?: ExecOptionsResponseFormat;
target?: ExecOptionsTarget;
path?: string;
source?: string;
}
export default class JoplinServerApi {
private options_: Options;
private session_: any;
public constructor(options: Options) {
this.options_ = options;
}
private baseUrl() {
return rtrimSlashes(this.options_.baseUrl());
}
private async session() {
// TODO: handle invalid session
if (this.session_) return this.session_;
this.session_ = await this.exec('POST', 'api/sessions', null, {
email: this.options_.username(),
password: this.options_.password(),
});
return this.session_;
}
private async sessionId() {
const session = await this.session();
return session ? session.id : '';
}
// private requestToCurl_(url: string, options: any) {
// const output = [];
// output.push('curl');
// output.push('-v');
// if (options.method) output.push(`-X ${options.method}`);
// if (options.headers) {
// for (const n in options.headers) {
// if (!options.headers.hasOwnProperty(n)) continue;
// output.push(`${'-H ' + '"'}${n}: ${options.headers[n]}"`);
// }
// }
// if (options.body) output.push(`${'--data ' + '\''}${JSON.stringify(options.body)}'`);
// output.push(url);
// return output.join(' ');
// }
public async exec(method: string, path: string = '', query: Record<string, any> = null, body: any = null, headers: any = null, options: ExecOptions = null) {
if (headers === null) headers = {};
if (options === null) options = {};
if (!options.responseFormat) options.responseFormat = ExecOptionsResponseFormat.Json;
if (!options.target) options.target = ExecOptionsTarget.String;
let sessionId = '';
if (path !== 'api/sessions' && !sessionId) {
sessionId = await this.sessionId();
}
if (sessionId) headers['X-API-AUTH'] = sessionId;
const fetchOptions: any = {};
fetchOptions.headers = headers;
fetchOptions.method = method;
if (options.path) fetchOptions.path = options.path;
if (body) {
if (typeof body === 'object') {
fetchOptions.body = JSON.stringify(body);
fetchOptions.headers['Content-Type'] = 'application/json';
} else {
fetchOptions.body = body;
}
fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(fetchOptions.body)}`;
}
let url = `${this.baseUrl()}/${path}`;
if (query) {
url += url.indexOf('?') < 0 ? '?' : '&';
url += stringify(query);
}
let response: any = null;
// console.info('Joplin API Call', `${method} ${url}`, headers, options);
// console.info(this.requestToCurl_(url, fetchOptions));
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
if (fetchOptions.path) {
const fileStat = await shim.fsDriver().stat(fetchOptions.path);
if (fileStat) fetchOptions.headers['Content-Length'] = `${fileStat.size}`;
}
response = await shim.uploadBlob(url, fetchOptions);
} else if (options.target == 'string') {
if (typeof body === 'string') fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(body)}`;
response = await shim.fetch(url, fetchOptions);
} else {
// file
response = await shim.fetchBlob(url, fetchOptions);
}
const responseText = await response.text();
// console.info('Joplin API Response', responseText);
// Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier
const newError = (message: string, code: number = 0) => {
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
// JSON. That way the error message will still show there's a problem but without filling up the log or screen.
const shortResponseText = (`${responseText}`).substr(0, 1024);
return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code);
};
let responseJson_: any = null;
const loadResponseJson = async () => {
if (!responseText) return null;
if (responseJson_) return responseJson_;
responseJson_ = JSON.parse(responseText);
if (!responseJson_) throw newError('Cannot parse JSON response', response.status);
return responseJson_;
};
if (!response.ok) {
if (options.target === 'file') throw newError('fetchBlob error', response.status);
let json = null;
try {
json = await loadResponseJson();
} catch (error) {
// Just send back the plain text in newErro()
}
if (json && json.message) {
throw newError(`${json.message}`, response.status);
}
throw newError('Unknown error', response.status);
}
if (options.responseFormat === 'text') return responseText;
const output = await loadResponseJson();
return output;
}
}

View File

@@ -1,6 +1,7 @@
const moment = require('moment');
const time = require('./time').default;
const { FsDriverDummy } = require('./fs-driver-dummy.js');
const { sprintf } = require('sprintf-js');
export enum TargetType {
Database = 'database',
@@ -24,6 +25,12 @@ interface Target {
prefix?: string;
path?: string;
source?: string;
// Default message format
format?: string;
// If specified, will use this as format if it's an info message
formatInfo?: string;
}
export interface LoggerWrapper {
@@ -173,9 +180,25 @@ class Logger {
if (level == LogLevel.Warn) fn = 'warn';
if (level == LogLevel.Info) fn = 'info';
const consoleObj = target.console ? target.console : console;
const prefixItems = [moment().format('HH:mm:ss')];
if (targetPrefix) prefixItems.push(targetPrefix);
const items = [`${prefixItems.join(': ')}:`].concat(...object);
let items: any[] = [];
if (target.format) {
const format = level === LogLevel.Info && target.formatInfo ? target.formatInfo : target.format;
const s = sprintf(format, {
date_time: moment().format('YYYY-MM-DD HH:mm:ss'),
level: Logger.levelIdToString(level),
prefix: targetPrefix || '',
message: '',
});
items = [s.trim()].concat(...object);
} else {
const prefixItems = [moment().format('HH:mm:ss')];
if (targetPrefix) prefixItems.push(targetPrefix);
items = [`${prefixItems.join(': ')}:`].concat(...object);
}
consoleObj[fn](...items);
} else if (target.type == 'file') {
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');

View File

@@ -0,0 +1,91 @@
import FileApiDriverJoplinServer from './file-api-driver-joplinServer';
import Setting from './models/Setting';
import Synchronizer from './Synchronizer';
import { _ } from './locale.js';
import JoplinServerApi from './JoplinServerApi2';
const BaseSyncTarget = require('./BaseSyncTarget.js');
const { FileApi } = require('./file-api.js');
interface FileApiOptions {
path(): string;
username(): string;
password(): string;
directory(): string;
}
export default class SyncTargetJoplinServer extends BaseSyncTarget {
static id() {
return 9;
}
static supportsConfigCheck() {
return true;
}
static targetName() {
return 'joplinServer';
}
static label() {
return _('Joplin Server');
}
async isAuthenticated() {
return true;
}
static async newFileApi_(options: FileApiOptions) {
const apiOptions = {
baseUrl: () => options.path(),
username: () => options.username(),
password: () => options.password(),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi(() => `root:/${options.directory()}`, driver);
fileApi.setSyncTargetId(this.id());
await fileApi.initialize();
return fileApi;
}
static async checkConfig(options: FileApiOptions) {
const fileApi = await SyncTargetJoplinServer.newFileApi_(options);
fileApi.requestRepeatCount_ = 0;
const output = {
ok: false,
errorMessage: '',
};
try {
const result = await fileApi.stat('');
if (!result) throw new Error(`Sync directory not found: "${options.directory()}" on server "${options.path()}"`);
output.ok = true;
} catch (error) {
output.errorMessage = error.message;
if (error.code) output.errorMessage += ` (Code ${error.code})`;
}
return output;
}
async initFileApi() {
const fileApi = await SyncTargetJoplinServer.newFileApi_({
path: () => Setting.value('sync.9.path'),
username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'),
directory: () => Setting.value('sync.9.directory'),
});
fileApi.setLogger(this.logger());
return fileApi;
}
async initSynchronizer() {
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
}
}

View File

@@ -336,6 +336,7 @@ export default class Synchronizer {
let syncLock = null;
try {
await this.api().initialize();
this.api().setTempDirName(Dirnames.Temp);
try {

View File

@@ -0,0 +1,196 @@
import JoplinServerApi from './JoplinServerApi2';
const { dirname, basename } = require('./path-utils');
function removeTrailingColon(path: string) {
if (!path || !path.length) return '';
if (path[path.length - 1] === ':') return path.substr(0, path.length - 1);
return path;
}
// All input paths should be in the format: "SPECIAL_DIR:/path/to/file"
// The trailing colon must not be included as it's automatically added
// when doing the API call.
// Only supported special dir at the moment is "root"
export default class FileApiDriverJoplinServer {
private api_: JoplinServerApi;
public constructor(api: JoplinServerApi) {
this.api_ = api;
}
public async initialize(basePath: string) {
const pieces = removeTrailingColon(basePath).split('/');
if (!pieces.length) return;
let parent = pieces.splice(0, 1)[0];
for (const p of pieces) {
// Syncing with the root, which is ok, and in that
// case there's no sub-dir to create.
if (!p && pieces.length === 1) return;
const subPath = `${parent}/${p}`;
await this.mkdir(subPath);
parent = subPath;
}
}
public api() {
return this.api_;
}
public requestRepeatCount() {
return 3;
}
private metadataToStat_(md: any, path: string, isDeleted: boolean = false) {
const output = {
path: path,
updated_time: md.updated_time,
isDir: !!md.is_directory,
isDeleted: isDeleted,
};
// TODO - HANDLE DELETED
// if (md['.tag'] === 'deleted') output.isDeleted = true;
return output;
}
private metadataToStats_(mds: any[]) {
const output = [];
for (let i = 0; i < mds.length; i++) {
output.push(this.metadataToStat_(mds[i], mds[i].name));
}
return output;
}
private apiFilePath_(p: string) {
if (p !== 'root') p += ':';
return `api/files/${p}`;
}
public async stat(path: string) {
try {
const response = await this.api().exec('GET', this.apiFilePath_(path));
return this.metadataToStat_(response, path);
} catch (error) {
if (error.code === 404) return null;
throw error;
}
}
public async delta(path: string, options: any) {
const context = options ? options.context : null;
let cursor = context ? context.cursor : null;
while (true) {
try {
const query = cursor ? { cursor } : {};
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query);
const stats = response.items.map((item: any) => {
return this.metadataToStat_(item.item, item.item.name, item.type === 3);
});
const output = {
items: stats,
hasMore: response.has_more,
context: { cursor: response.cursor },
};
return output;
} catch (error) {
// If there's an error related to an invalid cursor, clear the cursor and retry.
if (cursor && error.code === 'resyncRequired') {
cursor = null;
continue;
}
throw error;
}
}
}
public async list(path: string, options: any = null) {
options = {
context: null,
...options,
};
const query = options.context?.cursor ? { cursor: options.context.cursor } : null;
const results = await this.api().exec('GET', `${this.apiFilePath_(path)}/children`, query);
const newContext: any = {};
if (results.cursor) newContext.cursor = results.cursor;
return {
items: this.metadataToStats_(results.items),
hasMore: results.has_more,
context: newContext,
} as any;
}
public async get(path: string, options: any) {
if (!options) options = {};
if (!options.responseFormat) options.responseFormat = 'text';
try {
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/content`, null, null, null, options);
return response;
} catch (error) {
if (error.code !== 404) throw error;
return null;
}
}
private parentPath_(path: string) {
let output = dirname(path);
// This is the root or a special folder
if (output.split('/').length === 1) {
output = output.substr(0, output.length - 1);
}
return output;
}
private basename_(path: string) {
return basename(path);
}
public async mkdir(path: string) {
const parentPath = this.parentPath_(path);
const filename = this.basename_(path);
try {
const response = await this.api().exec('POST', `${this.apiFilePath_(parentPath)}/children`, null, {
name: filename,
is_directory: 1,
});
return response;
} catch (error) {
// 409 is OK - directory already exists
if (error.code !== 409) throw error;
}
}
public async put(path: string, content: any, options: any = null) {
return this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, null, content, {
'Content-Type': 'application/octet-stream',
}, options);
}
public async delete(path: string) {
return this.api().exec('DELETE', this.apiFilePath_(path));
}
public format() {
throw new Error('Not supported');
}
public async clearRoot(path: string) {
await this.delete(path);
await this.mkdir(path);
}
}

View File

@@ -8,6 +8,8 @@ const time = require('./time').default;
const { sprintf } = require('sprintf-js');
const Mutex = require('async-mutex').Mutex;
const logger = Logger.create('FileApi');
function requestCanBeRepeated(error) {
const errorCode = typeof error === 'object' && error.code ? error.code : null;
@@ -61,8 +63,14 @@ class FileApi {
this.remoteDateOffset_ = 0;
this.remoteDateNextCheckTime_ = 0;
this.remoteDateMutex_ = new Mutex();
this.initialized_ = false;
}
async initialize() {
if (this.initialized_) return;
this.initialized_ = true;
if (this.driver_.initialize) return this.driver_.initialize(this.fullPath_(''));
}
async fetchRemoteDateOffset_() {
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
@@ -108,7 +116,7 @@ class FileApi {
this.remoteDateNextCheckTime_ = Date.now() + 10 * 60 * 1000;
}
} catch (error) {
this.logger().warn('Could not retrieve remote date - defaulting to device date:', error);
logger.warn('Could not retrieve remote date - defaulting to device date:', error);
this.remoteDateOffset_ = 0;
this.remoteDateNextCheckTime_ = Date.now() + 60 * 1000;
} finally {
@@ -137,7 +145,7 @@ class FileApi {
}
baseDir() {
return this.baseDir_;
return typeof this.baseDir_ === 'function' ? this.baseDir_() : this.baseDir_;
}
tempDirName() {
@@ -191,7 +199,7 @@ class FileApi {
if (!('includeDirs' in options)) options.includeDirs = true;
if (!('syncItemsOnly' in options)) options.syncItemsOnly = false;
this.logger().debug(`list ${this.baseDir()}`);
logger.debug(`list ${this.baseDir()}`);
const result = await tryAndRepeat(() => this.driver_.list(this.fullPath_(path), options), this.requestRepeatCount());
@@ -216,18 +224,18 @@ class FileApi {
// Deprectated
setTimestamp(path, timestampMs) {
this.logger().debug(`setTimestamp ${this.fullPath_(path)}`);
logger.debug(`setTimestamp ${this.fullPath_(path)}`);
return tryAndRepeat(() => this.driver_.setTimestamp(this.fullPath_(path), timestampMs), this.requestRepeatCount());
// return this.driver_.setTimestamp(this.fullPath_(path), timestampMs);
}
mkdir(path) {
this.logger().debug(`mkdir ${this.fullPath_(path)}`);
logger.debug(`mkdir ${this.fullPath_(path)}`);
return tryAndRepeat(() => this.driver_.mkdir(this.fullPath_(path)), this.requestRepeatCount());
}
async stat(path) {
this.logger().debug(`stat ${this.fullPath_(path)}`);
logger.debug(`stat ${this.fullPath_(path)}`);
const output = await tryAndRepeat(() => this.driver_.stat(this.fullPath_(path)), this.requestRepeatCount());
@@ -246,12 +254,12 @@ class FileApi {
get(path, options = null) {
if (!options) options = {};
if (!options.encoding) options.encoding = 'utf8';
this.logger().debug(`get ${this.fullPath_(path)}`);
logger.debug(`get ${this.fullPath_(path)}`);
return tryAndRepeat(() => this.driver_.get(this.fullPath_(path), options), this.requestRepeatCount());
}
async put(path, content, options = null) {
this.logger().debug(`put ${this.fullPath_(path)}`, options);
logger.debug(`put ${this.fullPath_(path)}`, options);
if (options && options.source === 'file') {
if (!(await this.fsDriver().exists(options.path))) throw new JoplinError(`File not found: ${options.path}`, 'fileNotFound');
@@ -261,13 +269,13 @@ class FileApi {
}
delete(path) {
this.logger().debug(`delete ${this.fullPath_(path)}`);
logger.debug(`delete ${this.fullPath_(path)}`);
return tryAndRepeat(() => this.driver_.delete(this.fullPath_(path)), this.requestRepeatCount());
}
// Deprectated
move(oldPath, newPath) {
this.logger().debug(`move ${this.fullPath_(oldPath)} => ${this.fullPath_(newPath)}`);
logger.debug(`move ${this.fullPath_(oldPath)} => ${this.fullPath_(newPath)}`);
return tryAndRepeat(() => this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath)), this.requestRepeatCount());
}
@@ -281,7 +289,7 @@ class FileApi {
}
delta(path, options = null) {
this.logger().debug(`delta ${this.fullPath_(path)}`);
logger.debug(`delta ${this.fullPath_(path)}`);
return tryAndRepeat(() => this.driver_.delta(this.fullPath_(path), options), this.requestRepeatCount());
}
}

View File

@@ -1,3 +1,5 @@
import markdownUtils from './markdownUtils';
import { ResourceEntity } from './services/database/types';
const stringPadding = require('string-padding');
const stringToStream = require('string-to-stream');
const resourceUtils = require('./resourceUtils.js');
@@ -8,7 +10,51 @@ const NEWLINE = '[[NEWLINE]]';
const NEWLINE_MERGED = '[[MERGED]]';
const SPACE = '[[SPACE]]';
function processMdArrayNewLines(md) {
enum SectionType {
Text = 'text',
Tr = 'tr',
Td = 'td',
Table = 'table',
Caption = 'caption',
Hidden = 'hidden',
Code = 'code',
}
interface Section {
type: SectionType;
parent: Section;
lines: any[];
isHeader?: boolean;
}
interface ParserStateTag {
name: string;
visible: boolean;
}
interface ParserStateList {
tag: string;
counter: number;
startedText: boolean;
}
interface ParserState {
inCode: boolean[];
inPre: boolean;
inQuote: boolean;
lists: ParserStateList[];
anchorAttributes: any[];
spanAttributes: string[];
tags: ParserStateTag[];
currentCode?: string;
}
interface EnexXmlToMdArrayResult {
content: Section;
resources: ResourceEntity[];
}
function processMdArrayNewLines(md: string[]): string {
while (md.length && md[0] == BLOCK_OPEN) {
md.shift();
}
@@ -102,7 +148,7 @@ function processMdArrayNewLines(md) {
if (!output.trim().length) return '';
// To simplify the result, we only allow up to one empty line between blocks of text
const mergeMultipleNewLines = function(lines) {
const mergeMultipleNewLines = function(lines: string[]) {
const output = [];
let newlineCount = 0;
for (let i = 0; i < lines.length; i++) {
@@ -159,23 +205,23 @@ function processMdArrayNewLines(md) {
// differently than if there's a newlines between them. So the function below parses the almost final MD and add new lines depending
// on various rules.
const isHeading = function(line) {
const isHeading = function(line: string) {
return !!line.match(/^#+\s/);
};
const isListItem = function(line) {
const isListItem = function(line: string) {
return line && line.trim().indexOf('- ') === 0;
};
const isCodeLine = function(line) {
const isCodeLine = function(line: string) {
return line && line.indexOf('\t') === 0;
};
const isTableLine = function(line) {
const isTableLine = function(line: string) {
return line.indexOf('| ') === 0;
};
const isPlainParagraph = function(line) {
const isPlainParagraph = function(line: string) {
// Note: if a line is no longer than 80 characters, we don't consider it's a paragraph, which
// means no newlines will be added before or after. This is to handle text that has been
// written with "hard" new lines.
@@ -189,7 +235,7 @@ const isPlainParagraph = function(line) {
return true;
};
function formatMdLayout(lines) {
function formatMdLayout(lines: string[]) {
let previous = '';
const newLines = [];
for (let i = 0; i < lines.length; i++) {
@@ -235,13 +281,13 @@ function formatMdLayout(lines) {
return newLines;
}
function isWhiteSpace(c) {
function isWhiteSpace(c: string): boolean {
return c == '\n' || c == '\r' || c == '\v' || c == '\f' || c == '\t' || c == ' ';
}
// Like QString::simpified(), except that it preserves non-breaking spaces (which
// Evernote uses for identation, etc.)
function simplifyString(s) {
function simplifyString(s: string): string {
let output = '';
let previousWhite = false;
for (let i = 0; i < s.length; i++) {
@@ -261,7 +307,7 @@ function simplifyString(s) {
return output;
}
function collapseWhiteSpaceAndAppend(lines, state, text) {
function collapseWhiteSpaceAndAppend(lines: string[], state: any, text: string) {
if (state.inCode.length) {
lines.push(text);
} else {
@@ -296,7 +342,7 @@ function collapseWhiteSpaceAndAppend(lines, state, text) {
return lines;
}
function tagAttributeToMdText(attr) {
function tagAttributeToMdText(attr: string): string {
// HTML attributes may contain newlines so remove them.
// https://github.com/laurent22/joplin/issues/1583
if (!attr) return '';
@@ -305,7 +351,7 @@ function tagAttributeToMdText(attr) {
return attr;
}
function addResourceTag(lines, resource, alt = '') {
function addResourceTag(lines: string[], resource: ResourceEntity, alt: string = ''): string[] {
// Note: refactor to use Resource.markdownTag
if (!alt) alt = resource.title;
@@ -326,50 +372,50 @@ function addResourceTag(lines, resource, alt = '') {
return lines;
}
function isBlockTag(n) {
function isBlockTag(n: string) {
return ['div', 'p', 'dl', 'dd', 'dt', 'center', 'address'].indexOf(n) >= 0;
}
function isStrongTag(n) {
function isStrongTag(n: string) {
return n == 'strong' || n == 'b' || n == 'big';
}
function isStrikeTag(n) {
function isStrikeTag(n: string) {
return n == 'strike' || n == 's' || n == 'del';
}
function isEmTag(n) {
function isEmTag(n: string) {
return n == 'em' || n == 'i' || n == 'u';
}
function isAnchor(n) {
function isAnchor(n: string) {
return n == 'a';
}
function isIgnoredEndTag(n) {
function isIgnoredEndTag(n: string) {
return ['en-note', 'en-todo', 'body', 'html', 'font', 'br', 'hr', 'tbody', 'sup', 'img', 'abbr', 'cite', 'thead', 'small', 'tt', 'sub', 'colgroup', 'col', 'ins', 'caption', 'var', 'map', 'area'].indexOf(n) >= 0;
}
function isListTag(n) {
function isListTag(n: string) {
return n == 'ol' || n == 'ul';
}
// Elements that don't require any special treatment beside adding a newline character
function isNewLineOnlyEndTag(n) {
function isNewLineOnlyEndTag(n: string) {
return ['div', 'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'dd', 'dt', 'center', 'address'].indexOf(n) >= 0;
}
function isInlineCodeTag(n) {
function isInlineCodeTag(n: string) {
return ['samp', 'kbd'].indexOf(n) >= 0;
}
function isNewLineBlock(s) {
function isNewLineBlock(s: string) {
return s == BLOCK_OPEN || s == BLOCK_CLOSE;
}
function attributeToLowerCase(node) {
function attributeToLowerCase(node: any) {
if (!node.attributes) return {};
const output = {};
const output: any = {};
for (const n in node.attributes) {
if (!node.attributes.hasOwnProperty(n)) continue;
output[n.toLowerCase()] = node.attributes[n];
@@ -377,13 +423,13 @@ function attributeToLowerCase(node) {
return output;
}
function isInvisibleBlock(attributes) {
function isInvisibleBlock(attributes: any) {
const style = attributes.style;
if (!style) return false;
return !!style.match(/display:[\s\S]*none/);
}
function isSpanWithStyle(attributes) {
function isSpanWithStyle(attributes: any) {
if (attributes != undefined) {
if ('style' in attributes) {
return true;
@@ -391,9 +437,10 @@ function isSpanWithStyle(attributes) {
return false;
}
}
return false;
}
function isSpanStyleBold(attributes) {
function isSpanStyleBold(attributes: any) {
const style = attributes.style;
if (!style) return false;
@@ -406,13 +453,13 @@ function isSpanStyleBold(attributes) {
}
}
function isSpanStyleItalic(attributes) {
function isSpanStyleItalic(attributes: any) {
let style = attributes.style;
style = style.replace(/\s+/g, '');
return (style.toLowerCase().includes('font-style:italic;'));
}
function displaySaxWarning(context, message) {
function displaySaxWarning(context: any, message: string) {
const line = [];
const parser = context ? context._parser : null;
if (parser) {
@@ -422,31 +469,29 @@ function displaySaxWarning(context, message) {
console.warn(line.join(': '));
}
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
function removeSectionParent(section) {
if (typeof section === 'string') return section;
// function removeSectionParent(section:Section | string) {
// if (typeof section === 'string') return section;
section = { ...section };
delete section.parent;
// section = { ...section };
// delete section.parent;
section.lines = section.lines.slice();
// section.lines = section.lines.slice();
for (let i = 0; i < section.lines.length; i++) {
section.lines[i] = removeSectionParent(section.lines[i]);
}
// for (let i = 0; i < section.lines.length; i++) {
// section.lines[i] = removeSectionParent(section.lines[i]);
// }
return section;
}
// return section;
// }
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
function printSection(section) {
console.info(JSON.stringify(removeSectionParent(section), null, 4));
}
// function printSection(section:Section) {
// console.info(JSON.stringify(removeSectionParent(section), null, 4));
// }
function enexXmlToMdArray(stream, resources) {
function enexXmlToMdArray(stream: any, resources: ResourceEntity[]): Promise<EnexXmlToMdArrayResult> {
const remainingResources = resources.slice();
const removeRemainingResource = id => {
const removeRemainingResource = (id: string) => {
for (let i = 0; i < remainingResources.length; i++) {
const r = remainingResources[i];
if (r.id === id) {
@@ -456,7 +501,7 @@ function enexXmlToMdArray(stream, resources) {
};
return new Promise((resolve) => {
const state = {
const state: ParserState = {
inCode: [],
inPre: false,
inQuote: false,
@@ -470,17 +515,17 @@ function enexXmlToMdArray(stream, resources) {
const strict = false;
const saxStream = require('@joplin/fork-sax').createStream(strict, options);
let section = {
type: 'text',
let section: Section = {
type: SectionType.Text,
lines: [],
parent: null,
};
saxStream.on('error', function(e) {
saxStream.on('error', function(e: any) {
console.warn(e);
});
const unwrapInnerText = text => {
const unwrapInnerText = (text: string) => {
const lines = text.split('\n');
let output = '';
@@ -504,14 +549,14 @@ function enexXmlToMdArray(stream, resources) {
return output;
};
saxStream.on('text', function(text) {
saxStream.on('text', function(text: string) {
if (['table', 'tr', 'tbody'].indexOf(section.type) >= 0) return;
text = !state.inPre ? unwrapInnerText(text) : text;
section.lines = collapseWhiteSpaceAndAppend(section.lines, state, text);
});
saxStream.on('opentag', function(node) {
saxStream.on('opentag', function(node: any) {
const nodeAttributes = attributeToLowerCase(node);
const n = node.name.toLowerCase();
const isVisible = !isInvisibleBlock(nodeAttributes);
@@ -542,8 +587,8 @@ function enexXmlToMdArray(stream, resources) {
if (n == 'en-note') {
// Start of note
} else if (n == 'table') {
const newSection = {
type: 'table',
const newSection: Section = {
type: SectionType.Table,
lines: [],
parent: section,
};
@@ -568,8 +613,8 @@ function enexXmlToMdArray(stream, resources) {
// return;
}
const newSection = {
type: 'tr',
const newSection: Section = {
type: SectionType.Tr,
lines: [],
parent: section,
isHeader: false,
@@ -585,8 +630,8 @@ function enexXmlToMdArray(stream, resources) {
if (n == 'th') section.isHeader = true;
const newSection = {
type: 'td',
const newSection: Section = {
type: SectionType.Td,
lines: [],
parent: section,
};
@@ -599,8 +644,8 @@ function enexXmlToMdArray(stream, resources) {
// return;
}
const newSection = {
type: 'caption',
const newSection: Section = {
type: SectionType.Caption,
lines: [],
parent: section,
};
@@ -608,8 +653,8 @@ function enexXmlToMdArray(stream, resources) {
section.lines.push(newSection);
section = newSection;
} else if (isInvisibleBlock(nodeAttributes)) {
const newSection = {
type: 'hidden',
const newSection: Section = {
type: SectionType.Hidden,
lines: [],
parent: section,
};
@@ -624,7 +669,7 @@ function enexXmlToMdArray(stream, resources) {
section.lines.push(BLOCK_OPEN);
if (!state.lists.length) {
displaySaxWarning(this, 'Found <li> tag without being inside a list');
// return;
return;
}
const container = state.lists[state.lists.length - 1];
@@ -650,7 +695,7 @@ function enexXmlToMdArray(stream, resources) {
// Many (most?) img tags don't have no source associated, especially when they were imported from HTML
let s = '![';
if (nodeAttributes.alt) s += tagAttributeToMdText(nodeAttributes.alt);
s += `](${nodeAttributes.src})`;
s += `](${markdownUtils.escapeLinkUrl(nodeAttributes.src)})`;
section.lines.push(s);
}
} else if (isAnchor(n)) {
@@ -694,8 +739,8 @@ function enexXmlToMdArray(stream, resources) {
state.inCode.push(true);
state.currentCode = '';
const newSection = {
type: 'code',
const newSection: Section = {
type: SectionType.Code,
lines: [],
parent: section,
};
@@ -802,7 +847,7 @@ function enexXmlToMdArray(stream, resources) {
}
});
saxStream.on('closetag', function(n) {
saxStream.on('closetag', function(n: string) {
n = n ? n.toLowerCase() : n;
const poppedTag = state.tags.pop();
@@ -940,7 +985,7 @@ function enexXmlToMdArray(stream, resources) {
// [ Sign in ](https://example.com)
// to:
// [Sign in](https://example.com)
const trimTextStartAndEndSpaces = function(lines) {
const trimTextStartAndEndSpaces = function(lines: string[]) {
let firstBracketIndex = 0;
let foundFirstNonWhite = false;
for (let i = lines.length - 1; i >= 0; i--) {
@@ -999,14 +1044,14 @@ function enexXmlToMdArray(stream, resources) {
resolve({
content: section,
resources: remainingResources,
});
} as EnexXmlToMdArrayResult);
});
stream.pipe(saxStream);
});
}
function tableHasSubTables(table) {
function tableHasSubTables(table: Section) {
for (let trIndex = 0; trIndex < table.lines.length; trIndex++) {
const tr = table.lines[trIndex];
if (!tr || !tr.lines) continue;
@@ -1029,7 +1074,7 @@ function tableHasSubTables(table) {
// via Web Clipper. So to handle this, we render all the outer tables as regular text (as if replacing all the <table>, <tr> and <td>
// elements by <div>) and only the inner ones, those that don't contain any other tables, are rendered as actual tables. This is generally
// the required behaviour since the outer tables are usually for layout and the inner ones are the content.
function drawTable(table) {
function drawTable(table: Section) {
// | First Header | Second Header |
// | ------------- | ------------- |
// | Content Cell | Content Cell |
@@ -1061,7 +1106,7 @@ function drawTable(table) {
if (flatRender) {
line.push(BLOCK_OPEN);
let currentCells = [];
let currentCells: any[] = [];
const renderCurrentCells = () => {
if (!currentCells.length) return;
@@ -1092,7 +1137,7 @@ function drawTable(table) {
// A cell in a Markdown table cannot have actual new lines so replace
// them with <br>, which are supported by the markdown renderers.
let cellText = processMdArrayNewLines(td.lines, true);
let cellText = processMdArrayNewLines(td.lines);
let lines = cellText.split('\n');
lines = postProcessMarkdown(lines);
cellText = lines.join('\n').replace(/\n+/g, '<br>');
@@ -1142,19 +1187,19 @@ function drawTable(table) {
lines.push(BLOCK_CLOSE);
if (caption) {
const captionLines = renderLines(caption.lines);
const captionLines: any[] = renderLines(caption.lines);
lines = lines.concat(captionLines);
}
return flatRender ? lines : lines.join(`<<<<:D>>>>${NEWLINE}<<<<:D>>>>`).split('<<<<:D>>>>');
}
function postProcessMarkdown(lines) {
function postProcessMarkdown(lines: string[]) {
// After importing HTML, the resulting Markdown often has empty lines at the beginning and end due to
// block start/end or elements that were ignored, etc. If these white spaces were intended it's not really
// possible to detect it, so simply trim them all so that the result is more deterministic and can be
// easily unit tested.
const trimEmptyLines = function(lines) {
const trimEmptyLines = function(lines: string[]) {
while (lines.length) {
if (!lines[0].trim()) {
lines.splice(0, 1);
@@ -1174,7 +1219,7 @@ function postProcessMarkdown(lines) {
return lines;
};
function cleanUpSpaces(lines) {
function cleanUpSpaces(lines: string[]) {
const output = [];
for (let i = 0; i < lines.length; i++) {
@@ -1203,7 +1248,7 @@ function postProcessMarkdown(lines) {
// A "line" can be some Markdown text, or it can be a section, like a table,
// etc. so this function returns an array of strings.
function renderLine(line) {
function renderLine(line: any) {
if (typeof line === 'object' && line.type === 'table') {
// A table
const table = line;
@@ -1227,8 +1272,8 @@ function renderLine(line) {
}
}
function renderLines(lines) {
let mdLines = [];
function renderLines(lines: any[]) {
let mdLines: string[] = [];
for (let i = 0; i < lines.length; i++) {
const renderedLines = renderLine(lines[i]);
mdLines = mdLines.concat(renderedLines);
@@ -1236,9 +1281,9 @@ function renderLines(lines) {
return mdLines;
}
async function enexXmlToMd(xmlString, resources, options = {}) {
async function enexXmlToMd(xmlString: string, resources: ResourceEntity[]) {
const stream = stringToStream(xmlString);
const result = await enexXmlToMdArray(stream, resources, options);
const result = await enexXmlToMdArray(stream, resources);
let mdLines = renderLines(result.content.lines);
@@ -1258,4 +1303,4 @@ async function enexXmlToMd(xmlString, resources, options = {}) {
return output.join('\n');
}
module.exports = { enexXmlToMd, processMdArrayNewLines, NEWLINE, addResourceTag };
export { enexXmlToMd, processMdArrayNewLines, NEWLINE, addResourceTag };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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