1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

..

126 Commits

Author SHA1 Message Date
Laurent Cozic
e0b5ef6630 Android release v2.0.3 2021-06-16 10:48:10 +01:00
Laurent Cozic
4bbb3d1d58 Android: Verbose mode for synchronizer 2021-06-16 10:43:39 +01:00
Laurent Cozic
fd769945b1 ios-v12.0.2 2021-06-16 09:26:21 +01:00
Laurent Cozic
6e91d2784f Tools: Fixed iOS versions 2021-06-16 09:26:10 +01:00
Laurent Cozic
881b2f17b1 ios-v20.0.1 2021-06-16 09:06:37 +01:00
Laurent Cozic
e83cc58ea6 Tools: Fix iOS version number 2021-06-16 09:04:41 +01:00
Laurent Cozic
77def9f782 Tools: Publish full changelog for Android app 2021-06-15 21:08:55 +01:00
Laurent Cozic
b23cc5d30a Android 2.0.2 2021-06-15 21:08:28 +01:00
Laurent Cozic
d8119bcf07 Android release v2.0.2 2021-06-15 21:02:32 +01:00
Laurent Cozic
8bce259dc9 Desktop release v2.0.10 2021-06-15 20:56:38 +01:00
Laurent Cozic
8a00eef901 Server v2.0.12 2021-06-15 17:24:56 +01:00
Laurent Cozic
31121c86d5 Server: Fixed handling of user content URL 2021-06-15 17:24:04 +01:00
Laurent Cozic
a4a156c7a5 Desktop: Fixes #5080: Ensure resources are decrypted when sharing a notebook with Joplin Server 2021-06-15 17:17:12 +01:00
Laurent Cozic
c5b0529968 Cli: Allow setting up E2EE without having to confirm the password 2021-06-15 17:15:00 +01:00
Laurent Cozic
ba322b1f9b Tools: Only notarize macOS app when building a desktop app tag 2021-06-15 16:05:26 +01:00
Laurent Cozic
6f27eae7dd Server v2.0.11 2021-06-15 12:41:59 +01:00
Laurent Cozic
85cc08c0d4 typo 2021-06-15 12:41:15 +01:00
Laurent Cozic
ba38bf3490 Server v2.0.10 2021-06-15 12:28:05 +01:00
Laurent Cozic
2cf70675dc All: Fixed user content URLs when sharing note via Joplin Server 2021-06-15 12:25:55 +01:00
Laurent Cozic
58f8d7e1b4 Merge branch 'dev' into release-2.0 2021-06-15 11:53:51 +01:00
Helmut K. C. Tessarek
b55b35e53f Update translations 2021-06-14 11:38:21 -04:00
Michal Stanke
c7194bf243 All: Translation: Update cs_CZ.po (#5074) 2021-06-14 10:55:23 -04:00
milotype
48abe2316e All: Translation: Update hr_HR.po (#5073) 2021-06-13 18:02:08 -04:00
Laurent Cozic
7aca380cfa Desktop release v2.0.9 2021-06-12 10:00:27 +02:00
Laurent Cozic
551033f8ba Merge branch 'dev' into release-2.0 2021-06-12 10:00:04 +02:00
Laurent Cozic
3b6a66a016 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-12 09:57:54 +02:00
Laurent Cozic
5d7d1be363 Tools: Only install Docker Engine when server image needs to be built 2021-06-12 09:57:42 +02:00
Ahmad Mamdouh
2af3bf61ea All: Conflict notes will now populate a new field with the ID of the conflict note. (#5049) 2021-06-12 08:46:49 +01:00
Laurent Cozic
6803f1c6a7 Tools: Fixed Docker login for CI 2021-06-12 08:52:09 +02:00
Laurent Cozic
1aa70dd6b4 Generator: Include JSON files in webpack config 2021-06-12 00:18:33 +02:00
Laurent Cozic
feaecf7653 Desktop, Mobile: Filter out form elements from note body to prevent potential XSS (thanks to Dmytro Vdovychinskiy for the PoC) 2021-06-11 20:17:45 +02:00
Laurent Cozic
af9f3eedd3 Server v2.0.9 2021-06-11 18:49:29 +02:00
Laurent Cozic
815800827b Tools: Fixed Docker image version number 2021-06-11 18:34:16 +02:00
Laurent Cozic
8f1e3ba43c Server v2.0.8 2021-06-11 18:29:40 +02:00
Laurent Cozic
8459b46cd0 Tools: Allow building Docker image from CI 2021-06-11 18:24:59 +02:00
Nishant Mittal
c5c38a323f Desktop: Expose prompt to plugins as a command (#5058) 2021-06-11 00:26:16 +01:00
JackGruber
01e6ca4616 All: Fixes: Wrong field removed in API search (#5066) 2021-06-11 00:24:50 +01:00
Laurent Cozic
24a586c537 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-11 01:23:45 +02:00
Laurent Cozic
5d233a7387 Tools: Fixed tests 2021-06-11 01:15:43 +02:00
Helmut K. C. Tessarek
054e5428d5 All: Translation: Update da_DK.po (thanks ERYpTION) 2021-06-10 19:06:15 -04:00
Laurent
0120df7bdb Tools: Run CI on pull requests too 2021-06-10 23:24:44 +01:00
Laurent Cozic
a36b13dcb4 Server: Handle custom user content URLs 2021-06-10 19:33:04 +02:00
Laurent Cozic
b81c300907 Desktop release v2.0.8 2021-06-10 17:43:30 +02:00
Laurent Cozic
1ded589eeb Tools: Fixed macOS notarisation on CI 2021-06-10 17:43:09 +02:00
Laurent Cozic
315216132f Desktop release v2.0.7 2021-06-10 17:04:09 +02:00
Laurent Cozic
2eaa821272 Merge branch 'dev' into release-2.0 2021-06-10 17:03:29 +02:00
Laurent Cozic
7c93e268e4 Tools: Fixed CI for macOS build 2021-06-10 17:02:52 +02:00
Laurent Cozic
d0c4de92e2 Desktop release v2.0.6 2021-06-10 14:07:12 +02:00
Laurent Cozic
91ce465535 Desktop release v2.0.5 2021-06-10 14:06:09 +02:00
Laurent Cozic
4098c01e7c Merge branch 'dev' into release-2.0 2021-06-10 14:03:50 +02:00
Laurent
e617e6fab3 Tools: Migrated Continuous Integration to GitHub Actions (#5061)
And removed Travis
2021-06-10 13:01:55 +01:00
Laurent Cozic
5fd6571bf1 Desktop: Allow restoring a delete note from note history using command palette 2021-06-10 11:49:20 +02:00
Laurent Cozic
00dc1d881b Desktop: Allow passing arguments to commands in command palette 2021-06-10 11:46:41 +02:00
Laurent Cozic
c37eb56ed7 Tools: Fixed tests 2021-06-10 11:13:00 +02:00
Laurent Cozic
b2b6ad479a Revert "Desktop: Make font size consistent between Markdown and Rich Text editors"
This reverts commit a058e09183.

Reverts because this change means the settings are directly accessed
from the theme, which makes the themes unusable from Joplin Server.
2021-06-10 10:59:15 +02:00
Laurent Cozic
0e4c545e14 Tools: Fixed tests 2021-06-10 10:57:45 +02:00
Laurent Cozic
bbae1aef28 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-10 00:11:40 +02:00
Laurent Cozic
cf86ffc36e Plugin Repo: Update stats every 7 days 2021-06-10 00:11:11 +02:00
Roman Musin
9d80a79cda Android: Resolves #4216: Focus note editor where tapped instead of scrolling to the end (#4998) 2021-06-08 23:39:20 +01:00
Laurent Cozic
ca487ade9a Desktop: Add "Retry all" button to sync status screen for items that could not be uploaded 2021-06-08 22:36:10 +02:00
Laurent Cozic
75b66a9fff Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-08 20:38:05 +02:00
Laurent Cozic
56fdf97693 Desktop: Fixes #5034: Certain resource paths could be corrupted when saved from the Rich Text editor 2021-06-08 20:37:44 +02:00
Subhra264
ce02a30441 Desktop, Mobile: Fixes #5025: Inline Katex gets broken when editing in Rich Text editor (#5052) 2021-06-08 19:23:10 +01:00
Laurent Cozic
a058e09183 Desktop: Make font size consistent between Markdown and Rich Text editors 2021-06-08 20:21:11 +02:00
Laurent Cozic
594084e274 Server: Fixed error when creating user 2021-06-08 12:39:18 +02:00
Laurent Cozic
5614eb9442 Server: Added option to enable or disable stack traces 2021-06-08 12:08:40 +02:00
Laurent Cozic
7a3a2084db Server: Add navbar on login and sign up page 2021-06-08 11:48:58 +02:00
Laurent Cozic
95d7ccccea Desktop: Improved Joplin Server error handling 2021-06-08 01:34:33 +02:00
Laurent Cozic
f7a7009b3c Server v2.0.6 2021-06-07 19:28:18 +02:00
Laurent Cozic
de7579a14e Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-07 18:04:45 +02:00
Laurent Cozic
c8d7ecbf6c Server: Add request duration to log 2021-06-07 16:27:09 +02:00
Laurent Cozic
3c41b45e8e Server: Check share ID when uploading a note 2021-06-07 16:17:52 +02:00
mbalint
62a371b9f3 All: Resolves #4613: Improve search with Asian scripts (#5018) 2021-06-07 15:15:04 +01:00
Laurent Cozic
5528ab7cc8 Tools: Fixed tests 2021-06-07 15:46:35 +02:00
Laurent Cozic
824afd4809 Update website 2021-06-07 11:45:54 +02:00
Laurent Cozic
8ed1330d68 Doc: Added sponsor 2021-06-07 11:45:26 +02:00
Laurent Cozic
fec5d4b335 Update website 2021-06-07 11:40:20 +02:00
Laurent Cozic
e7b9103bfc Doc: Added sponsor 2021-06-07 11:39:34 +02:00
JackGruber
dd1c9e3c2a All: Fixes #5007: Items are filtered in the API search (#5017) 2021-06-07 10:21:24 +01:00
Roman Musin
7c45b95f6f Desktop: recreate http agent when the protocol changes (#5016) 2021-06-07 10:19:59 +01:00
Caleb John
a7e67952b8 Plugins: Support executing codemirror commands from plugins when using execCommand (#5012) 2021-06-07 10:19:35 +01:00
Austin Doupnik
1b7d40387d Desktop: Fixes #4877: Incorrect list renumbering (#4914) 2021-06-07 10:17:46 +01:00
Helmut K. C. Tessarek
7921e70c4f macOS: add 'Hide Others' and 'Show All' menu items (#5024) 2021-06-06 23:49:44 +01:00
col
8afac643ba Update README.md (#5057) 2021-06-06 22:10:41 +01:00
Laurent Cozic
23cfbc2367 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-06 19:14:48 +02:00
Laurent Cozic
de45740129 Server: Load shared user content from correct domain 2021-06-06 19:14:12 +02:00
Helmut K. C. Tessarek
a04d8ef441 Doc: fix text of terms and privacy (#5053) 2021-06-05 08:26:32 +01:00
Laurent Cozic
db7b802803 Server: Add terms and privacy page 2021-06-04 18:09:09 +02:00
Laurent Cozic
75d79f373a Server: Added way to disable signup page, and added links between signup and login pages 2021-06-04 17:08:21 +02:00
Laurent Cozic
e8a02c26d0 Desktop: Fixed: Ctrl+Clicking links in Rich Text editor was broken (regression) 2021-06-04 13:34:30 +02:00
Laurent Cozic
147b6b13ab package locks 2021-06-04 13:19:04 +02:00
Helmut K. C. Tessarek
a496a3d90d update en_US.po 2021-06-04 00:38:54 -04:00
Helmut K. C. Tessarek
69a8ada2ec add new translation strings 2021-06-04 00:34:19 -04:00
Ji-Hyeon Gim
87257870f4 All: Translation: Update ko.po (#5043)
It updates Korean translation to be better.

Signed-off-by: Ji-Hyeon Gim <potatogim@potatogim.net>
2021-06-03 20:41:21 -04:00
Laurent Cozic
21ea3253db Desktop: Add Joplin Cloud sync target 2021-06-03 17:12:07 +02:00
Laurent Cozic
770af6a53b Server: Add Stripe integration 2021-06-03 15:21:02 +02:00
Laurent Cozic
c88e4f6628 Tools: Trim white spaces in credential files 2021-06-02 21:59:53 +02:00
Laurent Cozic
2f79492192 Doc: Update travis-ci links 2021-06-02 14:58:27 +02:00
Laurent Cozic
69aa749205 Server v2.0.5 2021-06-02 10:26:38 +02:00
Laurent Cozic
87a5f18c7b Desktop release v2.0.4 2021-06-02 09:57:05 +02:00
Laurent Cozic
1d2a3a97d2 Keep Joplin Server name for now 2021-06-02 09:56:24 +02:00
Laurent Cozic
42891e37a1 Desktop release v2.0.3 2021-06-02 09:52:05 +02:00
Po-chiang Chao
fe802b8ebc All: Translation: Update zh-TW (#5039)
* Update zh_TW.po

Update/tweak the translation to the latest version.

* Update zh_TW.po

Revisit the file with POEdit
2021-06-01 19:29:43 -04:00
Laurent Cozic
3cb6d4568c Revert "All: Translation: Update zh_TW.po (#5029)"
This reverts commit 2a58664735 as it's
invalid translations:

https://travis-ci.org/github/laurent22/joplin/jobs/772889793#L1499
2021-06-01 18:09:46 +02:00
Laurent Cozic
a9f0a75d9d Releasing sub-packages 2021-06-01 11:28:50 +02:00
Laurent Cozic
07d30eb5d2 Plugin Repo: Only save stats every x hours 2021-06-01 11:26:13 +02:00
Laurent Cozic
8f6a47536c Desktop: Download plugins from GitHub release 2021-06-01 11:09:46 +02:00
Laurent Cozic
d8d83b236e Releasing sub-packages 2021-06-01 10:42:46 +02:00
Laurent Cozic
a355600e76 Plugin Repo: Add plugin assets to a release and save plugin stats 2021-06-01 10:39:31 +02:00
Po-chiang Chao
2a58664735 All: Translation: Update zh_TW.po (#5029)
Update/tweak the translation to the latest version.
2021-05-30 16:30:38 -04:00
Roman Musin
89bc181072 Delete ignored .js files (#5027) 2021-05-29 14:12:50 +01:00
Manuel Tassi
ab7380a09f All: Translation: Update it_IT.po (#5011)
* Update it_IT.po

* Update it_IT.po
2021-05-28 18:37:36 -04:00
Laurent Cozic
f8a26cf8f9 Server: Allow disabling item upload for a user 2021-05-27 16:25:37 +02:00
Laurent Cozic
3505a2a973 Doc: Fixed translation flags 2021-05-27 15:49:29 +02:00
Laurent Cozic
5f94de0f24 Fixed tests 2021-05-27 15:44:07 +02:00
Laurent Cozic
6811ea1eb9 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-05-27 15:34:12 +02:00
Laurent Cozic
7be59a7435 Doc: Update contributors 2021-05-27 15:33:51 +02:00
Laurent Cozic
c0683ca4c3 Doc: Added script to build Sponsors table 2021-05-27 15:24:56 +02:00
JackGruber
2b286410f6 Desktop: Fixes #4411: Count tags based on showCompletedTodos setting (#4957) 2021-05-27 12:44:58 +01:00
Laurent Cozic
907ac7c1f8 Doc: Updated link for Chrome extension development 2021-05-27 13:35:05 +02:00
Laurent Cozic
8bc27021db Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-05-26 20:05:30 +02:00
Laurent Cozic
41ed66d323 Server: Added signup pages 2021-05-26 19:55:43 +02:00
Laurent Cozic
0ef7e98479 Server: Add version number on website 2021-05-26 15:53:27 +02:00
Laurent Cozic
161c77cb48 Server v2.0.4 2021-05-25 20:35:49 +02:00
Laurent Cozic
50d17bfb36 Merge branch 'dev' into release-2.0 2021-05-25 20:30:36 +02:00
Laurent Cozic
ee0f23718b Server: Fixed Item and Log page when using Postgres 2021-05-25 20:29:59 +02:00
238 changed files with 20957 additions and 18778 deletions

View File

@@ -140,6 +140,9 @@ packages/app-desktop/commands/openProfileDirectory.js.map
packages/app-desktop/commands/replaceMisspelling.d.ts packages/app-desktop/commands/replaceMisspelling.d.ts
packages/app-desktop/commands/replaceMisspelling.js packages/app-desktop/commands/replaceMisspelling.js
packages/app-desktop/commands/replaceMisspelling.js.map packages/app-desktop/commands/replaceMisspelling.js.map
packages/app-desktop/commands/restoreNoteRevision.d.ts
packages/app-desktop/commands/restoreNoteRevision.js
packages/app-desktop/commands/restoreNoteRevision.js.map
packages/app-desktop/commands/startExternalEditing.d.ts packages/app-desktop/commands/startExternalEditing.d.ts
packages/app-desktop/commands/startExternalEditing.js packages/app-desktop/commands/startExternalEditing.js
packages/app-desktop/commands/startExternalEditing.js.map packages/app-desktop/commands/startExternalEditing.js.map
@@ -275,6 +278,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteContentProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.d.ts packages/app-desktop/gui/MainScreen/commands/showNoteProperties.d.ts
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showPrompt.d.ts
packages/app-desktop/gui/MainScreen/commands/showPrompt.js
packages/app-desktop/gui/MainScreen/commands/showPrompt.js.map
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.d.ts packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
@@ -338,6 +344,9 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearch.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.d.ts packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js.map packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.d.ts packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map
@@ -821,6 +830,9 @@ packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinCloud.d.ts
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinCloud.js.map
packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map packages/lib/SyncTargetJoplinServer.js.map
@@ -968,6 +980,9 @@ packages/lib/models/dateTimeFormats.test.js.map
packages/lib/models/settings/FileHandler.d.ts packages/lib/models/settings/FileHandler.d.ts
packages/lib/models/settings/FileHandler.js packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/FileHandler.js.map packages/lib/models/settings/FileHandler.js.map
packages/lib/models/utils/itemCanBeEncrypted.d.ts
packages/lib/models/utils/itemCanBeEncrypted.js
packages/lib/models/utils/itemCanBeEncrypted.js.map
packages/lib/models/utils/paginatedFeed.d.ts packages/lib/models/utils/paginatedFeed.d.ts
packages/lib/models/utils/paginatedFeed.js packages/lib/models/utils/paginatedFeed.js
packages/lib/models/utils/paginatedFeed.js.map packages/lib/models/utils/paginatedFeed.js.map
@@ -1157,6 +1172,9 @@ packages/lib/services/interop/InteropService_Importer_Raw.js.map
packages/lib/services/interop/types.d.ts packages/lib/services/interop/types.d.ts
packages/lib/services/interop/types.js packages/lib/services/interop/types.js
packages/lib/services/interop/types.js.map packages/lib/services/interop/types.js.map
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.d.ts
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js.map
packages/lib/services/keychain/KeychainService.d.ts packages/lib/services/keychain/KeychainService.d.ts
packages/lib/services/keychain/KeychainService.js packages/lib/services/keychain/KeychainService.js
packages/lib/services/keychain/KeychainService.js.map packages/lib/services/keychain/KeychainService.js.map
@@ -1475,6 +1493,9 @@ packages/lib/uuid.js.map
packages/lib/versionInfo.d.ts packages/lib/versionInfo.d.ts
packages/lib/versionInfo.js packages/lib/versionInfo.js
packages/lib/versionInfo.js.map packages/lib/versionInfo.js.map
packages/plugin-repo-cli/commands/updateRelease.d.ts
packages/plugin-repo-cli/commands/updateRelease.js
packages/plugin-repo-cli/commands/updateRelease.js.map
packages/plugin-repo-cli/index.d.ts packages/plugin-repo-cli/index.d.ts
packages/plugin-repo-cli/index.js packages/plugin-repo-cli/index.js
packages/plugin-repo-cli/index.js.map packages/plugin-repo-cli/index.js.map
@@ -1589,6 +1610,9 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts packages/renderer/utils.d.ts
packages/renderer/utils.js packages/renderer/utils.js
packages/renderer/utils.js.map packages/renderer/utils.js.map
packages/tools/buildServerDocker.d.ts
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.js.map
packages/tools/generate-database-types.d.ts packages/tools/generate-database-types.d.ts
packages/tools/generate-database-types.js packages/tools/generate-database-types.js
packages/tools/generate-database-types.js.map packages/tools/generate-database-types.js.map
@@ -1613,4 +1637,7 @@ packages/tools/release-server.js.map
packages/tools/tool-utils.d.ts packages/tools/tool-utils.d.ts
packages/tools/tool-utils.js packages/tools/tool-utils.js
packages/tools/tool-utils.js.map packages/tools/tool-utils.js.map
packages/tools/update-readme-sponsors.d.ts
packages/tools/update-readme-sponsors.js
packages/tools/update-readme-sponsors.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

134
.github/scripts/run_ci.sh vendored Executable file
View File

@@ -0,0 +1,134 @@
#!/bin/bash
# =============================================================================
# Setup environment variables
# =============================================================================
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
IS_PULL_REQUEST=0
IS_DEV_BRANCH=0
IS_LINUX=0
IS_MACOS=0
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
IS_PULL_REQUEST=1
fi
if [ "$GITHUB_REF" == "refs/heads/dev" ]; then
IS_DEV_BRANCH=1
fi
if [ "$RUNNER_OS" == "Linux" ]; then
IS_LINUX=1
IS_MACOS=0
else
IS_LINUX=0
IS_MACOS=1
fi
# =============================================================================
# Print environment
# =============================================================================
echo "GITHUB_WORKFLOW=$GITHUB_WORKFLOW"
echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME"
echo "GITHUB_REF=$GITHUB_REF"
echo "RUNNER_OS=$RUNNER_OS"
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
echo "IS_DEV_BRANCH=$IS_DEV_BRANCH"
echo "IS_LINUX=$IS_LINUX"
echo "IS_MACOS=$IS_MACOS"
echo "Node $( node -v )"
echo "Npm $( npm -v )"
# =============================================================================
# Install packages
# =============================================================================
cd "$ROOT_DIR"
npm install
# =============================================================================
# Run test units. Only do it for pull requests and dev branch because we don't
# want it to randomly fail when trying to create a desktop release.
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
npm run test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Run linter for pull requests only. We also don't want this to make the desktop
# release randomly fail.
# =============================================================================
if [ "$IS_PULL_REQUEST" != "1" ]; then
npm run linter-ci ./
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Validate translations - this is needed as some users manually edit .po files
# (and often make mistakes) instead of using a proper tool like poedit. Doing it
# for Linux only is sufficient.
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ]; then
if [ "$IS_LINUX" == "1" ]; then
node packages/tools/validate-translation.js
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# =============================================================================
# Find out if we should run the build or not. Electron-builder gets stuck when
# building PRs so we disable it in this case. The Linux build should provide
# enough info if the app builds or not.
# https://github.com/electron-userland/electron-builder/issues/4263
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ]; then
if [ "$IS_MACOS" == "1" ]; then
exit 0
fi
fi
# =============================================================================
# Prepare the Electron app and build it
#
# If the current tag is a desktop release tag (starts with "v", such as
# "v1.4.7"), we build and publish to github
#
# Otherwise we only build but don't publish to GitHub. It helps finding
# out any issue in pull requests and dev branch.
# =============================================================================
cd "$ROOT_DIR/packages/app-desktop"
if [[ $GIT_TAG_NAME = v* ]]; then
echo "Building and publishing desktop application..."
USE_HARD_LINKS=false npm run dist
elif [[ $GIT_TAG_NAME = server-v* ]] && [[ $IS_LINUX = 1 ]]; then
echo "Building Docker Image..."
cd "$ROOT_DIR"
npm run buildServerDocker -- --tag-name $GIT_TAG_NAME
else
echo "Building but *not* publishing desktop application..."
USE_HARD_LINKS=false npm run dist -- --publish=never
fi

View File

@@ -0,0 +1,63 @@
name: Joplin Continuous Integration
on: [push, pull_request]
jobs:
Main:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
# Silence apt-get update errors (for example when a module doesn't
# exist) since otherwise it will make the whole build fails, even though
# it might work without update. libsecret-1-dev is required for keytar -
# https://github.com/atom/node-keytar
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update || true
sudo apt-get install -y gettext
sudo apt-get install -y libsecret-1-dev
- name: Install Docker Engine
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
run: |
sudo apt-get install -y apt-transport-https
sudo apt-get install -y ca-certificates
sudo apt-get install -y curl
sudo apt-get install -y gnupg
sudo apt-get install -y lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update || true
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
- uses: actions/checkout@v2
- uses: olegtarasov/get-tag@v2.1
- uses: actions/setup-node@v2
with:
node-version: '12'
# Login to Docker only if we're on a server release tag. If we run this on
# a pull request it will fail because the PR doesn't have access to
# secrets
- uses: docker/login-action@v1
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run script...
env:
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
IS_CONTINUOUS_INTEGRATION: 1
run: |
"${GITHUB_WORKSPACE}/.github/scripts/run_ci.sh"

27
.gitignore vendored
View File

@@ -126,6 +126,9 @@ packages/app-desktop/commands/openProfileDirectory.js.map
packages/app-desktop/commands/replaceMisspelling.d.ts packages/app-desktop/commands/replaceMisspelling.d.ts
packages/app-desktop/commands/replaceMisspelling.js packages/app-desktop/commands/replaceMisspelling.js
packages/app-desktop/commands/replaceMisspelling.js.map packages/app-desktop/commands/replaceMisspelling.js.map
packages/app-desktop/commands/restoreNoteRevision.d.ts
packages/app-desktop/commands/restoreNoteRevision.js
packages/app-desktop/commands/restoreNoteRevision.js.map
packages/app-desktop/commands/startExternalEditing.d.ts packages/app-desktop/commands/startExternalEditing.d.ts
packages/app-desktop/commands/startExternalEditing.js packages/app-desktop/commands/startExternalEditing.js
packages/app-desktop/commands/startExternalEditing.js.map packages/app-desktop/commands/startExternalEditing.js.map
@@ -261,6 +264,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteContentProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.d.ts packages/app-desktop/gui/MainScreen/commands/showNoteProperties.d.ts
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showPrompt.d.ts
packages/app-desktop/gui/MainScreen/commands/showPrompt.js
packages/app-desktop/gui/MainScreen/commands/showPrompt.js.map
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.d.ts packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
@@ -324,6 +330,9 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearch.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.d.ts packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js.map packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.d.ts packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map
@@ -807,6 +816,9 @@ packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinCloud.d.ts
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinCloud.js.map
packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map packages/lib/SyncTargetJoplinServer.js.map
@@ -954,6 +966,9 @@ packages/lib/models/dateTimeFormats.test.js.map
packages/lib/models/settings/FileHandler.d.ts packages/lib/models/settings/FileHandler.d.ts
packages/lib/models/settings/FileHandler.js packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/FileHandler.js.map packages/lib/models/settings/FileHandler.js.map
packages/lib/models/utils/itemCanBeEncrypted.d.ts
packages/lib/models/utils/itemCanBeEncrypted.js
packages/lib/models/utils/itemCanBeEncrypted.js.map
packages/lib/models/utils/paginatedFeed.d.ts packages/lib/models/utils/paginatedFeed.d.ts
packages/lib/models/utils/paginatedFeed.js packages/lib/models/utils/paginatedFeed.js
packages/lib/models/utils/paginatedFeed.js.map packages/lib/models/utils/paginatedFeed.js.map
@@ -1143,6 +1158,9 @@ packages/lib/services/interop/InteropService_Importer_Raw.js.map
packages/lib/services/interop/types.d.ts packages/lib/services/interop/types.d.ts
packages/lib/services/interop/types.js packages/lib/services/interop/types.js
packages/lib/services/interop/types.js.map packages/lib/services/interop/types.js.map
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.d.ts
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js.map
packages/lib/services/keychain/KeychainService.d.ts packages/lib/services/keychain/KeychainService.d.ts
packages/lib/services/keychain/KeychainService.js packages/lib/services/keychain/KeychainService.js
packages/lib/services/keychain/KeychainService.js.map packages/lib/services/keychain/KeychainService.js.map
@@ -1461,6 +1479,9 @@ packages/lib/uuid.js.map
packages/lib/versionInfo.d.ts packages/lib/versionInfo.d.ts
packages/lib/versionInfo.js packages/lib/versionInfo.js
packages/lib/versionInfo.js.map packages/lib/versionInfo.js.map
packages/plugin-repo-cli/commands/updateRelease.d.ts
packages/plugin-repo-cli/commands/updateRelease.js
packages/plugin-repo-cli/commands/updateRelease.js.map
packages/plugin-repo-cli/index.d.ts packages/plugin-repo-cli/index.d.ts
packages/plugin-repo-cli/index.js packages/plugin-repo-cli/index.js
packages/plugin-repo-cli/index.js.map packages/plugin-repo-cli/index.js.map
@@ -1575,6 +1596,9 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts packages/renderer/utils.d.ts
packages/renderer/utils.js packages/renderer/utils.js
packages/renderer/utils.js.map packages/renderer/utils.js.map
packages/tools/buildServerDocker.d.ts
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.js.map
packages/tools/generate-database-types.d.ts packages/tools/generate-database-types.d.ts
packages/tools/generate-database-types.js packages/tools/generate-database-types.js
packages/tools/generate-database-types.js.map packages/tools/generate-database-types.js.map
@@ -1599,4 +1623,7 @@ packages/tools/release-server.js.map
packages/tools/tool-utils.d.ts packages/tools/tool-utils.d.ts
packages/tools/tool-utils.js packages/tools/tool-utils.js
packages/tools/tool-utils.js.map packages/tools/tool-utils.js.map
packages/tools/update-readme-sponsors.d.ts
packages/tools/update-readme-sponsors.js
packages/tools/update-readme-sponsors.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@@ -1,138 +0,0 @@
# Only build tags (Doesn't work - doesn't build anything)
if: tag IS present OR type = pull_request OR branch = dev
rvm: 2.3.3
# It's important to only build production branches otherwise Electron Builder
# might take assets from dev branches and overwrite those of production.
# https://docs.travis-ci.com/user/customizing-the-build/#Building-Specific-Branches
branches:
only:
- master
- dev
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
matrix:
include:
- os: osx
osx_image: xcode12
language: node_js
node_js: "12"
cache:
npm: false
# Cache was disabled because when changing from node_js 10 to node_js 12
# it was still using build files from Node 10 when building SQLite which
# was making it fail. Might be ok to re-enable later on, although it doesn't
# make build that much faster.
#
# env:
# - ELECTRON_CACHE=$HOME/.cache/electron
# - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
sudo: required
dist: trusty
language: node_js
node_js: "12"
cache:
npm: false
# env:
# - ELECTRON_CACHE=$HOME/.cache/electron
# - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
# cache:
# directories:
# - node_modules
# - $HOME/.cache/electron
# - $HOME/.cache/electron-builder
before_install:
# HOMEBREW_NO_AUTO_UPDATE needed so that Homebrew doesn't upgrade to the next
# version, which requires Ruby 2.3, which is not available on the Travis VM.
# Silence apt-get update errors (for example when a module doesn't exist) since
# otherwise it will make the whole build fails, even though all we need is yarn.
# libsecret-1-dev is required for keytar - https://github.com/atom/node-keytar
- |
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
HOMEBREW_NO_AUTO_UPDATE=1 brew install yarn
else
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update || true
sudo apt-get install -y yarn
sudo apt-get install -y gettext
sudo apt-get install -y libsecret-1-dev
fi
script:
- |
# Prints some env variables
echo "TRAVIS_OS_NAME=$TRAVIS_OS_NAME"
echo "TRAVIS_BRANCH=$TRAVIS_BRANCH"
echo "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST"
echo "TRAVIS_TAG=$TRAVIS_TAG"
# Install tools
npm install
# Run test units.
# Only do it for pull requests because Travis randomly fails to run them
# and that would break the desktop release.
if [ "$TRAVIS_PULL_REQUEST" != "false" ] || [ "$TRAVIS_BRANCH" = "dev" ]; then
npm run test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# Run linter for pull requests only - this is so that
# bypassing eslint is allowed for urgent fixes.
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
npm run linter-ci ./
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# Validate translations - this is needed as some users manually
# edit .po files (and often make mistakes) instead of using a proper
# tool like poedit. Doing it for Linux only is sufficient.
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
if [ "$TRAVIS_OS_NAME" != "osx" ]; then
node packages/tools/validate-translation.js
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# Find out if we should run the build or not. Electron-builder gets stuck when
# building PRs so we disable it in this case. The Linux build should provide
# enough info if the app builds or not.
# https://github.com/electron-userland/electron-builder/issues/4263
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
exit 0
fi
fi
# Prepare the Electron app and build it
#
# If the current tag is a desktop release tag (starts with "v", such as
# "v1.4.7"), we build and publish to github
#
# Otherwise we only build but don't publish to GitHub. It helps finding
# out any issue in pull requests and dev branch.
cd packages/app-desktop
if [[ $TRAVIS_TAG = v* ]]; then
USE_HARD_LINKS=false npm run dist
else
USE_HARD_LINKS=false npm run dist -- --publish=never
fi

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,5 +1,3 @@
[![Travis Build Status](https://travis-ci.org/laurent22/joplin.svg?branch=master)](https://travis-ci.org/laurent22/joplin) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/laurent22/joplin?branch=master&passingText=master%20-%20OK&svg=true)](https://ci.appveyor.com/project/laurent22/joplin)
# Building the applications # Building the applications
The Joplin source code is hosted on a [monorepo](https://en.wikipedia.org/wiki/Monorepo) managed by Lerna. The usage of Lerna is mostly transparent as the needed commands have been moved to the root package.json and thus are invoked for example when running `npm install` or `npm run watch`. The main thing to know about Lerna is that it links the packages in the monorepo using `npm link`, so if you check the node_modules directory you will see links instead of actual directories for certain packages. This is something to keep in mind as these links can cause issues in some cases. The Joplin source code is hosted on a [monorepo](https://en.wikipedia.org/wiki/Monorepo) managed by Lerna. The usage of Lerna is mostly transparent as the needed commands have been moved to the root package.json and thus are invoked for example when running `npm install` or `npm run watch`. The main thing to know about Lerna is that it links the packages in the monorepo using `npm link`, so if you check the node_modules directory you will see links instead of actual directories for certain packages. This is something to keep in mind as these links can cause issues in some cases.
@@ -64,7 +62,7 @@ Normally the **bundler** should start automatically with the application. If it
npm install npm install
npm run watch # To watch for changes npm run watch # To watch for changes
To test the extension please refer to the relevant pages for each browser: [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Trying_it_out) / [Chrome](https://developer.chrome.com/extensions/faq#faq-dev-01). Please note that the extension in dev mode will only connect to a dev instance of the desktop app (and vice-versa). To test the extension please refer to the relevant pages for each browser: [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Trying_it_out) / [Chrome](https://developer.chrome.com/docs/extensions/mv3/getstarted/). Please note that the extension in dev mode will only connect to a dev instance of the desktop app (and vice-versa).
## Watching files ## Watching files

230
README.md
View File

@@ -36,7 +36,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.8.5/Jo
Operating System | Download | Alt. Download Operating System | Download | Alt. Download
---|---|--- ---|---|---
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.7.5/joplin-v1.7.5.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.7.5/joplin-v1.7.5-32bit.apk) Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.0.2/joplin-v2.0.2.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.0.2/joplin-v2.0.2-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | - iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | -
## Terminal application ## Terminal application
@@ -64,17 +64,19 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
# Sponsors # Sponsors
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href=" https://tranio.com/italy/"><img title="Tranio" width="256" src="https://joplinapp.org/images/sponsors/Tranio.png"/></a>
* * * * * *
| | | | <!-- SPONSORS -->
| :---: | :---: | :---: | | | | | |
| <img width="50" src="https://avatars0.githubusercontent.com/u/6979755?s=96&v=4"/></br>[Devon Zuegel](https://github.com/devonzuegel) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[小西 孝宗](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[Alexander van der Berg](https://github.com/avanderberg) | :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1168659?s=96&v=4"/></br>[Nicholas Head](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[Frank Bloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[Thomas Broussard](https://github.com/thomasbroussard) | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[Brandon Johnson](https://github.com/dbrandonjohnson) | <img width="50" src="https://avatars1.githubusercontent.com/u/3061769?s=96&v=4"/></br>[@cnagy](https://github.com/c-nagy) | <img width="50" src="https://avatars3.githubusercontent.com/u/53228972?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl) | <img width="50" src="https://avatars2.githubusercontent.com/u/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) | <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) | <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) |
| <img width="50" src="https://avatars.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars3.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jess Sullivan](https://github.com/jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | <img width="50" src="https://avatars2.githubusercontent.com/u/53228972?s=96&v=4"/></br>[wasteisobscene](https://github.com/wasteisobscene) | |
<!-- SPONSORS -->
<!-- TOC --> <!-- TOC -->
# Table of contents # Table of contents
@@ -407,6 +409,12 @@ For more information see [Plugins](https://github.com/laurent22/joplin/blob/dev/
Joplin implements the SQLite Full Text Search (FTS4) extension. It means the content of all the notes is indexed in real time and search queries return results very fast. Both [Simple FTS Queries](https://www.sqlite.org/fts3.html#simple_fts_queries) and [Full-Text Index Queries](https://www.sqlite.org/fts3.html#full_text_index_queries) are supported. See below for the list of supported queries: Joplin implements the SQLite Full Text Search (FTS4) extension. It means the content of all the notes is indexed in real time and search queries return results very fast. Both [Simple FTS Queries](https://www.sqlite.org/fts3.html#simple_fts_queries) and [Full-Text Index Queries](https://www.sqlite.org/fts3.html#full_text_index_queries) are supported. See below for the list of supported queries:
One caveat of SQLite FTS is that it does not support languages which do not use Latin word boundaries (spaces, tabs, punctuation). To solve this issue, Joplin has a custom search mode, that does not use FTS, but still has all of its features (multi term search, filters, etc.). One of its drawbacks is that it can get slow on larger note collections. Also, the sorting of the results will be less accurate, as the ranking algorithm (BM25) is, for now, only implemented for FTS. Finally, in this mode there are no restrictions on using the `*` wildcard (`swim*`, `*swim` and `ast*rix` all work). This search mode is currently enabled if one of the following languages are detected:
- Chinese
- Japanese
- Korean
- Thai
## Supported queries ## Supported queries
Search type | Description | Example Search type | Description | Example
@@ -511,47 +519,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED --> <!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done &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) | [Whaell O](mailto:Whaell@protonmail.com) | 96% <img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 95%
![](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 | 30% <img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 30%
![](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) | 75% <img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | 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) | 74%
![](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) | | 58% <img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 57%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 83% <img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 82%
![](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) | [Milo Ivir](mailto:mail@milotype.de) | 96% <img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 100%
![](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) | 86% <img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 100%
![](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: | 96% <img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | 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: | 99%
![](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) | [Atalanttore](mailto:atalanttore@googlemail.com) | 95% <img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 94%
![](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) | | 57% <img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 56%
![](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% <img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | 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% <img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | 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) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | 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) | 94%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 33% <img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 32%
![](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) | mrkaato | 94% <img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 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% <img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 98%
![](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) | 38% <img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 38%
![](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) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 93% <img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 92%
![](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) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 99%
![](https://joplinapp.org/images/flags/country-4x3/hu.png) | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 88% <img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 88%
![](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) | | 92% <img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 91%
![](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) | 95% <img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 94%
![](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) | 76% <img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 75%
![](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) | 71% <img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 71%
![](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) | [konhi](mailto:hello.konhi@gmail.com) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 94%
![](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) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 94%
![](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) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 94%
![](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) | 66% <img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 66%
![](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) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 96% <img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 95%
![](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) | 61% <img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 61%
![](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) | | 45% <img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 45%
![](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) | | 73% <img src="https://joplinapp.org/images/flags/country-4x3/vi.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 73%
![](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% <img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | 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/ua.png) | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.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) | 97% <img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 96%
![](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) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71% <img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 94% <img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [南宫小骏](mailto:jackytsu@vip.qq.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) | 92% <img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Po-Chiang Chao](mailto:BobChao%29%20%28bobchao@gmail.com) | 99%
![](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% <img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [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% <img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 99%
<!-- LOCALE-TABLE-AUTO-GENERATED --> <!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors # Contributors
@@ -561,52 +569,80 @@ Thank you to everyone who've contributed to Joplin's source code!
<!-- CONTRIBUTORS-TABLE-AUTO-GENERATED --> <!-- CONTRIBUTORS-TABLE-AUTO-GENERATED -->
| | | | | | | | | | | |
| :---: | :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1285584?v=4"/></br>[laurent22](https://api.github.com/users/laurent22) | <img width="50" src="https://avatars3.githubusercontent.com/u/223439?v=4"/></br>[tessus](https://api.github.com/users/tessus) | <img width="50" src="https://avatars0.githubusercontent.com/u/1732810?v=4"/></br>[mic704b](https://api.github.com/users/mic704b) | <img width="50" src="https://avatars3.githubusercontent.com/u/2179547?v=4"/></br>[CalebJohn](https://api.github.com/users/CalebJohn) | <img width="50" src="https://avatars1.githubusercontent.com/u/3542031?v=4"/></br>[PackElend](https://api.github.com/users/PackElend) | | <img width="50" src="https://avatars.githubusercontent.com/u/1285584?v=4"/></br>[laurent22](https://github.com/laurent22) | <img width="50" src="https://avatars.githubusercontent.com/u/223439?v=4"/></br>[tessus](https://github.com/tessus) | <img width="50" src="https://avatars.githubusercontent.com/u/2179547?v=4"/></br>[CalebJohn](https://github.com/CalebJohn) | <img width="50" src="https://avatars.githubusercontent.com/u/1732810?v=4"/></br>[mic704b](https://github.com/mic704b) | <img width="50" src="https://avatars.githubusercontent.com/u/995612?v=4"/></br>[roman-r-m](https://github.com/roman-r-m) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/4553672?v=4"/></br>[tanrax](https://api.github.com/users/tanrax) | <img width="50" src="https://avatars0.githubusercontent.com/u/8701534?v=4"/></br>[rtmkrlv](https://api.github.com/users/rtmkrlv) | <img width="50" src="https://avatars3.githubusercontent.com/u/10997189?v=4"/></br>[fmrtn](https://api.github.com/users/fmrtn) | <img width="50" src="https://avatars1.githubusercontent.com/u/29672555?v=4"/></br>[genneko](https://api.github.com/users/genneko) | <img width="50" src="https://avatars1.githubusercontent.com/u/6979755?v=4"/></br>[devonzuegel](https://api.github.com/users/devonzuegel) | | <img width="50" src="https://avatars.githubusercontent.com/u/29672555?v=4"/></br>[genneko](https://github.com/genneko) | <img width="50" src="https://avatars.githubusercontent.com/u/63491353?v=4"/></br>[j-krl](https://github.com/j-krl) | <img width="50" src="https://avatars.githubusercontent.com/u/4553672?v=4"/></br>[tanrax](https://github.com/tanrax) | <img width="50" src="https://avatars.githubusercontent.com/u/30305957?v=4"/></br>[naviji](https://github.com/naviji) | <img width="50" src="https://avatars.githubusercontent.com/u/3542031?v=4"/></br>[PackElend](https://github.com/PackElend) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/16101778?v=4"/></br>[gabcoh](https://api.github.com/users/gabcoh) | <img width="50" src="https://avatars3.githubusercontent.com/u/10927304?v=4"/></br>[matsest](https://api.github.com/users/matsest) | <img width="50" src="https://avatars0.githubusercontent.com/u/6319051?v=4"/></br>[abonte](https://api.github.com/users/abonte) | <img width="50" src="https://avatars2.githubusercontent.com/u/1685517?v=4"/></br>[Abijeet](https://api.github.com/users/Abijeet) | <img width="50" src="https://avatars0.githubusercontent.com/u/27751740?v=4"/></br>[ishantgupta777](https://api.github.com/users/ishantgupta777) | | <img width="50" src="https://avatars.githubusercontent.com/u/8701534?v=4"/></br>[rtmkrlv](https://github.com/rtmkrlv) | <img width="50" src="https://avatars.githubusercontent.com/u/10997189?v=4"/></br>[fmrtn](https://github.com/fmrtn) | <img width="50" src="https://avatars.githubusercontent.com/u/4374338?v=4"/></br>[potatogim](https://github.com/potatogim) | <img width="50" src="https://avatars.githubusercontent.com/u/6979755?v=4"/></br>[devonzuegel](https://github.com/devonzuegel) | <img width="50" src="https://avatars.githubusercontent.com/u/26695184?v=4"/></br>[anjulalk](https://github.com/anjulalk) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/208212?v=4"/></br>[foxmask](https://api.github.com/users/foxmask) | <img width="50" src="https://avatars2.githubusercontent.com/u/6557454?v=4"/></br>[innocuo](https://api.github.com/users/innocuo) | <img width="50" src="https://avatars1.githubusercontent.com/u/26695184?v=4"/></br>[anjulalk](https://api.github.com/users/anjulalk) | <img width="50" src="https://avatars1.githubusercontent.com/u/44024553?v=4"/></br>[rabeehrz](https://api.github.com/users/rabeehrz) | <img width="50" src="https://avatars0.githubusercontent.com/u/35633575?v=4"/></br>[coderrsid](https://api.github.com/users/coderrsid) | | <img width="50" src="https://avatars.githubusercontent.com/u/16101778?v=4"/></br>[gabcoh](https://github.com/gabcoh) | <img width="50" src="https://avatars.githubusercontent.com/u/10927304?v=4"/></br>[matsest](https://github.com/matsest) | <img width="50" src="https://avatars.githubusercontent.com/u/6319051?v=4"/></br>[abonte](https://github.com/abonte) | <img width="50" src="https://avatars.githubusercontent.com/u/1685517?v=4"/></br>[Abijeet](https://github.com/Abijeet) | <img width="50" src="https://avatars.githubusercontent.com/u/27751740?v=4"/></br>[ishantgupta777](https://github.com/ishantgupta777) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/4237724?v=4"/></br>[alexdevero](https://api.github.com/users/alexdevero) | <img width="50" src="https://avatars3.githubusercontent.com/u/35904727?v=4"/></br>[Runo-saduwa](https://api.github.com/users/Runo-saduwa) | <img width="50" src="https://avatars2.githubusercontent.com/u/5365582?v=4"/></br>[marcosvega91](https://api.github.com/users/marcosvega91) | <img width="50" src="https://avatars3.githubusercontent.com/u/37639389?v=4"/></br>[petrz12](https://api.github.com/users/petrz12) | <img width="50" src="https://avatars0.githubusercontent.com/u/3194829?v=4"/></br>[moltenform](https://api.github.com/users/moltenform) | | <img width="50" src="https://avatars.githubusercontent.com/u/24863925?v=4"/></br>[JackGruber](https://github.com/JackGruber) | <img width="50" src="https://avatars.githubusercontent.com/u/2063957?v=4"/></br>[Ardakilic](https://github.com/Ardakilic) | <img width="50" src="https://avatars.githubusercontent.com/u/44024553?v=4"/></br>[rabeehrz](https://github.com/rabeehrz) | <img width="50" src="https://avatars.githubusercontent.com/u/35633575?v=4"/></br>[coderrsid](https://github.com/coderrsid) | <img width="50" src="https://avatars.githubusercontent.com/u/208212?v=4"/></br>[foxmask](https://github.com/foxmask) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/5199995?v=4"/></br>[zuphilip](https://api.github.com/users/zuphilip) | <img width="50" src="https://avatars1.githubusercontent.com/u/1904967?v=4"/></br>[readingsnail](https://api.github.com/users/readingsnail) | <img width="50" src="https://avatars0.githubusercontent.com/u/3985557?v=4"/></br>[XarisA](https://api.github.com/users/XarisA) | <img width="50" src="https://avatars2.githubusercontent.com/u/4245227?v=4"/></br>[zblesk](https://api.github.com/users/zblesk) | <img width="50" src="https://avatars2.githubusercontent.com/u/31567272?v=4"/></br>[0ndrey](https://api.github.com/users/0ndrey) | | <img width="50" src="https://avatars.githubusercontent.com/u/6557454?v=4"/></br>[innocuo](https://github.com/innocuo) | <img width="50" src="https://avatars.githubusercontent.com/u/54268438?v=4"/></br>[Rahulm2310](https://github.com/Rahulm2310) | <img width="50" src="https://avatars.githubusercontent.com/u/1904967?v=4"/></br>[readingsnail](https://github.com/readingsnail) | <img width="50" src="https://avatars.githubusercontent.com/u/7415668?v=4"/></br>[mablin7](https://github.com/mablin7) | <img width="50" src="https://avatars.githubusercontent.com/u/3985557?v=4"/></br>[XarisA](https://github.com/XarisA) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/12906090?v=4"/></br>[amitsin6h](https://api.github.com/users/amitsin6h) | <img width="50" src="https://avatars3.githubusercontent.com/u/23281486?v=4"/></br>[martonpaulo](https://api.github.com/users/martonpaulo) | <img width="50" src="https://avatars3.githubusercontent.com/u/4497566?v=4"/></br>[rccavalcanti](https://api.github.com/users/rccavalcanti) | <img width="50" src="https://avatars0.githubusercontent.com/u/54268438?v=4"/></br>[Rahulm2310](https://api.github.com/users/Rahulm2310) | <img width="50" src="https://avatars0.githubusercontent.com/u/559346?v=4"/></br>[metbril](https://api.github.com/users/metbril) | | <img width="50" src="https://avatars.githubusercontent.com/u/49979415?v=4"/></br>[jonath92](https://github.com/jonath92) | <img width="50" src="https://avatars.githubusercontent.com/u/4237724?v=4"/></br>[alexdevero](https://github.com/alexdevero) | <img width="50" src="https://avatars.githubusercontent.com/u/35904727?v=4"/></br>[Runo-saduwa](https://github.com/Runo-saduwa) | <img width="50" src="https://avatars.githubusercontent.com/u/5365582?v=4"/></br>[marcosvega91](https://github.com/marcosvega91) | <img width="50" src="https://avatars.githubusercontent.com/u/37639389?v=4"/></br>[petrz12](https://github.com/petrz12) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1540054?v=4"/></br>[ShaneKilkelly](https://api.github.com/users/ShaneKilkelly) | <img width="50" src="https://avatars1.githubusercontent.com/u/6734573?v=4"/></br>[stweil](https://api.github.com/users/stweil) | <img width="50" src="https://avatars3.githubusercontent.com/u/937861?v=4"/></br>[archont00](https://api.github.com/users/archont00) | <img width="50" src="https://avatars3.githubusercontent.com/u/32770029?v=4"/></br>[bradmcl](https://api.github.com/users/bradmcl) | <img width="50" src="https://avatars1.githubusercontent.com/u/22592201?v=4"/></br>[tfinnberg](https://api.github.com/users/tfinnberg) | | <img width="50" src="https://avatars.githubusercontent.com/u/51550769?v=4"/></br>[rnbastos](https://github.com/rnbastos) | <img width="50" src="https://avatars.githubusercontent.com/u/32396?v=4"/></br>[ProgramFan](https://github.com/ProgramFan) | <img width="50" src="https://avatars.githubusercontent.com/u/4245227?v=4"/></br>[zblesk](https://github.com/zblesk) | <img width="50" src="https://avatars.githubusercontent.com/u/5730052?v=4"/></br>[vsimkus](https://github.com/vsimkus) | <img width="50" src="https://avatars.githubusercontent.com/u/3194829?v=4"/></br>[moltenform](https://github.com/moltenform) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/3870964?v=4"/></br>[marcushill](https://api.github.com/users/marcushill) | <img width="50" src="https://avatars3.githubusercontent.com/u/102242?v=4"/></br>[nathanleiby](https://api.github.com/users/nathanleiby) | <img width="50" src="https://avatars0.githubusercontent.com/u/226708?v=4"/></br>[RaphaelKimmig](https://api.github.com/users/RaphaelKimmig) | <img width="50" src="https://avatars0.githubusercontent.com/u/17768566?v=4"/></br>[RenatoXSR](https://api.github.com/users/RenatoXSR) | <img width="50" src="https://avatars1.githubusercontent.com/u/36303913?v=4"/></br>[sensor-freak](https://api.github.com/users/sensor-freak) | | <img width="50" src="https://avatars.githubusercontent.com/u/36989112?v=4"/></br>[nishantwrp](https://github.com/nishantwrp) | <img width="50" src="https://avatars.githubusercontent.com/u/5199995?v=4"/></br>[zuphilip](https://github.com/zuphilip) | <img width="50" src="https://avatars.githubusercontent.com/u/54576074?v=4"/></br>[Rishabh-malhotraa](https://github.com/Rishabh-malhotraa) | <img width="50" src="https://avatars.githubusercontent.com/u/559346?v=4"/></br>[metbril](https://github.com/metbril) | <img width="50" src="https://avatars.githubusercontent.com/u/47623588?v=4"/></br>[WhiredPlanck](https://github.com/WhiredPlanck) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/2063957?v=4"/></br>[Ardakilic](https://api.github.com/users/Ardakilic) | <img width="50" src="https://avatars3.githubusercontent.com/u/21161146?v=4"/></br>[BartBucknill](https://api.github.com/users/BartBucknill) | <img width="50" src="https://avatars3.githubusercontent.com/u/2494769?v=4"/></br>[mrwulf](https://api.github.com/users/mrwulf) | <img width="50" src="https://avatars2.githubusercontent.com/u/560571?v=4"/></br>[chrisb86](https://api.github.com/users/chrisb86) | <img width="50" src="https://avatars3.githubusercontent.com/u/1686759?v=4"/></br>[chrmoritz](https://api.github.com/users/chrmoritz) | | <img width="50" src="https://avatars.githubusercontent.com/u/43657314?v=4"/></br>[milotype](https://github.com/milotype) | <img width="50" src="https://avatars.githubusercontent.com/u/32196447?v=4"/></br>[yaozeye](https://github.com/yaozeye) | <img width="50" src="https://avatars.githubusercontent.com/u/12264626?v=4"/></br>[ylc395](https://github.com/ylc395) | <img width="50" src="https://avatars.githubusercontent.com/u/17768566?v=4"/></br>[RenatoXSR](https://github.com/RenatoXSR) | <img width="50" src="https://avatars.githubusercontent.com/u/54888685?v=4"/></br>[RedDocMD](https://github.com/RedDocMD) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/5001259?v=4"/></br>[ethan42411](https://api.github.com/users/ethan42411) | <img width="50" src="https://avatars2.githubusercontent.com/u/2733783?v=4"/></br>[JOJ0](https://api.github.com/users/JOJ0) | <img width="50" src="https://avatars2.githubusercontent.com/u/3140223?v=4"/></br>[jdrobertso](https://api.github.com/users/jdrobertso) | <img width="50" src="https://avatars2.githubusercontent.com/u/339645?v=4"/></br>[jmontane](https://api.github.com/users/jmontane) | <img width="50" src="https://avatars2.githubusercontent.com/u/4168339?v=4"/></br>[solariz](https://api.github.com/users/solariz) | | <img width="50" src="https://avatars.githubusercontent.com/u/31567272?v=4"/></br>[q1011](https://github.com/q1011) | <img width="50" src="https://avatars.githubusercontent.com/u/12906090?v=4"/></br>[amitsin6h](https://github.com/amitsin6h) | <img width="50" src="https://avatars.githubusercontent.com/u/628474?v=4"/></br>[Atalanttore](https://github.com/Atalanttore) | <img width="50" src="https://avatars.githubusercontent.com/u/42747216?v=4"/></br>[Mannivu](https://github.com/Mannivu) | <img width="50" src="https://avatars.githubusercontent.com/u/23281486?v=4"/></br>[martonpaulo](https://github.com/martonpaulo) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/390889?v=4"/></br>[mmahmoudian](https://api.github.com/users/mmahmoudian) | <img width="50" src="https://avatars1.githubusercontent.com/u/25288?v=4"/></br>[maicki](https://api.github.com/users/maicki) | <img width="50" src="https://avatars3.githubusercontent.com/u/2136373?v=4"/></br>[mjjzf](https://api.github.com/users/mjjzf) | <img width="50" src="https://avatars3.githubusercontent.com/u/30305957?v=4"/></br>[naviji](https://api.github.com/users/naviji) | <img width="50" src="https://avatars3.githubusercontent.com/u/27608187?v=4"/></br>[rt-oliveira](https://api.github.com/users/rt-oliveira) | | <img width="50" src="https://avatars.githubusercontent.com/u/390889?v=4"/></br>[mmahmoudian](https://github.com/mmahmoudian) | <img width="50" src="https://avatars.githubusercontent.com/u/4497566?v=4"/></br>[rccavalcanti](https://github.com/rccavalcanti) | <img width="50" src="https://avatars.githubusercontent.com/u/1540054?v=4"/></br>[ShaneKilkelly](https://github.com/ShaneKilkelly) | <img width="50" src="https://avatars.githubusercontent.com/u/7091080?v=4"/></br>[sinkuu](https://github.com/sinkuu) | <img width="50" src="https://avatars.githubusercontent.com/u/6734573?v=4"/></br>[stweil](https://github.com/stweil) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/54576074?v=4"/></br>[Rishgod](https://api.github.com/users/Rishgod) | <img width="50" src="https://avatars0.githubusercontent.com/u/2486806?v=4"/></br>[sebastienjust](https://api.github.com/users/sebastienjust) | <img width="50" src="https://avatars2.githubusercontent.com/u/28362310?v=4"/></br>[sealch](https://api.github.com/users/sealch) | <img width="50" src="https://avatars1.githubusercontent.com/u/34258070?v=4"/></br>[StarFang208](https://api.github.com/users/StarFang208) | <img width="50" src="https://avatars2.githubusercontent.com/u/1782292?v=4"/></br>[SubodhDahal](https://api.github.com/users/SubodhDahal) | | <img width="50" src="https://avatars.githubusercontent.com/u/692072?v=4"/></br>[conyx](https://github.com/conyx) | <img width="50" src="https://avatars.githubusercontent.com/u/49116134?v=4"/></br>[anihm136](https://github.com/anihm136) | <img width="50" src="https://avatars.githubusercontent.com/u/937861?v=4"/></br>[archont00](https://github.com/archont00) | <img width="50" src="https://avatars.githubusercontent.com/u/32770029?v=4"/></br>[bradmcl](https://github.com/bradmcl) | <img width="50" src="https://avatars.githubusercontent.com/u/22592201?v=4"/></br>[tfinnberg](https://github.com/tfinnberg) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/5912371?v=4"/></br>[TobiasDev](https://api.github.com/users/TobiasDev) | <img width="50" src="https://avatars2.githubusercontent.com/u/692072?v=4"/></br>[conyx](https://api.github.com/users/conyx) | <img width="50" src="https://avatars2.githubusercontent.com/u/5730052?v=4"/></br>[vsimkus](https://api.github.com/users/vsimkus) | <img width="50" src="https://avatars1.githubusercontent.com/u/4079047?v=4"/></br>[Zorbeyd](https://api.github.com/users/Zorbeyd) | <img width="50" src="https://avatars3.githubusercontent.com/u/5077221?v=4"/></br>[axq](https://api.github.com/users/axq) | | <img width="50" src="https://avatars.githubusercontent.com/u/8716226?v=4"/></br>[amandamcg](https://github.com/amandamcg) | <img width="50" src="https://avatars.githubusercontent.com/u/3870964?v=4"/></br>[marcushill](https://github.com/marcushill) | <img width="50" src="https://avatars.githubusercontent.com/u/102242?v=4"/></br>[nathanleiby](https://github.com/nathanleiby) | <img width="50" src="https://avatars.githubusercontent.com/u/226708?v=4"/></br>[RaphaelKimmig](https://github.com/RaphaelKimmig) | <img width="50" src="https://avatars.githubusercontent.com/u/20461071?v=4"/></br>[Vaso3](https://github.com/Vaso3) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/8808502?v=4"/></br>[barbowza](https://api.github.com/users/barbowza) | <img width="50" src="https://avatars1.githubusercontent.com/u/4316805?v=4"/></br>[lightray22](https://api.github.com/users/lightray22) | <img width="50" src="https://avatars0.githubusercontent.com/u/17399340?v=4"/></br>[pf-siedler](https://api.github.com/users/pf-siedler) | <img width="50" src="https://avatars1.githubusercontent.com/u/17232523?v=4"/></br>[ruuti](https://api.github.com/users/ruuti) | <img width="50" src="https://avatars2.githubusercontent.com/u/23638148?v=4"/></br>[s1nceri7y](https://api.github.com/users/s1nceri7y) | | <img width="50" src="https://avatars.githubusercontent.com/u/36303913?v=4"/></br>[sensor-freak](https://github.com/sensor-freak) | <img width="50" src="https://avatars.githubusercontent.com/u/63918341?v=4"/></br>[lkiThakur](https://github.com/lkiThakur) | <img width="50" src="https://avatars.githubusercontent.com/u/28987176?v=4"/></br>[infinity052](https://github.com/infinity052) | <img width="50" src="https://avatars.githubusercontent.com/u/21161146?v=4"/></br>[BartBucknill](https://github.com/BartBucknill) | <img width="50" src="https://avatars.githubusercontent.com/u/2494769?v=4"/></br>[mrwulf](https://github.com/mrwulf) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/10117386?v=4"/></br>[kornava](https://api.github.com/users/kornava) | <img width="50" src="https://avatars1.githubusercontent.com/u/7471938?v=4"/></br>[ShuiHuo](https://api.github.com/users/ShuiHuo) | <img width="50" src="https://avatars2.githubusercontent.com/u/11596277?v=4"/></br>[ikunya](https://api.github.com/users/ikunya) | <img width="50" src="https://avatars3.githubusercontent.com/u/59133880?v=4"/></br>[bedwardly-down](https://api.github.com/users/bedwardly-down) | <img width="50" src="https://avatars2.githubusercontent.com/u/47456195?v=4"/></br>[hexclover](https://api.github.com/users/hexclover) | | <img width="50" src="https://avatars.githubusercontent.com/u/560571?v=4"/></br>[chrisb86](https://github.com/chrisb86) | <img width="50" src="https://avatars.githubusercontent.com/u/1686759?v=4"/></br>[chrmoritz](https://github.com/chrmoritz) | <img width="50" src="https://avatars.githubusercontent.com/u/58074586?v=4"/></br>[Daeraxa](https://github.com/Daeraxa) | <img width="50" src="https://avatars.githubusercontent.com/u/71190696?v=4"/></br>[Elaborendum](https://github.com/Elaborendum) | <img width="50" src="https://avatars.githubusercontent.com/u/5001259?v=4"/></br>[ethan42411](https://github.com/ethan42411) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/45535789?v=4"/></br>[2jaeyeol](https://api.github.com/users/2jaeyeol) | <img width="50" src="https://avatars1.githubusercontent.com/u/15862474?v=4"/></br>[aaronxn](https://api.github.com/users/aaronxn) | <img width="50" src="https://avatars1.githubusercontent.com/u/3660978?v=4"/></br>[alanfortlink](https://api.github.com/users/alanfortlink) | <img width="50" src="https://avatars3.githubusercontent.com/u/14836659?v=4"/></br>[apankratov](https://api.github.com/users/apankratov) | <img width="50" src="https://avatars1.githubusercontent.com/u/7045739?v=4"/></br>[teterkin](https://api.github.com/users/teterkin) | | <img width="50" src="https://avatars.githubusercontent.com/u/2733783?v=4"/></br>[JOJ0](https://github.com/JOJ0) | <img width="50" src="https://avatars.githubusercontent.com/u/17108695?v=4"/></br>[jalajcodes](https://github.com/jalajcodes) | <img width="50" src="https://avatars.githubusercontent.com/u/238088?v=4"/></br>[jblunck](https://github.com/jblunck) | <img width="50" src="https://avatars.githubusercontent.com/u/3140223?v=4"/></br>[jdrobertso](https://github.com/jdrobertso) | <img width="50" src="https://avatars.githubusercontent.com/u/37297218?v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/41290751?v=4"/></br>[serenitatis](https://api.github.com/users/serenitatis) | <img width="50" src="https://avatars2.githubusercontent.com/u/4408379?v=4"/></br>[lex111](https://api.github.com/users/lex111) | <img width="50" src="https://avatars2.githubusercontent.com/u/5417051?v=4"/></br>[tekdel](https://api.github.com/users/tekdel) | <img width="50" src="https://avatars1.githubusercontent.com/u/498326?v=4"/></br>[Shaxine](https://api.github.com/users/Shaxine) | <img width="50" src="https://avatars0.githubusercontent.com/u/201215?v=4"/></br>[assimd](https://api.github.com/users/assimd) | | <img width="50" src="https://avatars.githubusercontent.com/u/339645?v=4"/></br>[jmontane](https://github.com/jmontane) | <img width="50" src="https://avatars.githubusercontent.com/u/69011?v=4"/></br>[johanhammar](https://github.com/johanhammar) | <img width="50" src="https://avatars.githubusercontent.com/u/4168339?v=4"/></br>[solariz](https://github.com/solariz) | <img width="50" src="https://avatars.githubusercontent.com/u/25288?v=4"/></br>[maicki](https://github.com/maicki) | <img width="50" src="https://avatars.githubusercontent.com/u/2136373?v=4"/></br>[mjjzf](https://github.com/mjjzf) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/42698687?v=4"/></br>[baymoe](https://api.github.com/users/baymoe) | <img width="50" src="https://avatars2.githubusercontent.com/u/7034200?v=4"/></br>[bimlas](https://api.github.com/users/bimlas) | <img width="50" src="https://avatars0.githubusercontent.com/u/16287077?v=4"/></br>[carlbordum](https://api.github.com/users/carlbordum) | <img width="50" src="https://avatars0.githubusercontent.com/u/105843?v=4"/></br>[chaifeng](https://api.github.com/users/chaifeng) | <img width="50" src="https://avatars2.githubusercontent.com/u/549349?v=4"/></br>[charles-e](https://api.github.com/users/charles-e) | | <img width="50" src="https://avatars.githubusercontent.com/u/27608187?v=4"/></br>[rt-oliveira](https://github.com/rt-oliveira) | <img width="50" src="https://avatars.githubusercontent.com/u/2486806?v=4"/></br>[sebastienjust](https://github.com/sebastienjust) | <img width="50" src="https://avatars.githubusercontent.com/u/28362310?v=4"/></br>[sealch](https://github.com/sealch) | <img width="50" src="https://avatars.githubusercontent.com/u/34258070?v=4"/></br>[StarFang208](https://github.com/StarFang208) | <img width="50" src="https://avatars.githubusercontent.com/u/59690052?v=4"/></br>[Subhra264](https://github.com/Subhra264) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/2348463?v=4"/></br>[Techwolf12](https://api.github.com/users/Techwolf12) | <img width="50" src="https://avatars0.githubusercontent.com/u/2282880?v=4"/></br>[cloudtrends](https://api.github.com/users/cloudtrends) | <img width="50" src="https://avatars2.githubusercontent.com/u/1044056?v=4"/></br>[daniellandau](https://api.github.com/users/daniellandau) | <img width="50" src="https://avatars2.githubusercontent.com/u/26189247?v=4"/></br>[daukadolt](https://api.github.com/users/daukadolt) | <img width="50" src="https://avatars2.githubusercontent.com/u/28535750?v=4"/></br>[NeverMendel](https://api.github.com/users/NeverMendel) | | <img width="50" src="https://avatars.githubusercontent.com/u/1782292?v=4"/></br>[SubodhDahal](https://github.com/SubodhDahal) | <img width="50" src="https://avatars.githubusercontent.com/u/5912371?v=4"/></br>[TobiasDev](https://github.com/TobiasDev) | <img width="50" src="https://avatars.githubusercontent.com/u/13502069?v=4"/></br>[Whaell](https://github.com/Whaell) | <img width="50" src="https://avatars.githubusercontent.com/u/29891001?v=4"/></br>[jyuvaraj03](https://github.com/jyuvaraj03) | <img width="50" src="https://avatars.githubusercontent.com/u/15380913?v=4"/></br>[kowalskidev](https://github.com/kowalskidev) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/11378282?v=4"/></br>[diego-betto](https://api.github.com/users/diego-betto) | <img width="50" src="https://avatars0.githubusercontent.com/u/215270?v=4"/></br>[erdody](https://api.github.com/users/erdody) | <img width="50" src="https://avatars0.githubusercontent.com/u/10371667?v=4"/></br>[domgoodwin](https://api.github.com/users/domgoodwin) | <img width="50" src="https://avatars3.githubusercontent.com/u/72066?v=4"/></br>[b4mboo](https://api.github.com/users/b4mboo) | <img width="50" src="https://avatars0.githubusercontent.com/u/5131923?v=4"/></br>[donbowman](https://api.github.com/users/donbowman) | | <img width="50" src="https://avatars.githubusercontent.com/u/337455?v=4"/></br>[alexchee](https://github.com/alexchee) | <img width="50" src="https://avatars.githubusercontent.com/u/5077221?v=4"/></br>[axq](https://github.com/axq) | <img width="50" src="https://avatars.githubusercontent.com/u/8808502?v=4"/></br>[barbowza](https://github.com/barbowza) | <img width="50" src="https://avatars.githubusercontent.com/u/42007357?v=4"/></br>[eresytter](https://github.com/eresytter) | <img width="50" src="https://avatars.githubusercontent.com/u/4316805?v=4"/></br>[lightray22](https://github.com/lightray22) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/47756?v=4"/></br>[dflock](https://api.github.com/users/dflock) | <img width="50" src="https://avatars0.githubusercontent.com/u/7990534?v=4"/></br>[drobilica](https://api.github.com/users/drobilica) | <img width="50" src="https://avatars3.githubusercontent.com/u/1962738?v=4"/></br>[einverne](https://api.github.com/users/einverne) | <img width="50" src="https://avatars0.githubusercontent.com/u/628474?v=4"/></br>[Atalanttore](https://api.github.com/users/Atalanttore) | <img width="50" src="https://avatars1.githubusercontent.com/u/16492558?v=4"/></br>[eodeluga](https://api.github.com/users/eodeluga) | | <img width="50" src="https://avatars.githubusercontent.com/u/11711053?v=4"/></br>[lscolombo](https://github.com/lscolombo) | <img width="50" src="https://avatars.githubusercontent.com/u/36228623?v=4"/></br>[mrkaato](https://github.com/mrkaato) | <img width="50" src="https://avatars.githubusercontent.com/u/17399340?v=4"/></br>[pf-siedler](https://github.com/pf-siedler) | <img width="50" src="https://avatars.githubusercontent.com/u/17232523?v=4"/></br>[ruuti](https://github.com/ruuti) | <img width="50" src="https://avatars.githubusercontent.com/u/23638148?v=4"/></br>[s1nceri7y](https://github.com/s1nceri7y) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/3057302?v=4"/></br>[fer22f](https://api.github.com/users/fer22f) | <img width="50" src="https://avatars0.githubusercontent.com/u/43272148?v=4"/></br>[fpindado](https://api.github.com/users/fpindado) | <img width="50" src="https://avatars2.githubusercontent.com/u/1714374?v=4"/></br>[FleischKarussel](https://api.github.com/users/FleischKarussel) | <img width="50" src="https://avatars1.githubusercontent.com/u/18525376?v=4"/></br>[talkdirty](https://api.github.com/users/talkdirty) | <img width="50" src="https://avatars0.githubusercontent.com/u/6190183?v=4"/></br>[gmag11](https://api.github.com/users/gmag11) | | <img width="50" src="https://avatars.githubusercontent.com/u/10117386?v=4"/></br>[kornava](https://github.com/kornava) | <img width="50" src="https://avatars.githubusercontent.com/u/7471938?v=4"/></br>[ShuiHuo](https://github.com/ShuiHuo) | <img width="50" src="https://avatars.githubusercontent.com/u/11596277?v=4"/></br>[ikunya](https://github.com/ikunya) | <img width="50" src="https://avatars.githubusercontent.com/u/8184424?v=4"/></br>[Ahmad45123](https://github.com/Ahmad45123) | <img width="50" src="https://avatars.githubusercontent.com/u/59133880?v=4"/></br>[bedwardly-down](https://github.com/bedwardly-down) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/24235344?v=4"/></br>[guiemi](https://api.github.com/users/guiemi) | <img width="50" src="https://avatars2.githubusercontent.com/u/2257024?v=4"/></br>[gusbemacbe](https://api.github.com/users/gusbemacbe) | <img width="50" src="https://avatars0.githubusercontent.com/u/18524580?v=4"/></br>[Fvbor](https://api.github.com/users/Fvbor) | <img width="50" src="https://avatars0.githubusercontent.com/u/22606250?v=4"/></br>[bennetthanna](https://api.github.com/users/bennetthanna) | <img width="50" src="https://avatars3.githubusercontent.com/u/3379379?v=4"/></br>[sczhg](https://api.github.com/users/sczhg) | | <img width="50" src="https://avatars.githubusercontent.com/u/50335724?v=4"/></br>[dcaveiro](https://github.com/dcaveiro) | <img width="50" src="https://avatars.githubusercontent.com/u/47456195?v=4"/></br>[hexclover](https://github.com/hexclover) | <img width="50" src="https://avatars.githubusercontent.com/u/45535789?v=4"/></br>[2jaeyeol](https://github.com/2jaeyeol) | <img width="50" src="https://avatars.githubusercontent.com/u/25622825?v=4"/></br>[thackeraaron](https://github.com/thackeraaron) | <img width="50" src="https://avatars.githubusercontent.com/u/15862474?v=4"/></br>[aaronxn](https://github.com/aaronxn) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/1716229?v=4"/></br>[Vistaus](https://api.github.com/users/Vistaus) | <img width="50" src="https://avatars1.githubusercontent.com/u/19862172?v=4"/></br>[iahmedbacha](https://api.github.com/users/iahmedbacha) | <img width="50" src="https://avatars0.githubusercontent.com/u/1533624?v=4"/></br>[IrvinDominin](https://api.github.com/users/IrvinDominin) | <img width="50" src="https://avatars3.githubusercontent.com/u/33200024?v=4"/></br>[ishammahajan](https://api.github.com/users/ishammahajan) | <img width="50" src="https://avatars0.githubusercontent.com/u/19985741?v=4"/></br>[JRaiden16](https://api.github.com/users/JRaiden16) | | <img width="50" src="https://avatars.githubusercontent.com/u/40672207?v=4"/></br>[xUser5000](https://github.com/xUser5000) | <img width="50" src="https://avatars.githubusercontent.com/u/56785486?v=4"/></br>[iamabhi222](https://github.com/iamabhi222) | <img width="50" src="https://avatars.githubusercontent.com/u/63443657?v=4"/></br>[Aksh-Konda](https://github.com/Aksh-Konda) | <img width="50" src="https://avatars.githubusercontent.com/u/3660978?v=4"/></br>[alanfortlink](https://github.com/alanfortlink) | <img width="50" src="https://avatars.githubusercontent.com/u/53372753?v=4"/></br>[AverageUser2](https://github.com/AverageUser2) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/11466782?v=4"/></br>[jacobherrington](https://api.github.com/users/jacobherrington) | <img width="50" src="https://avatars2.githubusercontent.com/u/9365179?v=4"/></br>[jamesadjinwa](https://api.github.com/users/jamesadjinwa) | <img width="50" src="https://avatars1.githubusercontent.com/u/4995433?v=4"/></br>[jaredcrowe](https://api.github.com/users/jaredcrowe) | <img width="50" src="https://avatars3.githubusercontent.com/u/4374338?v=4"/></br>[potatogim](https://api.github.com/users/potatogim) | <img width="50" src="https://avatars0.githubusercontent.com/u/163555?v=4"/></br>[JoelRSimpson](https://api.github.com/users/JoelRSimpson) | | <img width="50" src="https://avatars.githubusercontent.com/u/4056990?v=4"/></br>[afischer211](https://github.com/afischer211) | <img width="50" src="https://avatars.githubusercontent.com/u/26230870?v=4"/></br>[a13xk](https://github.com/a13xk) | <img width="50" src="https://avatars.githubusercontent.com/u/14836659?v=4"/></br>[apankratov](https://github.com/apankratov) | <img width="50" src="https://avatars.githubusercontent.com/u/7045739?v=4"/></br>[teterkin](https://github.com/teterkin) | <img width="50" src="https://avatars.githubusercontent.com/u/215668?v=4"/></br>[avanderberg](https://github.com/avanderberg) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/6965062?v=4"/></br>[joeltaylor](https://api.github.com/users/joeltaylor) | <img width="50" src="https://avatars3.githubusercontent.com/u/242107?v=4"/></br>[exic](https://api.github.com/users/exic) | <img width="50" src="https://avatars1.githubusercontent.com/u/23194385?v=4"/></br>[jony0008](https://api.github.com/users/jony0008) | <img width="50" src="https://avatars1.githubusercontent.com/u/6048003?v=4"/></br>[joybinchen](https://api.github.com/users/joybinchen) | <img width="50" src="https://avatars1.githubusercontent.com/u/1560189?v=4"/></br>[y-usuzumi](https://api.github.com/users/y-usuzumi) | | <img width="50" src="https://avatars.githubusercontent.com/u/41290751?v=4"/></br>[serenitatis](https://github.com/serenitatis) | <img width="50" src="https://avatars.githubusercontent.com/u/4408379?v=4"/></br>[lex111](https://github.com/lex111) | <img width="50" src="https://avatars.githubusercontent.com/u/60134194?v=4"/></br>[Alkindi42](https://github.com/Alkindi42) | <img width="50" src="https://avatars.githubusercontent.com/u/7129815?v=4"/></br>[Jumanjii](https://github.com/Jumanjii) | <img width="50" src="https://avatars.githubusercontent.com/u/19962243?v=4"/></br>[AlphaJack](https://github.com/AlphaJack) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1660460?v=4"/></br>[xuhcc](https://api.github.com/users/xuhcc) | <img width="50" src="https://avatars0.githubusercontent.com/u/16933735?v=4"/></br>[kirtanprht](https://api.github.com/users/kirtanprht) | <img width="50" src="https://avatars3.githubusercontent.com/u/7824233?v=4"/></br>[kklas](https://api.github.com/users/kklas) | <img width="50" src="https://avatars1.githubusercontent.com/u/8622992?v=4"/></br>[xmlangel](https://api.github.com/users/xmlangel) | <img width="50" src="https://avatars0.githubusercontent.com/u/1055100?v=4"/></br>[troilus](https://api.github.com/users/troilus) | | <img width="50" src="https://avatars.githubusercontent.com/u/65647302?v=4"/></br>[Lord-Aman](https://github.com/Lord-Aman) | <img width="50" src="https://avatars.githubusercontent.com/u/14096959?v=4"/></br>[richtwin567](https://github.com/richtwin567) | <img width="50" src="https://avatars.githubusercontent.com/u/487182?v=4"/></br>[ajilderda](https://github.com/ajilderda) | <img width="50" src="https://avatars.githubusercontent.com/u/922429?v=4"/></br>[adrynov](https://github.com/adrynov) | <img width="50" src="https://avatars.githubusercontent.com/u/94937?v=4"/></br>[andrewperry](https://github.com/andrewperry) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/50335724?v=4"/></br>[Lorinson](https://api.github.com/users/Lorinson) | <img width="50" src="https://avatars2.githubusercontent.com/u/2599210?v=4"/></br>[lboullo0](https://api.github.com/users/lboullo0) | <img width="50" src="https://avatars1.githubusercontent.com/u/1562062?v=4"/></br>[dbinary](https://api.github.com/users/dbinary) | <img width="50" src="https://avatars3.githubusercontent.com/u/5699725?v=4"/></br>[mvonmaltitz](https://api.github.com/users/mvonmaltitz) | <img width="50" src="https://avatars3.githubusercontent.com/u/11036464?v=4"/></br>[mlkood](https://api.github.com/users/mlkood) | | <img width="50" src="https://avatars.githubusercontent.com/u/5417051?v=4"/></br>[tekdel](https://github.com/tekdel) | <img width="50" src="https://avatars.githubusercontent.com/u/54475686?v=4"/></br>[anshuman9999](https://github.com/anshuman9999) | <img width="50" src="https://avatars.githubusercontent.com/u/25694659?v=4"/></br>[rasklaad](https://github.com/rasklaad) | <img width="50" src="https://avatars.githubusercontent.com/u/17809291?v=4"/></br>[Technik-J](https://github.com/Technik-J) | <img width="50" src="https://avatars.githubusercontent.com/u/498326?v=4"/></br>[Shaxine](https://github.com/Shaxine) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/5788516?v=4"/></br>[Marmo](https://api.github.com/users/Marmo) | <img width="50" src="https://avatars0.githubusercontent.com/u/640949?v=4"/></br>[freaktechnik](https://api.github.com/users/freaktechnik) | <img width="50" src="https://avatars2.githubusercontent.com/u/12831489?v=4"/></br>[mgroth0](https://api.github.com/users/mgroth0) | <img width="50" src="https://avatars0.githubusercontent.com/u/21796?v=4"/></br>[silentmatt](https://api.github.com/users/silentmatt) | <img width="50" src="https://avatars0.githubusercontent.com/u/51273874?v=4"/></br>[MichipX](https://api.github.com/users/MichipX) | | <img width="50" src="https://avatars.githubusercontent.com/u/9095073?v=4"/></br>[antonio-ramadas](https://github.com/antonio-ramadas) | <img width="50" src="https://avatars.githubusercontent.com/u/28067395?v=4"/></br>[heyapoorva](https://github.com/heyapoorva) | <img width="50" src="https://avatars.githubusercontent.com/u/201215?v=4"/></br>[assimd](https://github.com/assimd) | <img width="50" src="https://avatars.githubusercontent.com/u/26827848?v=4"/></br>[Atrate](https://github.com/Atrate) | <img width="50" src="https://avatars.githubusercontent.com/u/60288895?v=4"/></br>[Beowulf2](https://github.com/Beowulf2) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/53177864?v=4"/></br>[MrTraduttore](https://api.github.com/users/MrTraduttore) | <img width="50" src="https://avatars3.githubusercontent.com/u/9076687?v=4"/></br>[NJannasch](https://api.github.com/users/NJannasch) | <img width="50" src="https://avatars2.githubusercontent.com/u/12369770?v=4"/></br>[Ouvill](https://api.github.com/users/Ouvill) | <img width="50" src="https://avatars3.githubusercontent.com/u/43815417?v=4"/></br>[shorty2380](https://api.github.com/users/shorty2380) | <img width="50" src="https://avatars0.githubusercontent.com/u/19418601?v=4"/></br>[Rakleed](https://api.github.com/users/Rakleed) | | <img width="50" src="https://avatars.githubusercontent.com/u/7034200?v=4"/></br>[bimlas](https://github.com/bimlas) | <img width="50" src="https://avatars.githubusercontent.com/u/47641641?v=4"/></br>[brenobaptista](https://github.com/brenobaptista) | <img width="50" src="https://avatars.githubusercontent.com/u/60824?v=4"/></br>[brttbndr](https://github.com/brttbndr) | <img width="50" src="https://avatars.githubusercontent.com/u/16287077?v=4"/></br>[carlbordum](https://github.com/carlbordum) | <img width="50" src="https://avatars.githubusercontent.com/u/20382?v=4"/></br>[carlosedp](https://github.com/carlosedp) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/6306608?v=4"/></br>[Diadlo](https://api.github.com/users/Diadlo) | <img width="50" src="https://avatars1.githubusercontent.com/u/13197246?v=4"/></br>[R-L-T-Y](https://api.github.com/users/R-L-T-Y) | <img width="50" src="https://avatars2.githubusercontent.com/u/42652941?v=4"/></br>[rajprakash00](https://api.github.com/users/rajprakash00) | <img width="50" src="https://avatars0.githubusercontent.com/u/54888685?v=4"/></br>[RedDocMD](https://api.github.com/users/RedDocMD) | <img width="50" src="https://avatars2.githubusercontent.com/u/17312341?v=4"/></br>[reinhart1010](https://api.github.com/users/reinhart1010) | | <img width="50" src="https://avatars.githubusercontent.com/u/105843?v=4"/></br>[chaifeng](https://github.com/chaifeng) | <img width="50" src="https://avatars.githubusercontent.com/u/549349?v=4"/></br>[charles-e](https://github.com/charles-e) | <img width="50" src="https://avatars.githubusercontent.com/u/19870089?v=4"/></br>[cyy5358](https://github.com/cyy5358) | <img width="50" src="https://avatars.githubusercontent.com/u/32337926?v=4"/></br>[Chillu1](https://github.com/Chillu1) | <img width="50" src="https://avatars.githubusercontent.com/u/2348463?v=4"/></br>[Techwolf12](https://github.com/Techwolf12) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/744655?v=4"/></br>[ruzaq](https://api.github.com/users/ruzaq) | <img width="50" src="https://avatars0.githubusercontent.com/u/19328605?v=4"/></br>[SamuelBlickle](https://api.github.com/users/SamuelBlickle) | <img width="50" src="https://avatars1.githubusercontent.com/u/1776?v=4"/></br>[bronson](https://api.github.com/users/bronson) | <img width="50" src="https://avatars0.githubusercontent.com/u/24606935?v=4"/></br>[semperor](https://api.github.com/users/semperor) | <img width="50" src="https://avatars0.githubusercontent.com/u/7091080?v=4"/></br>[sinkuu](https://api.github.com/users/sinkuu) | | <img width="50" src="https://avatars.githubusercontent.com/u/2282880?v=4"/></br>[cloudtrends](https://github.com/cloudtrends) | <img width="50" src="https://avatars.githubusercontent.com/u/17257053?v=4"/></br>[idcristi](https://github.com/idcristi) | <img width="50" src="https://avatars.githubusercontent.com/u/15956322?v=4"/></br>[damienmascre](https://github.com/damienmascre) | <img width="50" src="https://avatars.githubusercontent.com/u/1044056?v=4"/></br>[daniellandau](https://github.com/daniellandau) | <img width="50" src="https://avatars.githubusercontent.com/u/12847693?v=4"/></br>[danil-tolkachev](https://github.com/danil-tolkachev) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/9937486?v=4"/></br>[SFoskitt](https://api.github.com/users/SFoskitt) | <img width="50" src="https://avatars2.githubusercontent.com/u/505011?v=4"/></br>[kcrt](https://api.github.com/users/kcrt) | <img width="50" src="https://avatars1.githubusercontent.com/u/538584?v=4"/></br>[xissy](https://api.github.com/users/xissy) | <img width="50" src="https://avatars3.githubusercontent.com/u/466122?v=4"/></br>[Tekki](https://api.github.com/users/Tekki) | <img width="50" src="https://avatars0.githubusercontent.com/u/21969426?v=4"/></br>[TheoDutch](https://api.github.com/users/TheoDutch) | | <img width="50" src="https://avatars.githubusercontent.com/u/7279100?v=4"/></br>[darshani28](https://github.com/darshani28) | <img width="50" src="https://avatars.githubusercontent.com/u/26189247?v=4"/></br>[daukadolt](https://github.com/daukadolt) | <img width="50" src="https://avatars.githubusercontent.com/u/28535750?v=4"/></br>[NeverMendel](https://github.com/NeverMendel) | <img width="50" src="https://avatars.githubusercontent.com/u/26790323?v=4"/></br>[dervist](https://github.com/dervist) | <img width="50" src="https://avatars.githubusercontent.com/u/11378282?v=4"/></br>[diego-betto](https://github.com/diego-betto) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/8731922?v=4"/></br>[tbroadley](https://api.github.com/users/tbroadley) | <img width="50" src="https://avatars1.githubusercontent.com/u/114300?v=4"/></br>[Kriechi](https://api.github.com/users/Kriechi) | <img width="50" src="https://avatars0.githubusercontent.com/u/3457339?v=4"/></br>[tkilaker](https://api.github.com/users/tkilaker) | <img width="50" src="https://avatars1.githubusercontent.com/u/4201229?v=4"/></br>[tcyrus](https://api.github.com/users/tcyrus) | <img width="50" src="https://avatars2.githubusercontent.com/u/834914?v=4"/></br>[tobias-grasse](https://api.github.com/users/tobias-grasse) | | <img width="50" src="https://avatars.githubusercontent.com/u/215270?v=4"/></br>[erdody](https://github.com/erdody) | <img width="50" src="https://avatars.githubusercontent.com/u/10371667?v=4"/></br>[domgoodwin](https://github.com/domgoodwin) | <img width="50" src="https://avatars.githubusercontent.com/u/72066?v=4"/></br>[b4mboo](https://github.com/b4mboo) | <img width="50" src="https://avatars.githubusercontent.com/u/5131923?v=4"/></br>[donbowman](https://github.com/donbowman) | <img width="50" src="https://avatars.githubusercontent.com/u/579727?v=4"/></br>[sirnacnud](https://github.com/sirnacnud) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/6691273?v=4"/></br>[strobeltobias](https://api.github.com/users/strobeltobias) | <img width="50" src="https://avatars2.githubusercontent.com/u/70296?v=4"/></br>[tbergeron](https://api.github.com/users/tbergeron) | <img width="50" src="https://avatars1.githubusercontent.com/u/10265443?v=4"/></br>[Ullas-Aithal](https://api.github.com/users/Ullas-Aithal) | <img width="50" src="https://avatars2.githubusercontent.com/u/6104498?v=4"/></br>[MyTheValentinus](https://api.github.com/users/MyTheValentinus) | <img width="50" src="https://avatars3.githubusercontent.com/u/26511487?v=4"/></br>[WisdomCode](https://api.github.com/users/WisdomCode) | | <img width="50" src="https://avatars.githubusercontent.com/u/47756?v=4"/></br>[dflock](https://github.com/dflock) | <img width="50" src="https://avatars.githubusercontent.com/u/7990534?v=4"/></br>[drobilica](https://github.com/drobilica) | <img width="50" src="https://avatars.githubusercontent.com/u/21699905?v=4"/></br>[educbraga](https://github.com/educbraga) | <img width="50" src="https://avatars.githubusercontent.com/u/67867099?v=4"/></br>[eduardokimmel](https://github.com/eduardokimmel) | <img width="50" src="https://avatars.githubusercontent.com/u/30393516?v=4"/></br>[VodeniZeko](https://github.com/VodeniZeko) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/1921957?v=4"/></br>[xsak](https://api.github.com/users/xsak) | <img width="50" src="https://avatars2.githubusercontent.com/u/11031696?v=4"/></br>[ymitsos](https://api.github.com/users/ymitsos) | <img width="50" src="https://avatars3.githubusercontent.com/u/29891001?v=4"/></br>[jyuvaraj03](https://api.github.com/users/jyuvaraj03) | <img width="50" src="https://avatars0.githubusercontent.com/u/15380913?v=4"/></br>[kowalskidev](https://api.github.com/users/kowalskidev) | <img width="50" src="https://avatars0.githubusercontent.com/u/63324960?v=4"/></br>[abolishallprivateproperty](https://api.github.com/users/abolishallprivateproperty) | | <img width="50" src="https://avatars.githubusercontent.com/u/17415256?v=4"/></br>[ei-ke](https://github.com/ei-ke) | <img width="50" src="https://avatars.githubusercontent.com/u/1962738?v=4"/></br>[einverne](https://github.com/einverne) | <img width="50" src="https://avatars.githubusercontent.com/u/16492558?v=4"/></br>[eodeluga](https://github.com/eodeluga) | <img width="50" src="https://avatars.githubusercontent.com/u/16875937?v=4"/></br>[fathyar](https://github.com/fathyar) | <img width="50" src="https://avatars.githubusercontent.com/u/3057302?v=4"/></br>[fer22f](https://github.com/fer22f) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/11336076?v=4"/></br>[aerotog](https://api.github.com/users/aerotog) | <img width="50" src="https://avatars2.githubusercontent.com/u/49116134?v=4"/></br>[anihm136](https://api.github.com/users/anihm136) | <img width="50" src="https://avatars2.githubusercontent.com/u/35600612?v=4"/></br>[boring10](https://api.github.com/users/boring10) | <img width="50" src="https://avatars0.githubusercontent.com/u/35413451?v=4"/></br>[chenlhlinux](https://api.github.com/users/chenlhlinux) | <img width="50" src="https://avatars3.githubusercontent.com/u/30935096?v=4"/></br>[cybertramp](https://api.github.com/users/cybertramp) | | <img width="50" src="https://avatars.githubusercontent.com/u/43272148?v=4"/></br>[fpindado](https://github.com/fpindado) | <img width="50" src="https://avatars.githubusercontent.com/u/1714374?v=4"/></br>[FleischKarussel](https://github.com/FleischKarussel) | <img width="50" src="https://avatars.githubusercontent.com/u/18525376?v=4"/></br>[talkdirty](https://github.com/talkdirty) | <img width="50" src="https://avatars.githubusercontent.com/u/19814827?v=4"/></br>[gmaubach](https://github.com/gmaubach) | <img width="50" src="https://avatars.githubusercontent.com/u/6190183?v=4"/></br>[gmag11](https://github.com/gmag11) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/9694906?v=4"/></br>[delta-emil](https://api.github.com/users/delta-emil) | <img width="50" src="https://avatars0.githubusercontent.com/u/926263?v=4"/></br>[doc75](https://api.github.com/users/doc75) | <img width="50" src="https://avatars2.githubusercontent.com/u/2903013?v=4"/></br>[ebayer](https://api.github.com/users/ebayer) | <img width="50" src="https://avatars3.githubusercontent.com/u/701050?v=4"/></br>[espinosa](https://api.github.com/users/espinosa) | <img width="50" src="https://avatars1.githubusercontent.com/u/18619090?v=4"/></br>[exponentactivity](https://api.github.com/users/exponentactivity) | | <img width="50" src="https://avatars.githubusercontent.com/u/6209647?v=4"/></br>[Jackymancs4](https://github.com/Jackymancs4) | <img width="50" src="https://avatars.githubusercontent.com/u/297578?v=4"/></br>[Glandos](https://github.com/Glandos) | <img width="50" src="https://avatars.githubusercontent.com/u/24235344?v=4"/></br>[vibraniumdev](https://github.com/vibraniumdev) | <img width="50" src="https://avatars.githubusercontent.com/u/2257024?v=4"/></br>[gusbemacbe](https://github.com/gusbemacbe) | <img width="50" src="https://avatars.githubusercontent.com/u/64917442?v=4"/></br>[HOLLYwyh](https://github.com/HOLLYwyh) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/16708935?v=4"/></br>[exprez135](https://api.github.com/users/exprez135) | <img width="50" src="https://avatars1.githubusercontent.com/u/9768112?v=4"/></br>[fab4x](https://api.github.com/users/fab4x) | <img width="50" src="https://avatars0.githubusercontent.com/u/47755037?v=4"/></br>[fabianski7](https://api.github.com/users/fabianski7) | <img width="50" src="https://avatars0.githubusercontent.com/u/14201321?v=4"/></br>[rasperepodvipodvert](https://api.github.com/users/rasperepodvipodvert) | <img width="50" src="https://avatars1.githubusercontent.com/u/748808?v=4"/></br>[gasolin](https://api.github.com/users/gasolin) | | <img width="50" src="https://avatars.githubusercontent.com/u/18524580?v=4"/></br>[Fvbor](https://github.com/Fvbor) | <img width="50" src="https://avatars.githubusercontent.com/u/22606250?v=4"/></br>[bennetthanna](https://github.com/bennetthanna) | <img width="50" src="https://avatars.githubusercontent.com/u/67231570?v=4"/></br>[harshitkathuria](https://github.com/harshitkathuria) | <img width="50" src="https://avatars.githubusercontent.com/u/1716229?v=4"/></br>[Vistaus](https://github.com/Vistaus) | <img width="50" src="https://avatars.githubusercontent.com/u/6509881?v=4"/></br>[ianjs](https://github.com/ianjs) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/47191051?v=4"/></br>[githubaccount073](https://api.github.com/users/githubaccount073) | <img width="50" src="https://avatars1.githubusercontent.com/u/11388094?v=4"/></br>[hydrandt](https://api.github.com/users/hydrandt) | <img width="50" src="https://avatars0.githubusercontent.com/u/557540?v=4"/></br>[jabdoa2](https://api.github.com/users/jabdoa2) | <img width="50" src="https://avatars3.githubusercontent.com/u/53862536?v=4"/></br>[johanvanheusden](https://api.github.com/users/johanvanheusden) | <img width="50" src="https://avatars1.githubusercontent.com/u/54991735?v=4"/></br>[krzysiekwie](https://api.github.com/users/krzysiekwie) | | <img width="50" src="https://avatars.githubusercontent.com/u/19862172?v=4"/></br>[iahmedbacha](https://github.com/iahmedbacha) | <img width="50" src="https://avatars.githubusercontent.com/u/1533624?v=4"/></br>[IrvinDominin](https://github.com/IrvinDominin) | <img width="50" src="https://avatars.githubusercontent.com/u/33200024?v=4"/></br>[ishammahajan](https://github.com/ishammahajan) | <img width="50" src="https://avatars.githubusercontent.com/u/6916297?v=4"/></br>[ffadilaputra](https://github.com/ffadilaputra) | <img width="50" src="https://avatars.githubusercontent.com/u/19985741?v=4"/></br>[JRaiden16](https://github.com/JRaiden16) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/12849008?v=4"/></br>[lighthousebulb](https://api.github.com/users/lighthousebulb) | <img width="50" src="https://avatars0.githubusercontent.com/u/4140247?v=4"/></br>[luzpaz](https://api.github.com/users/luzpaz) | <img width="50" src="https://avatars2.githubusercontent.com/u/30428258?v=4"/></br>[nmiquan](https://api.github.com/users/nmiquan) | <img width="50" src="https://avatars0.githubusercontent.com/u/31123054?v=4"/></br>[nullpointer666](https://api.github.com/users/nullpointer666) | <img width="50" src="https://avatars2.githubusercontent.com/u/2979926?v=4"/></br>[oscaretu](https://api.github.com/users/oscaretu) | | <img width="50" src="https://avatars.githubusercontent.com/u/11466782?v=4"/></br>[jacobherrington](https://github.com/jacobherrington) | <img width="50" src="https://avatars.githubusercontent.com/u/9365179?v=4"/></br>[jamesadjinwa](https://github.com/jamesadjinwa) | <img width="50" src="https://avatars.githubusercontent.com/u/20801821?v=4"/></br>[jrwrigh](https://github.com/jrwrigh) | <img width="50" src="https://avatars.githubusercontent.com/u/4995433?v=4"/></br>[jaredcrowe](https://github.com/jaredcrowe) | <img width="50" src="https://avatars.githubusercontent.com/u/4087105?v=4"/></br>[volatilevar](https://github.com/volatilevar) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/36965591?v=4"/></br>[daehruoydeef](https://api.github.com/users/daehruoydeef) | <img width="50" src="https://avatars1.githubusercontent.com/u/42961947?v=4"/></br>[pensierocrea](https://api.github.com/users/pensierocrea) | <img width="50" src="https://avatars3.githubusercontent.com/u/10206967?v=4"/></br>[rhtenhove](https://api.github.com/users/rhtenhove) | <img width="50" src="https://avatars2.githubusercontent.com/u/16728217?v=4"/></br>[rikanotank1](https://api.github.com/users/rikanotank1) | <img width="50" src="https://avatars1.githubusercontent.com/u/51550769?v=4"/></br>[rnbastos](https://api.github.com/users/rnbastos) | | <img width="50" src="https://avatars.githubusercontent.com/u/47724360?v=4"/></br>[innkuika](https://github.com/innkuika) | <img width="50" src="https://avatars.githubusercontent.com/u/163555?v=4"/></br>[JoelRSimpson](https://github.com/JoelRSimpson) | <img width="50" src="https://avatars.githubusercontent.com/u/6965062?v=4"/></br>[joeltaylor](https://github.com/joeltaylor) | <img width="50" src="https://avatars.githubusercontent.com/u/242107?v=4"/></br>[exic](https://github.com/exic) | <img width="50" src="https://avatars.githubusercontent.com/u/13716151?v=4"/></br>[JonathanPlasse](https://github.com/JonathanPlasse) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/14062932?v=4"/></br>[simonsan](https://api.github.com/users/simonsan) | <img width="50" src="https://avatars2.githubusercontent.com/u/5004545?v=4"/></br>[stellarpower](https://api.github.com/users/stellarpower) | <img width="50" src="https://avatars1.githubusercontent.com/u/12995773?v=4"/></br>[sumomo-99](https://api.github.com/users/sumomo-99) | <img width="50" src="https://avatars0.githubusercontent.com/u/6908872?v=4"/></br>[taw00](https://api.github.com/users/taw00) | <img width="50" src="https://avatars0.githubusercontent.com/u/10956653?v=4"/></br>[tcassaert](https://api.github.com/users/tcassaert) | | <img width="50" src="https://avatars.githubusercontent.com/u/1248504?v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars.githubusercontent.com/u/6048003?v=4"/></br>[joybinchen](https://github.com/joybinchen) | <img width="50" src="https://avatars.githubusercontent.com/u/37601331?v=4"/></br>[kaustubhsh](https://github.com/kaustubhsh) | <img width="50" src="https://avatars.githubusercontent.com/u/1560189?v=4"/></br>[y-usuzumi](https://github.com/y-usuzumi) | <img width="50" src="https://avatars.githubusercontent.com/u/1660460?v=4"/></br>[xuhcc](https://github.com/xuhcc) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/46327531?v=4"/></br>[vicoutorama](https://api.github.com/users/vicoutorama) | <img width="50" src="https://avatars0.githubusercontent.com/u/2216902?v=4"/></br>[xcffl](https://api.github.com/users/xcffl) | <img width="50" src="https://avatars2.githubusercontent.com/u/37692927?v=4"/></br>[zaoyifan](https://api.github.com/users/zaoyifan) | <img width="50" src="https://avatars3.githubusercontent.com/u/55245068?v=4"/></br>[zen-quo](https://api.github.com/users/zen-quo) | <img width="50" src="https://avatars0.githubusercontent.com/u/25315?v=4"/></br>[xcession](https://api.github.com/users/xcession) | | <img width="50" src="https://avatars.githubusercontent.com/u/16933735?v=4"/></br>[kirtanprht](https://github.com/kirtanprht) | <img width="50" src="https://avatars.githubusercontent.com/u/37491732?v=4"/></br>[k0ur0x](https://github.com/k0ur0x) | <img width="50" src="https://avatars.githubusercontent.com/u/7824233?v=4"/></br>[kklas](https://github.com/kklas) | <img width="50" src="https://avatars.githubusercontent.com/u/8622992?v=4"/></br>[xmlangel](https://github.com/xmlangel) | <img width="50" src="https://avatars.githubusercontent.com/u/1055100?v=4"/></br>[troilus](https://github.com/troilus) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/34542665?v=4"/></br>[paventyang](https://api.github.com/users/paventyang) | <img width="50" src="https://avatars1.githubusercontent.com/u/1308646?v=4"/></br>[zhangmx](https://api.github.com/users/zhangmx) | | | | | <img width="50" src="https://avatars.githubusercontent.com/u/2599210?v=4"/></br>[lboullo0](https://github.com/lboullo0) | <img width="50" src="https://avatars.githubusercontent.com/u/1562062?v=4"/></br>[dbinary](https://github.com/dbinary) | <img width="50" src="https://avatars.githubusercontent.com/u/15436007?v=4"/></br>[marc-bouvier](https://github.com/marc-bouvier) | <img width="50" src="https://avatars.githubusercontent.com/u/5699725?v=4"/></br>[mvonmaltitz](https://github.com/mvonmaltitz) | <img width="50" src="https://avatars.githubusercontent.com/u/11036464?v=4"/></br>[mlkood](https://github.com/mlkood) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2480960?v=4"/></br>[plextoriano](https://github.com/plextoriano) | <img width="50" src="https://avatars.githubusercontent.com/u/5788516?v=4"/></br>[Marmo](https://github.com/Marmo) | <img width="50" src="https://avatars.githubusercontent.com/u/29300939?v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars.githubusercontent.com/u/640949?v=4"/></br>[freaktechnik](https://github.com/freaktechnik) | <img width="50" src="https://avatars.githubusercontent.com/u/79802125?v=4"/></br>[martinkorelic](https://github.com/martinkorelic) |
| <img width="50" src="https://avatars.githubusercontent.com/u/287105?v=4"/></br>[Petemir](https://github.com/Petemir) | <img width="50" src="https://avatars.githubusercontent.com/u/5218859?v=4"/></br>[matsair](https://github.com/matsair) | <img width="50" src="https://avatars.githubusercontent.com/u/12831489?v=4"/></br>[mgroth0](https://github.com/mgroth0) | <img width="50" src="https://avatars.githubusercontent.com/u/21796?v=4"/></br>[silentmatt](https://github.com/silentmatt) | <img width="50" src="https://avatars.githubusercontent.com/u/76700192?v=4"/></br>[maxs-test](https://github.com/maxs-test) |
| <img width="50" src="https://avatars.githubusercontent.com/u/59669349?v=4"/></br>[MichBoi](https://github.com/MichBoi) | <img width="50" src="https://avatars.githubusercontent.com/u/51273874?v=4"/></br>[MichipX](https://github.com/MichipX) | <img width="50" src="https://avatars.githubusercontent.com/u/53177864?v=4"/></br>[MrTraduttore](https://github.com/MrTraduttore) | <img width="50" src="https://avatars.githubusercontent.com/u/48156230?v=4"/></br>[sanjarcode](https://github.com/sanjarcode) | <img width="50" src="https://avatars.githubusercontent.com/u/43955099?v=4"/></br>[Mustafa-ALD](https://github.com/Mustafa-ALD) |
| <img width="50" src="https://avatars.githubusercontent.com/u/9076687?v=4"/></br>[NJannasch](https://github.com/NJannasch) | <img width="50" src="https://avatars.githubusercontent.com/u/8016073?v=4"/></br>[zomglings](https://github.com/zomglings) | <img width="50" src="https://avatars.githubusercontent.com/u/10386884?v=4"/></br>[Frichetten](https://github.com/Frichetten) | <img width="50" src="https://avatars.githubusercontent.com/u/5541611?v=4"/></br>[nicolas-suzuki](https://github.com/nicolas-suzuki) | <img width="50" src="https://avatars.githubusercontent.com/u/12369770?v=4"/></br>[Ouvill](https://github.com/Ouvill) |
| <img width="50" src="https://avatars.githubusercontent.com/u/43815417?v=4"/></br>[shorty2380](https://github.com/shorty2380) | <img width="50" src="https://avatars.githubusercontent.com/u/15014287?v=4"/></br>[dist3r](https://github.com/dist3r) | <img width="50" src="https://avatars.githubusercontent.com/u/19418601?v=4"/></br>[rakleed](https://github.com/rakleed) | <img width="50" src="https://avatars.githubusercontent.com/u/7881932?v=4"/></br>[idle-code](https://github.com/idle-code) | <img width="50" src="https://avatars.githubusercontent.com/u/168931?v=4"/></br>[bobchao](https://github.com/bobchao) |
| <img width="50" src="https://avatars.githubusercontent.com/u/6306608?v=4"/></br>[Diadlo](https://github.com/Diadlo) | <img width="50" src="https://avatars.githubusercontent.com/u/42793024?v=4"/></br>[pranavmodx](https://github.com/pranavmodx) | <img width="50" src="https://avatars.githubusercontent.com/u/50834839?v=4"/></br>[R3dError](https://github.com/R3dError) | <img width="50" src="https://avatars.githubusercontent.com/u/42652941?v=4"/></br>[rajprakash00](https://github.com/rajprakash00) | <img width="50" src="https://avatars.githubusercontent.com/u/32304956?v=4"/></br>[rahil1304](https://github.com/rahil1304) |
| <img width="50" src="https://avatars.githubusercontent.com/u/8257474?v=4"/></br>[rasulkireev](https://github.com/rasulkireev) | <img width="50" src="https://avatars.githubusercontent.com/u/17312341?v=4"/></br>[reinhart1010](https://github.com/reinhart1010) | <img width="50" src="https://avatars.githubusercontent.com/u/60484714?v=4"/></br>[Retew](https://github.com/Retew) | <img width="50" src="https://avatars.githubusercontent.com/u/10456131?v=4"/></br>[ambrt](https://github.com/ambrt) | <img width="50" src="https://avatars.githubusercontent.com/u/15892014?v=4"/></br>[Derkades](https://github.com/Derkades) |
| <img width="50" src="https://avatars.githubusercontent.com/u/49439044?v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars.githubusercontent.com/u/54365?v=4"/></br>[rodgco](https://github.com/rodgco) | <img width="50" src="https://avatars.githubusercontent.com/u/96014?v=4"/></br>[Ronnie76er](https://github.com/Ronnie76er) | <img width="50" src="https://avatars.githubusercontent.com/u/79168?v=4"/></br>[roryokane](https://github.com/roryokane) | <img width="50" src="https://avatars.githubusercontent.com/u/744655?v=4"/></br>[ruzaq](https://github.com/ruzaq) |
| <img width="50" src="https://avatars.githubusercontent.com/u/20490839?v=4"/></br>[szokesandor](https://github.com/szokesandor) | <img width="50" src="https://avatars.githubusercontent.com/u/19328605?v=4"/></br>[SamuelBlickle](https://github.com/SamuelBlickle) | <img width="50" src="https://avatars.githubusercontent.com/u/80849457?v=4"/></br>[livingc0l0ur](https://github.com/livingc0l0ur) | <img width="50" src="https://avatars.githubusercontent.com/u/1776?v=4"/></br>[bronson](https://github.com/bronson) | <img width="50" src="https://avatars.githubusercontent.com/u/24606935?v=4"/></br>[semperor](https://github.com/semperor) |
| <img width="50" src="https://avatars.githubusercontent.com/u/607938?v=4"/></br>[shawnaxsom](https://github.com/shawnaxsom) | <img width="50" src="https://avatars.githubusercontent.com/u/9937486?v=4"/></br>[SFoskitt](https://github.com/SFoskitt) | <img width="50" src="https://avatars.githubusercontent.com/u/505011?v=4"/></br>[kcrt](https://github.com/kcrt) | <img width="50" src="https://avatars.githubusercontent.com/u/538584?v=4"/></br>[xissy](https://github.com/xissy) | <img width="50" src="https://avatars.githubusercontent.com/u/164962?v=4"/></br>[tams](https://github.com/tams) |
| <img width="50" src="https://avatars.githubusercontent.com/u/466122?v=4"/></br>[Tekki](https://github.com/Tekki) | <img width="50" src="https://avatars.githubusercontent.com/u/2112477?v=4"/></br>[ThatcherC](https://github.com/ThatcherC) | <img width="50" src="https://avatars.githubusercontent.com/u/21969426?v=4"/></br>[TheoDutch](https://github.com/TheoDutch) | <img width="50" src="https://avatars.githubusercontent.com/u/8731922?v=4"/></br>[tbroadley](https://github.com/tbroadley) | <img width="50" src="https://avatars.githubusercontent.com/u/114300?v=4"/></br>[Kriechi](https://github.com/Kriechi) |
| <img width="50" src="https://avatars.githubusercontent.com/u/3457339?v=4"/></br>[tkilaker](https://github.com/tkilaker) | <img width="50" src="https://avatars.githubusercontent.com/u/802148?v=4"/></br>[Tim-Erwin](https://github.com/Tim-Erwin) | <img width="50" src="https://avatars.githubusercontent.com/u/4201229?v=4"/></br>[tcyrus](https://github.com/tcyrus) | <img width="50" src="https://avatars.githubusercontent.com/u/834914?v=4"/></br>[tobias-grasse](https://github.com/tobias-grasse) | <img width="50" src="https://avatars.githubusercontent.com/u/6691273?v=4"/></br>[strobeltobias](https://github.com/strobeltobias) |
| <img width="50" src="https://avatars.githubusercontent.com/u/1677578?v=4"/></br>[kostegit](https://github.com/kostegit) | <img width="50" src="https://avatars.githubusercontent.com/u/70296?v=4"/></br>[tbergeron](https://github.com/tbergeron) | <img width="50" src="https://avatars.githubusercontent.com/u/10265443?v=4"/></br>[Ullas-Aithal](https://github.com/Ullas-Aithal) | <img width="50" src="https://avatars.githubusercontent.com/u/6104498?v=4"/></br>[MyTheValentinus](https://github.com/MyTheValentinus) | <img width="50" src="https://avatars.githubusercontent.com/u/2830093?v=4"/></br>[vassudanagunta](https://github.com/vassudanagunta) |
| <img width="50" src="https://avatars.githubusercontent.com/u/54314949?v=4"/></br>[vijayjoshi16](https://github.com/vijayjoshi16) | <img width="50" src="https://avatars.githubusercontent.com/u/59287619?v=4"/></br>[max-keviv](https://github.com/max-keviv) | <img width="50" src="https://avatars.githubusercontent.com/u/598576?v=4"/></br>[vandreykiv](https://github.com/vandreykiv) | <img width="50" src="https://avatars.githubusercontent.com/u/26511487?v=4"/></br>[WisdomCode](https://github.com/WisdomCode) | <img width="50" src="https://avatars.githubusercontent.com/u/1921957?v=4"/></br>[xsak](https://github.com/xsak) |
| <img width="50" src="https://avatars.githubusercontent.com/u/11031696?v=4"/></br>[ymitsos](https://github.com/ymitsos) | <img width="50" src="https://avatars.githubusercontent.com/u/63324960?v=4"/></br>[abolishallprivateproperty](https://github.com/abolishallprivateproperty) | <img width="50" src="https://avatars.githubusercontent.com/u/11336076?v=4"/></br>[aerotog](https://github.com/aerotog) | <img width="50" src="https://avatars.githubusercontent.com/u/39854348?v=4"/></br>[albertopasqualetto](https://github.com/albertopasqualetto) | <img width="50" src="https://avatars.githubusercontent.com/u/44570278?v=4"/></br>[asrient](https://github.com/asrient) |
| <img width="50" src="https://avatars.githubusercontent.com/u/621360?v=4"/></br>[bestlibre](https://github.com/bestlibre) | <img width="50" src="https://avatars.githubusercontent.com/u/35600612?v=4"/></br>[boring10](https://github.com/boring10) | <img width="50" src="https://avatars.githubusercontent.com/u/13894820?v=4"/></br>[cadolphs](https://github.com/cadolphs) | <img width="50" src="https://avatars.githubusercontent.com/u/12461043?v=4"/></br>[colorchestra](https://github.com/colorchestra) | <img width="50" src="https://avatars.githubusercontent.com/u/30935096?v=4"/></br>[cybertramp](https://github.com/cybertramp) |
| <img width="50" src="https://avatars.githubusercontent.com/u/15824892?v=4"/></br>[dartero](https://github.com/dartero) | <img width="50" src="https://avatars.githubusercontent.com/u/9694906?v=4"/></br>[delta-emil](https://github.com/delta-emil) | <img width="50" src="https://avatars.githubusercontent.com/u/926263?v=4"/></br>[doc75](https://github.com/doc75) | <img width="50" src="https://avatars.githubusercontent.com/u/5589253?v=4"/></br>[dsp77](https://github.com/dsp77) | <img width="50" src="https://avatars.githubusercontent.com/u/2903013?v=4"/></br>[ebayer](https://github.com/ebayer) |
| <img width="50" src="https://avatars.githubusercontent.com/u/9206310?v=4"/></br>[elsiehupp](https://github.com/elsiehupp) | <img width="50" src="https://avatars.githubusercontent.com/u/701050?v=4"/></br>[espinosa](https://github.com/espinosa) | <img width="50" src="https://avatars.githubusercontent.com/u/18619090?v=4"/></br>[exponentactivity](https://github.com/exponentactivity) | <img width="50" src="https://avatars.githubusercontent.com/u/16708935?v=4"/></br>[exprez135](https://github.com/exprez135) | <img width="50" src="https://avatars.githubusercontent.com/u/9768112?v=4"/></br>[fab4x](https://github.com/fab4x) |
| <img width="50" src="https://avatars.githubusercontent.com/u/47755037?v=4"/></br>[fabianski7](https://github.com/fabianski7) | <img width="50" src="https://avatars.githubusercontent.com/u/14201321?v=4"/></br>[rasperepodvipodvert](https://github.com/rasperepodvipodvert) | <img width="50" src="https://avatars.githubusercontent.com/u/748808?v=4"/></br>[gasolin](https://github.com/gasolin) | <img width="50" src="https://avatars.githubusercontent.com/u/47191051?v=4"/></br>[githubaccount073](https://github.com/githubaccount073) | <img width="50" src="https://avatars.githubusercontent.com/u/43672033?v=4"/></br>[hms5232](https://github.com/hms5232) |
| <img width="50" src="https://avatars.githubusercontent.com/u/11388094?v=4"/></br>[hydrandt](https://github.com/hydrandt) | <img width="50" src="https://avatars.githubusercontent.com/u/61012185?v=4"/></br>[iamtalwinder](https://github.com/iamtalwinder) | <img width="50" src="https://avatars.githubusercontent.com/u/557540?v=4"/></br>[jabdoa2](https://github.com/jabdoa2) | <img width="50" src="https://avatars.githubusercontent.com/u/29166402?v=4"/></br>[jduar](https://github.com/jduar) | <img width="50" src="https://avatars.githubusercontent.com/u/2678545?v=4"/></br>[jibedoubleve](https://github.com/jibedoubleve) |
| <img width="50" src="https://avatars.githubusercontent.com/u/53862536?v=4"/></br>[johanvanheusden](https://github.com/johanvanheusden) | <img width="50" src="https://avatars.githubusercontent.com/u/38327267?v=4"/></br>[jtagcat](https://github.com/jtagcat) | <img width="50" src="https://avatars.githubusercontent.com/u/61631665?v=4"/></br>[konhi](https://github.com/konhi) | <img width="50" src="https://avatars.githubusercontent.com/u/54991735?v=4"/></br>[krzysiekwie](https://github.com/krzysiekwie) | <img width="50" src="https://avatars.githubusercontent.com/u/12849008?v=4"/></br>[lighthousebulb](https://github.com/lighthousebulb) |
| <img width="50" src="https://avatars.githubusercontent.com/u/4140247?v=4"/></br>[luzpaz](https://github.com/luzpaz) | <img width="50" src="https://avatars.githubusercontent.com/u/29355048?v=4"/></br>[majsterkovic](https://github.com/majsterkovic) | <img width="50" src="https://avatars.githubusercontent.com/u/77744862?v=4"/></br>[mak2002](https://github.com/mak2002) | <img width="50" src="https://avatars.githubusercontent.com/u/30428258?v=4"/></br>[nmiquan](https://github.com/nmiquan) | <img width="50" src="https://avatars.githubusercontent.com/u/31123054?v=4"/></br>[nullpointer666](https://github.com/nullpointer666) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2979926?v=4"/></br>[oscaretu](https://github.com/oscaretu) | <img width="50" src="https://avatars.githubusercontent.com/u/36965591?v=4"/></br>[oskarsh](https://github.com/oskarsh) | <img width="50" src="https://avatars.githubusercontent.com/u/52031346?v=4"/></br>[osso73](https://github.com/osso73) | <img width="50" src="https://avatars.githubusercontent.com/u/29743024?v=4"/></br>[over-soul](https://github.com/over-soul) | <img width="50" src="https://avatars.githubusercontent.com/u/42961947?v=4"/></br>[pensierocrea](https://github.com/pensierocrea) |
| <img width="50" src="https://avatars.githubusercontent.com/u/45542782?v=4"/></br>[pomeloy](https://github.com/pomeloy) | <img width="50" src="https://avatars.githubusercontent.com/u/10206967?v=4"/></br>[rhtenhove](https://github.com/rhtenhove) | <img width="50" src="https://avatars.githubusercontent.com/u/16728217?v=4"/></br>[rikanotank1](https://github.com/rikanotank1) | <img width="50" src="https://avatars.githubusercontent.com/u/24560368?v=4"/></br>[rxliuli](https://github.com/rxliuli) | <img width="50" src="https://avatars.githubusercontent.com/u/14062932?v=4"/></br>[simonsan](https://github.com/simonsan) |
| <img width="50" src="https://avatars.githubusercontent.com/u/5004545?v=4"/></br>[stellarpower](https://github.com/stellarpower) | <img width="50" src="https://avatars.githubusercontent.com/u/20983267?v=4"/></br>[suixinio](https://github.com/suixinio) | <img width="50" src="https://avatars.githubusercontent.com/u/12995773?v=4"/></br>[sumomo-99](https://github.com/sumomo-99) | <img width="50" src="https://avatars.githubusercontent.com/u/367170?v=4"/></br>[xtatsux](https://github.com/xtatsux) | <img width="50" src="https://avatars.githubusercontent.com/u/6908872?v=4"/></br>[taw00](https://github.com/taw00) |
| <img width="50" src="https://avatars.githubusercontent.com/u/10956653?v=4"/></br>[tcassaert](https://github.com/tcassaert) | <img width="50" src="https://avatars.githubusercontent.com/u/46327531?v=4"/></br>[victante](https://github.com/victante) | <img width="50" src="https://avatars.githubusercontent.com/u/7252567?v=4"/></br>[Voltinus](https://github.com/Voltinus) | <img width="50" src="https://avatars.githubusercontent.com/u/2216902?v=4"/></br>[xcffl](https://github.com/xcffl) | <img width="50" src="https://avatars.githubusercontent.com/u/46404814?v=4"/></br>[yourcontact](https://github.com/yourcontact) |
| <img width="50" src="https://avatars.githubusercontent.com/u/37692927?v=4"/></br>[zaoyifan](https://github.com/zaoyifan) | <img width="50" src="https://avatars.githubusercontent.com/u/10813608?v=4"/></br>[zawnk](https://github.com/zawnk) | <img width="50" src="https://avatars.githubusercontent.com/u/55245068?v=4"/></br>[zen-quo](https://github.com/zen-quo) | <img width="50" src="https://avatars.githubusercontent.com/u/23507174?v=4"/></br>[zozolina123](https://github.com/zozolina123) | <img width="50" src="https://avatars.githubusercontent.com/u/25315?v=4"/></br>[xcession](https://github.com/xcession) |
| <img width="50" src="https://avatars.githubusercontent.com/u/34542665?v=4"/></br>[paventyang](https://github.com/paventyang) | <img width="50" src="https://avatars.githubusercontent.com/u/608014?v=4"/></br>[jackytsu](https://github.com/jackytsu) | <img width="50" src="https://avatars.githubusercontent.com/u/1308646?v=4"/></br>[zhangmx](https://github.com/zhangmx) | | |
<!-- CONTRIBUTORS-TABLE-AUTO-GENERATED --> <!-- CONTRIBUTORS-TABLE-AUTO-GENERATED -->
# Known bugs # Known bugs
@@ -618,7 +654,7 @@ Thank you to everyone who've contributed to Joplin's source code!
MIT License MIT License
Copyright (c) 2016-2020 Laurent Cozic Copyright (c) 2016-2021 Laurent Cozic
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -86,6 +86,28 @@
</ul> </ul>
<p>To view what arguments are supported, you can open any of these files <p>To view what arguments are supported, you can open any of these files
and look at the <code>execute()</code> command.</p> and look at the <code>execute()</code> command.</p>
<a href="#executing-editor-commands" id="executing-editor-commands" style="color: inherit; text-decoration: none;">
<h2>Executing editor commands</h2>
</a>
<p>There might be a situation where you want to invoke editor commands
without using a <a href="joplincontentscripts.html">contentScript</a>. For this
reason Joplin provides the built in <code>editor.execCommand</code> command.</p>
<p><code>editor.execCommand</code> should work with any core command in both the
<a href="https://codemirror.net/doc/manual.html#execCommand">CodeMirror</a> and
<a href="https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand">TinyMCE</a> editors,
as well as most functions calls directly on a CodeMirror editor object (extensions).</p>
<ul>
<li><a href="https://codemirror.net/doc/manual.html#commands">CodeMirror commands</a></li>
<li><a href="https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands">TinyMCE core editor commands</a></li>
</ul>
<p><code>editor.execCommand</code> supports adding arguments for the commands.</p>
<pre><code class="language-typescript"><span class="hljs-keyword">await</span> joplin.commands.execute(<span class="hljs-string">&#x27;editor.execCommand&#x27;</span>, {
<span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;madeUpCommand&#x27;</span>, <span class="hljs-comment">// CodeMirror and TinyMCE</span>
<span class="hljs-attr">args</span>: [], <span class="hljs-comment">// CodeMirror and TinyMCE</span>
<span class="hljs-attr">ui</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// TinyMCE only</span>
<span class="hljs-attr">value</span>: <span class="hljs-string">&#x27;&#x27;</span>, <span class="hljs-comment">// TinyMCE only</span>
});</code></pre>
<p><a href="https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts">View the example using the CodeMirror editor</a></p>
</div> </div>
</section> </section>
<!-- <!--

View File

@@ -149,10 +149,8 @@
<p>Currently the supported context variables aren&#39;t documented, but you can <p>Currently the supported context variables aren&#39;t documented, but you can
find the list below:</p> find the list below:</p>
<ul> <ul>
<li>[Global When <li><a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">Global When Clauses</a></li>
Clauses](<a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts</a>).</li> <li><a href="https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts">Desktop app When Clauses</a></li>
<li>[Desktop app When
Clauses](<a href="https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts">https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts</a>).</li>
</ul> </ul>
<p>Note: Commands are enabled by default unless you use this property.</p> <p>Note: Commands are enabled by default unless you use this property.</p>
</div> </div>

View File

@@ -405,6 +405,12 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog.md
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p> <p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p>
<hr> <hr>
<h1>Joplin changelog<a name="joplin-changelog" href="#joplin-changelog" class="heading-anchor">🔗</a></h1> <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/v2.0.4">v2.0.4</a> (Pre-release) - 2021-06-02T12:54:17Z<a name="v2-0-4-https-github-com-laurent22-joplin-releases-tag-v2-0-4-pre-release-2021-06-02t12-54-17z" href="#v2-0-4-https-github-com-laurent22-joplin-releases-tag-v2-0-4-pre-release-2021-06-02t12-54-17z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Improved: Download plugins from GitHub release (8f6a475)</li>
<li>Fixed: Count tags based on showCompletedTodos setting (<a href="https://github.com/laurent22/joplin/issues/4957">#4957</a>) (<a href="https://github.com/laurent22/joplin/issues/4411">#4411</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
<li>Fixed: Fixes panels overflowing window (<a href="https://github.com/laurent22/joplin/issues/4991">#4991</a>) (<a href="https://github.com/laurent22/joplin/issues/4864">#4864</a> by <a href="https://github.com/mablin7">@mablin7</a>)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.2">v2.0.2</a> (Pre-release) - 2021-05-21T18:07:48Z<a name="v2-0-2-https-github-com-laurent22-joplin-releases-tag-v2-0-2-pre-release-2021-05-21t18-07-48z" href="#v2-0-2-https-github-com-laurent22-joplin-releases-tag-v2-0-2-pre-release-2021-05-21t18-07-48z" class="heading-anchor">🔗</a></h2> <h2><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.2">v2.0.2</a> (Pre-release) - 2021-05-21T18:07:48Z<a name="v2-0-2-https-github-com-laurent22-joplin-releases-tag-v2-0-2-pre-release-2021-05-21t18-07-48z" href="#v2-0-2-https-github-com-laurent22-joplin-releases-tag-v2-0-2-pre-release-2021-05-21t18-07-48z" class="heading-anchor">🔗</a></h2>
<ul> <ul>
<li>New: Add Share Notebook menu item (6f2f241)</li> <li>New: Add Share Notebook menu item (6f2f241)</li>

View File

@@ -405,6 +405,40 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog_server.md
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p> <p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p>
<hr> <hr>
<h1>Joplin Server Changelog<a name="joplin-server-changelog" href="#joplin-server-changelog" class="heading-anchor">🔗</a></h1> <h1>Joplin Server Changelog<a name="joplin-server-changelog" href="#joplin-server-changelog" class="heading-anchor">🔗</a></h1>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.5">server-v2.0.5</a> (Pre-release) - 2021-06-02T08:14:47Z<a name="server-v2-0-5-https-github-com-laurent22-joplin-releases-tag-server-v2-0-5-pre-release-2021-06-02t08-14-47z" href="#server-v2-0-5-https-github-com-laurent22-joplin-releases-tag-server-v2-0-5-pre-release-2021-06-02t08-14-47z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add version number on website (0ef7e98)</li>
<li>New: Added signup pages (41ed66d)</li>
<li>Improved: Allow disabling item upload for a user (f8a26cf)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.4">server-v2.0.4</a> (Pre-release) - 2021-05-25T18:33:11Z<a name="server-v2-0-4-https-github-com-laurent22-joplin-releases-tag-server-v2-0-4-pre-release-2021-05-25t18-33-11z" href="#server-v2-0-4-https-github-com-laurent22-joplin-releases-tag-server-v2-0-4-pre-release-2021-05-25t18-33-11z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Fixed: Fixed Item and Log page when using Postgres (ee0f237)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.3">server-v2.0.3</a> (Pre-release) - 2021-05-25T18:08:46Z<a name="server-v2-0-3-https-github-com-laurent22-joplin-releases-tag-server-v2-0-3-pre-release-2021-05-25t18-08-46z" href="#server-v2-0-3-https-github-com-laurent22-joplin-releases-tag-server-v2-0-3-pre-release-2021-05-25t18-08-46z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Fixed: Fixed handling of request origin (12a6634)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.2">server-v2.0.2</a> (Pre-release) - 2021-05-25T19:15:50Z<a name="server-v2-0-2-https-github-com-laurent22-joplin-releases-tag-server-v2-0-2-pre-release-2021-05-25t19-15-50z" href="#server-v2-0-2-https-github-com-laurent22-joplin-releases-tag-server-v2-0-2-pre-release-2021-05-25t19-15-50z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add mailer service (ed8ee67)</li>
<li>New: Add support for item size limit (6afde54)</li>
<li>New: Added API end points to manage users (77b284f)</li>
<li>Improved: Allow enabling or disabling the sharing feature per user (daaaa13)</li>
<li>Improved: Allow setting the path to the SQLite database using SQLITE_DATABASE env variable (68e79f1)</li>
<li>Improved: Allow using a different domain for API, main website and user content (83cef7a)</li>
<li>Improved: Generate only one share link per note (e156ee1)</li>
<li>Improved: Go back to home page when there is an error and user is logged in (a24b009)</li>
<li>Improved: Improved Items table and added item size to it (7f05420)</li>
<li>Improved: Improved log table too and made it sortable (ec7f0f4)</li>
<li>Improved: Make it more difficult to delete all data (b01aa7e)</li>
<li>Improved: Redirect to correct page when trying to access the root (51051e0)</li>
<li>Improved: Use external directory to store Postgres data in Docker-compose config (71a7fc0)</li>
<li>Fixed: Fixed /items page when using Postgres (2d0580f)</li>
<li>Fixed: Fixed bug when unsharing a notebook that has no recipients (6ddb69e)</li>
<li>Fixed: Fixed deleting a note that has been shared (489995d)</li>
<li>Fixed: Make sure temp files are deleted after upload is done (#4540)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.1">server-v2.0.1</a> (Pre-release) - 2021-05-14T13:55:45Z<a name="server-v2-0-1-https-github-com-laurent22-joplin-releases-tag-server-v2-0-1-pre-release-2021-05-14t13-55-45z" href="#server-v2-0-1-https-github-com-laurent22-joplin-releases-tag-server-v2-0-1-pre-release-2021-05-14t13-55-45z" class="heading-anchor">🔗</a></h2> <h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.1">server-v2.0.1</a> (Pre-release) - 2021-05-14T13:55:45Z<a name="server-v2-0-1-https-github-com-laurent22-joplin-releases-tag-server-v2-0-1-pre-release-2021-05-14t13-55-45z" href="#server-v2-0-1-https-github-com-laurent22-joplin-releases-tag-server-v2-0-1-pre-release-2021-05-14t13-55-45z" class="heading-anchor">🔗</a></h2>
<ul> <ul>
<li>New: Add support for sharing notes via a link (ccbc329)</li> <li>New: Add support for sharing notes via a link (ccbc329)</li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -99,17 +99,17 @@
"sync.9.path": { "sync.9.path": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "Joplin Server URL. Attention: If you change this location, make sure you copy all your content to it before syncing, otherwise all files will be removed! See the FAQ for more details: https://joplinapp.org/faq/" "description": "Joplin Cloud URL. Attention: If you change this location, make sure you copy all your content to it before syncing, otherwise all files will be removed! See the FAQ for more details: https://joplinapp.org/faq/"
}, },
"sync.9.username": { "sync.9.username": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "Joplin Server email" "description": "Joplin Cloud email"
}, },
"sync.9.password": { "sync.9.password": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "Joplin Server password", "description": "Joplin Cloud password",
"$comment": "private" "$comment": "private"
}, },
"sync.5.syncTargets": { "sync.5.syncTargets": {

View File

@@ -415,15 +415,15 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tbody> <tbody>
<tr> <tr>
<td>Total Windows downloads</td> <td>Total Windows downloads</td>
<td>1,425,567</td> <td>1,444,540</td>
</tr> </tr>
<tr> <tr>
<td>Total macOs downloads</td> <td>Total macOs downloads</td>
<td>554,909</td> <td>561,465</td>
</tr> </tr>
<tr> <tr>
<td>Total Linux downloads</td> <td>Total Linux downloads</td>
<td>463,554</td> <td>473,228</td>
</tr> </tr>
<tr> <tr>
<td>Windows %</td> <td>Windows %</td>
@@ -453,92 +453,100 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.4">v2.0.4</a> (p)</td>
<td>2021-06-02T12:54:17Z</td>
<td>898</td>
<td>267</td>
<td>242</td>
<td>1,407</td>
</tr>
<tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.2">v2.0.2</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.2">v2.0.2</a> (p)</td>
<td>2021-05-21T18:07:48Z</td> <td>2021-05-21T18:07:48Z</td>
<td>594</td> <td>1,953</td>
<td>179</td> <td>470</td>
<td>448</td> <td>1,554</td>
<td>1,221</td> <td>3,977</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.1">v2.0.1</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.1">v2.0.1</a> (p)</td>
<td>2021-05-15T13:22:58Z</td> <td>2021-05-15T13:22:58Z</td>
<td>770</td> <td>784</td>
<td>243</td> <td>245</td>
<td>984</td> <td>994</td>
<td>1,997</td> <td>2,023</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.5">v1.8.5</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.5">v1.8.5</a></td>
<td>2021-05-10T11:58:14Z</td> <td>2021-05-10T11:58:14Z</td>
<td>11,870</td> <td>27,272</td>
<td>6,670</td> <td>12,591</td>
<td>5,753</td> <td>13,983</td>
<td>24,293</td> <td>53,846</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.4">v1.8.4</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.4">v1.8.4</a> (p)</td>
<td>2021-05-09T18:05:05Z</td> <td>2021-05-09T18:05:05Z</td>
<td>623</td> <td>656</td>
<td>120</td> <td>120</td>
<td>433</td> <td>433</td>
<td>1,176</td> <td>1,209</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.3">v1.8.3</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.3">v1.8.3</a> (p)</td>
<td>2021-05-04T10:38:16Z</td> <td>2021-05-04T10:38:16Z</td>
<td>1,049</td> <td>1,280</td>
<td>290</td> <td>293</td>
<td>912</td> <td>912</td>
<td>2,251</td> <td>2,485</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.2">v1.8.2</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.2">v1.8.2</a> (p)</td>
<td>2021-04-25T10:50:51Z</td> <td>2021-04-25T10:50:51Z</td>
<td>1,445</td> <td>1,473</td>
<td>421</td> <td>421</td>
<td>1,261</td> <td>1,261</td>
<td>3,127</td> <td>3,155</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.1">v1.8.1</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.1">v1.8.1</a> (p)</td>
<td>2021-03-29T10:46:41Z</td> <td>2021-03-29T10:46:41Z</td>
<td>3,003</td> <td>3,025</td>
<td>805</td> <td>805</td>
<td>2,418</td> <td>2,419</td>
<td>6,226</td> <td>6,249</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.11">v1.7.11</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.11">v1.7.11</a></td>
<td>2021-02-03T12:50:01Z</td> <td>2021-02-03T12:50:01Z</td>
<td>113,794</td> <td>113,946</td>
<td>42,526</td> <td>42,556</td>
<td>64,040</td> <td>64,079</td>
<td>220,360</td> <td>220,581</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.10">v1.7.10</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.10">v1.7.10</a></td>
<td>2021-01-30T13:25:29Z</td> <td>2021-01-30T13:25:29Z</td>
<td>13,825</td> <td>13,830</td>
<td>4,831</td> <td>4,831</td>
<td>4,425</td> <td>4,429</td>
<td>23,081</td> <td>23,090</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.9">v1.7.9</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.9">v1.7.9</a> (p)</td>
<td>2021-01-28T09:50:21Z</td> <td>2021-01-28T09:50:21Z</td>
<td>480</td> <td>481</td>
<td>123</td> <td>123</td>
<td>483</td> <td>483</td>
<td>1,086</td> <td>1,087</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.6">v1.7.6</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.6">v1.7.6</a> (p)</td>
<td>2021-01-27T10:36:05Z</td> <td>2021-01-27T10:36:05Z</td>
<td>283</td> <td>284</td>
<td>82</td> <td>82</td>
<td>277</td> <td>277</td>
<td>642</td> <td>643</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.5">v1.7.5</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.5">v1.7.5</a> (p)</td>
@@ -559,10 +567,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.8">v1.6.8</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.8">v1.6.8</a></td>
<td>2021-01-20T18:11:34Z</td> <td>2021-01-20T18:11:34Z</td>
<td>18,064</td> <td>18,148</td>
<td>7,662</td> <td>7,665</td>
<td>7,578</td> <td>7,581</td>
<td>33,304</td> <td>33,394</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.3">v1.7.3</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.3">v1.7.3</a> (p)</td>
@@ -575,50 +583,50 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.7">v1.6.7</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.7">v1.6.7</a></td>
<td>2021-01-11T23:20:33Z</td> <td>2021-01-11T23:20:33Z</td>
<td>10,375</td> <td>10,418</td>
<td>4,617</td> <td>4,619</td>
<td>4,531</td> <td>4,531</td>
<td>19,523</td> <td>19,568</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.6">v1.6.6</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.6">v1.6.6</a></td>
<td>2021-01-09T16:15:31Z</td> <td>2021-01-09T16:15:31Z</td>
<td>12,359</td> <td>12,362</td>
<td>3,405</td> <td>3,405</td>
<td>4,776</td> <td>4,776</td>
<td>20,540</td> <td>20,543</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.5">v1.6.5</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.5">v1.6.5</a> (p)</td>
<td>2021-01-09T01:24:32Z</td> <td>2021-01-09T01:24:32Z</td>
<td>553</td> <td>580</td>
<td>57</td> <td>57</td>
<td>300</td> <td>300</td>
<td>910</td> <td>937</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.4">v1.6.4</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.4">v1.6.4</a> (p)</td>
<td>2021-01-07T19:11:32Z</td> <td>2021-01-07T19:11:32Z</td>
<td>381</td> <td>381</td>
<td>72</td> <td>73</td>
<td>197</td> <td>197</td>
<td>650</td> <td>651</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.2">v1.6.2</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.2">v1.6.2</a> (p)</td>
<td>2021-01-04T22:34:55Z</td> <td>2021-01-04T22:34:55Z</td>
<td>664</td> <td>665</td>
<td>221</td> <td>221</td>
<td>577</td> <td>577</td>
<td>1,462</td> <td>1,463</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.14">v1.5.14</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.14">v1.5.14</a></td>
<td>2020-12-30T01:48:46Z</td> <td>2020-12-30T01:48:46Z</td>
<td>10,847</td> <td>10,889</td>
<td>5,191</td> <td>5,192</td>
<td>5,512</td> <td>5,512</td>
<td>21,550</td> <td>21,593</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.1">v1.6.1</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.1">v1.6.1</a> (p)</td>
@@ -639,18 +647,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.12">v1.5.12</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.12">v1.5.12</a></td>
<td>2020-12-28T15:14:08Z</td> <td>2020-12-28T15:14:08Z</td>
<td>2,374</td> <td>2,378</td>
<td>1,762</td> <td>1,762</td>
<td>911</td> <td>911</td>
<td>5,047</td> <td>5,051</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.11">v1.5.11</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.11">v1.5.11</a></td>
<td>2020-12-27T19:54:07Z</td> <td>2020-12-27T19:54:07Z</td>
<td>13,999</td> <td>14,009</td>
<td>4,605</td> <td>4,605</td>
<td>4,253</td> <td>4,254</td>
<td>22,857</td> <td>22,868</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.10">v1.5.10</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.10">v1.5.10</a> (p)</td>
@@ -664,9 +672,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.9">v1.5.9</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.9">v1.5.9</a> (p)</td>
<td>2020-12-23T18:01:08Z</td> <td>2020-12-23T18:01:08Z</td>
<td>321</td> <td>321</td>
<td>367</td> <td>368</td>
<td>399</td> <td>400</td>
<td>1,087</td> <td>1,089</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.8">v1.5.8</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.8">v1.5.8</a> (p)</td>
@@ -695,26 +703,26 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.19">v1.4.19</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.19">v1.4.19</a></td>
<td>2020-12-01T11:11:16Z</td> <td>2020-12-01T11:11:16Z</td>
<td>25,492</td> <td>25,530</td>
<td>13,350</td> <td>13,358</td>
<td>11,610</td> <td>11,615</td>
<td>50,452</td> <td>50,503</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.18">v1.4.18</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.18">v1.4.18</a></td>
<td>2020-11-28T12:21:41Z</td> <td>2020-11-28T12:21:41Z</td>
<td>11,082</td> <td>11,087</td>
<td>3,870</td> <td>3,871</td>
<td>3,076</td> <td>3,081</td>
<td>18,028</td> <td>18,039</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.16">v1.4.16</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.16">v1.4.16</a></td>
<td>2020-11-27T19:40:16Z</td> <td>2020-11-27T19:40:16Z</td>
<td>1,452</td> <td>1,457</td>
<td>822</td> <td>822</td>
<td>584</td> <td>584</td>
<td>2,858</td> <td>2,863</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.15">v1.4.15</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.15">v1.4.15</a></td>
@@ -727,18 +735,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.12">v1.4.12</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.12">v1.4.12</a></td>
<td>2020-11-23T18:58:07Z</td> <td>2020-11-23T18:58:07Z</td>
<td>2,983</td> <td>2,994</td>
<td>1,316</td> <td>1,316</td>
<td>1,287</td> <td>1,287</td>
<td>5,586</td> <td>5,597</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.11">v1.4.11</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.11">v1.4.11</a> (p)</td>
<td>2020-11-19T23:06:51Z</td> <td>2020-11-19T23:06:51Z</td>
<td>946</td> <td>974</td>
<td>147</td> <td>148</td>
<td>574</td> <td>574</td>
<td>1,667</td> <td>1,696</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.10">v1.4.10</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.10">v1.4.10</a> (p)</td>
@@ -751,10 +759,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.9">v1.4.9</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.9">v1.4.9</a> (p)</td>
<td>2020-11-11T14:23:17Z</td> <td>2020-11-11T14:23:17Z</td>
<td>497</td> <td>499</td>
<td>133</td> <td>133</td>
<td>393</td> <td>393</td>
<td>1,023</td> <td>1,025</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.7">v1.4.7</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.7">v1.4.7</a> (p)</td>
@@ -767,10 +775,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.18">v1.3.18</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.18">v1.3.18</a></td>
<td>2020-11-06T12:07:02Z</td> <td>2020-11-06T12:07:02Z</td>
<td>30,609</td> <td>30,668</td>
<td>11,316</td> <td>11,316</td>
<td>10,495</td> <td>10,495</td>
<td>52,420</td> <td>52,479</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.17">v1.3.17</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.17">v1.3.17</a> (p)</td>
@@ -783,18 +791,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.6">v1.4.6</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.6">v1.4.6</a> (p)</td>
<td>2020-11-05T22:44:12Z</td> <td>2020-11-05T22:44:12Z</td>
<td>339</td> <td>341</td>
<td>86</td> <td>86</td>
<td>45</td> <td>45</td>
<td>470</td> <td>472</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.15">v1.3.15</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.15">v1.3.15</a></td>
<td>2020-11-04T12:22:50Z</td> <td>2020-11-04T12:22:50Z</td>
<td>2,221</td> <td>2,223</td>
<td>1,290</td> <td>1,290</td>
<td>836</td> <td>836</td>
<td>4,347</td> <td>4,349</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.11">v1.3.11</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.11">v1.3.11</a> (p)</td>
@@ -807,10 +815,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.10">v1.3.10</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.10">v1.3.10</a> (p)</td>
<td>2020-10-29T13:27:14Z</td> <td>2020-10-29T13:27:14Z</td>
<td>368</td> <td>369</td>
<td>107</td> <td>107</td>
<td>307</td> <td>307</td>
<td>782</td> <td>783</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.9">v1.3.9</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.9">v1.3.9</a> (p)</td>
@@ -848,9 +856,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.3">v1.3.3</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.3">v1.3.3</a> (p)</td>
<td>2020-10-17T10:56:57Z</td> <td>2020-10-17T10:56:57Z</td>
<td>113</td> <td>113</td>
<td>36</td> <td>38</td>
<td>25</td> <td>25</td>
<td>174</td> <td>176</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.2">v1.3.2</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.2">v1.3.2</a> (p)</td>
@@ -864,17 +872,17 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.1">v1.3.1</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.1">v1.3.1</a> (p)</td>
<td>2020-10-11T15:10:18Z</td> <td>2020-10-11T15:10:18Z</td>
<td>77</td> <td>77</td>
<td>45</td> <td>46</td>
<td>35</td> <td>36</td>
<td>157</td> <td>159</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.6">v1.2.6</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.6">v1.2.6</a></td>
<td>2020-10-09T13:56:59Z</td> <td>2020-10-09T13:56:59Z</td>
<td>44,164</td> <td>44,215</td>
<td>17,713</td> <td>17,713</td>
<td>14,024</td> <td>14,026</td>
<td>75,901</td> <td>75,954</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.4">v1.2.4</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.4">v1.2.4</a> (p)</td>
@@ -895,18 +903,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.2">v1.2.2</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.2">v1.2.2</a> (p)</td>
<td>2020-09-22T20:31:55Z</td> <td>2020-09-22T20:31:55Z</td>
<td>777</td> <td>779</td>
<td>199</td> <td>199</td>
<td>631</td> <td>631</td>
<td>1,607</td> <td>1,609</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.4">v1.1.4</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.4">v1.1.4</a></td>
<td>2020-09-21T11:20:09Z</td> <td>2020-09-21T11:20:09Z</td>
<td>27,572</td> <td>27,592</td>
<td>13,489</td> <td>13,490</td>
<td>7,740</td> <td>7,740</td>
<td>48,801</td> <td>48,822</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.3">v1.1.3</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.3">v1.1.3</a> (p)</td>
@@ -927,42 +935,42 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.1">v1.1.1</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.1">v1.1.1</a> (p)</td>
<td>2020-09-11T23:32:47Z</td> <td>2020-09-11T23:32:47Z</td>
<td>519</td> <td>521</td>
<td>195</td> <td>195</td>
<td>342</td> <td>342</td>
<td>1,056</td> <td>1,058</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.245">v1.0.245</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.245">v1.0.245</a></td>
<td>2020-09-09T12:56:10Z</td> <td>2020-09-09T12:56:10Z</td>
<td>21,148</td> <td>21,177</td>
<td>9,999</td> <td>9,999</td>
<td>5,634</td> <td>5,634</td>
<td>36,781</td> <td>36,810</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.242">v1.0.242</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.242">v1.0.242</a></td>
<td>2020-09-04T22:00:34Z</td> <td>2020-09-04T22:00:34Z</td>
<td>12,439</td> <td>12,446</td>
<td>6,418</td> <td>6,418</td>
<td>3,015</td> <td>3,015</td>
<td>21,872</td> <td>21,879</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.241">v1.0.241</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.241">v1.0.241</a></td>
<td>2020-09-04T18:06:00Z</td> <td>2020-09-04T18:06:00Z</td>
<td>23,628</td> <td>23,665</td>
<td>5,748</td> <td>5,749</td>
<td>4,994</td> <td>4,995</td>
<td>34,370</td> <td>34,409</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.239">v1.0.239</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.239">v1.0.239</a> (p)</td>
<td>2020-09-01T21:56:36Z</td> <td>2020-09-01T21:56:36Z</td>
<td>599</td> <td>601</td>
<td>226</td> <td>226</td>
<td>400</td> <td>400</td>
<td>1,225</td> <td>1,227</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.237">v1.0.237</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.237">v1.0.237</a> (p)</td>
@@ -983,26 +991,26 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.235">v1.0.235</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.235">v1.0.235</a> (p)</td>
<td>2020-08-18T22:08:01Z</td> <td>2020-08-18T22:08:01Z</td>
<td>1,671</td> <td>1,673</td>
<td>489</td> <td>489</td>
<td>920</td> <td>920</td>
<td>3,080</td> <td>3,082</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.234">v1.0.234</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.234">v1.0.234</a> (p)</td>
<td>2020-08-17T23:13:02Z</td> <td>2020-08-17T23:13:02Z</td>
<td>536</td> <td>538</td>
<td>125</td> <td>125</td>
<td>100</td> <td>100</td>
<td>761</td> <td>763</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.233">v1.0.233</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.233">v1.0.233</a></td>
<td>2020-08-01T14:51:15Z</td> <td>2020-08-01T14:51:15Z</td>
<td>43,098</td> <td>43,153</td>
<td>18,188</td> <td>18,188</td>
<td>12,358</td> <td>12,358</td>
<td>73,644</td> <td>73,699</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.232">v1.0.232</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.232">v1.0.232</a> (p)</td>
@@ -1015,10 +1023,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.227">v1.0.227</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.227">v1.0.227</a></td>
<td>2020-07-07T20:44:54Z</td> <td>2020-07-07T20:44:54Z</td>
<td>40,384</td> <td>40,414</td>
<td>15,273</td> <td>15,274</td>
<td>9,627</td> <td>9,629</td>
<td>65,284</td> <td>65,317</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.226">v1.0.226</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.226">v1.0.226</a> (p)</td>
@@ -1031,10 +1039,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.224">v1.0.224</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.224">v1.0.224</a></td>
<td>2020-06-20T22:26:08Z</td> <td>2020-06-20T22:26:08Z</td>
<td>24,774</td> <td>24,788</td>
<td>11,005</td> <td>11,005</td>
<td>6,006</td> <td>6,006</td>
<td>41,785</td> <td>41,799</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.223">v1.0.223</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.223">v1.0.223</a> (p)</td>
@@ -1055,18 +1063,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.220">v1.0.220</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.220">v1.0.220</a></td>
<td>2020-06-13T18:26:22Z</td> <td>2020-06-13T18:26:22Z</td>
<td>31,712</td> <td>31,734</td>
<td>9,916</td> <td>9,916</td>
<td>6,411</td> <td>6,411</td>
<td>48,039</td> <td>48,061</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.218">v1.0.218</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.218">v1.0.218</a></td>
<td>2020-06-07T10:43:34Z</td> <td>2020-06-07T10:43:34Z</td>
<td>14,535</td> <td>14,536</td>
<td>6,968</td> <td>6,968</td>
<td>2,954</td> <td>2,954</td>
<td>24,457</td> <td>24,458</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.217">v1.0.217</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.217">v1.0.217</a> (p)</td>
@@ -1079,18 +1087,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.216">v1.0.216</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.216">v1.0.216</a></td>
<td>2020-05-24T14:21:01Z</td> <td>2020-05-24T14:21:01Z</td>
<td>37,277</td> <td>37,327</td>
<td>14,268</td> <td>14,269</td>
<td>10,177</td> <td>10,177</td>
<td>61,722</td> <td>61,773</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.214">v1.0.214</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.214">v1.0.214</a> (p)</td>
<td>2020-05-21T17:15:15Z</td> <td>2020-05-21T17:15:15Z</td>
<td>6,529</td> <td>6,545</td>
<td>3,466</td> <td>3,466</td>
<td>760</td> <td>760</td>
<td>10,755</td> <td>10,771</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.212">v1.0.212</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.212">v1.0.212</a> (p)</td>
@@ -1119,18 +1127,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.207">v1.0.207</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.207">v1.0.207</a> (p)</td>
<td>2020-05-10T16:37:35Z</td> <td>2020-05-10T16:37:35Z</td>
<td>1,187</td> <td>1,188</td>
<td>263</td> <td>263</td>
<td>1,016</td> <td>1,016</td>
<td>2,466</td> <td>2,467</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.201">v1.0.201</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.201">v1.0.201</a></td>
<td>2020-04-15T22:55:13Z</td> <td>2020-04-15T22:55:13Z</td>
<td>53,311</td> <td>53,324</td>
<td>20,043</td> <td>20,043</td>
<td>18,180</td> <td>18,180</td>
<td>91,534</td> <td>91,547</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.200">v1.0.200</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.200">v1.0.200</a></td>
@@ -1143,98 +1151,98 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.199">v1.0.199</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.199">v1.0.199</a></td>
<td>2020-04-10T18:41:58Z</td> <td>2020-04-10T18:41:58Z</td>
<td>19,339</td> <td>19,347</td>
<td>5,884</td> <td>5,884</td>
<td>3,788</td> <td>3,788</td>
<td>29,011</td> <td>29,019</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.197">v1.0.197</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.197">v1.0.197</a></td>
<td>2020-03-30T17:21:22Z</td> <td>2020-03-30T17:21:22Z</td>
<td>22,280</td> <td>22,290</td>
<td>9,540</td> <td>9,540</td>
<td>5,726</td> <td>5,734</td>
<td>37,546</td> <td>37,564</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.195">v1.0.195</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.195">v1.0.195</a></td>
<td>2020-03-22T19:56:12Z</td> <td>2020-03-22T19:56:12Z</td>
<td>18,890</td> <td>18,892</td>
<td>7,948</td> <td>7,949</td>
<td>4,506</td> <td>4,506</td>
<td>31,344</td> <td>31,347</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.194">v1.0.194</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.194">v1.0.194</a> (p)</td>
<td>2020-03-14T00:00:32Z</td> <td>2020-03-14T00:00:32Z</td>
<td>1,285</td> <td>1,285</td>
<td>1,375</td> <td>1,377</td>
<td>511</td> <td>513</td>
<td>3,171</td> <td>3,175</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.193">v1.0.193</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.193">v1.0.193</a></td>
<td>2020-03-08T08:58:53Z</td> <td>2020-03-08T08:58:53Z</td>
<td>28,641</td> <td>28,642</td>
<td>10,907</td> <td>10,907</td>
<td>7,392</td> <td>7,393</td>
<td>46,940</td> <td>46,942</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.192">v1.0.192</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.192">v1.0.192</a> (p)</td>
<td>2020-03-06T23:27:52Z</td> <td>2020-03-06T23:27:52Z</td>
<td>472</td> <td>473</td>
<td>122</td> <td>122</td>
<td>89</td> <td>89</td>
<td>683</td> <td>684</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.190">v1.0.190</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.190">v1.0.190</a> (p)</td>
<td>2020-03-06T01:22:22Z</td> <td>2020-03-06T01:22:22Z</td>
<td>373</td> <td>374</td>
<td>90</td> <td>90</td>
<td>85</td> <td>85</td>
<td>548</td> <td>549</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.189">v1.0.189</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.189">v1.0.189</a> (p)</td>
<td>2020-03-04T17:27:15Z</td> <td>2020-03-04T17:27:15Z</td>
<td>342</td> <td>343</td>
<td>96</td> <td>96</td>
<td>90</td> <td>90</td>
<td>528</td> <td>529</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.187">v1.0.187</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.187">v1.0.187</a> (p)</td>
<td>2020-03-01T12:31:06Z</td> <td>2020-03-01T12:31:06Z</td>
<td>919</td> <td>920</td>
<td>230</td> <td>230</td>
<td>263</td> <td>263</td>
<td>1,412</td> <td>1,413</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.179">v1.0.179</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.179">v1.0.179</a></td>
<td>2020-01-24T22:42:41Z</td> <td>2020-01-24T22:42:41Z</td>
<td>71,023</td> <td>71,040</td>
<td>28,545</td> <td>28,550</td>
<td>22,534</td> <td>22,535</td>
<td>122,102</td> <td>122,125</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.178">v1.0.178</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.178">v1.0.178</a></td>
<td>2020-01-20T19:06:45Z</td> <td>2020-01-20T19:06:45Z</td>
<td>17,539</td> <td>17,540</td>
<td>5,962</td> <td>5,962</td>
<td>2,584</td> <td>2,584</td>
<td>26,085</td> <td>26,086</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.177">v1.0.177</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.177">v1.0.177</a> (p)</td>
<td>2019-12-30T14:40:40Z</td> <td>2019-12-30T14:40:40Z</td>
<td>1,943</td> <td>1,944</td>
<td>438</td> <td>438</td>
<td>678</td> <td>679</td>
<td>3,059</td> <td>3,061</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.176">v1.0.176</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.176">v1.0.176</a> (p)</td>
@@ -1247,42 +1255,42 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.175">v1.0.175</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.175">v1.0.175</a></td>
<td>2019-12-08T11:48:47Z</td> <td>2019-12-08T11:48:47Z</td>
<td>72,519</td> <td>72,538</td>
<td>16,905</td> <td>16,906</td>
<td>16,509</td> <td>16,509</td>
<td>105,933</td> <td>105,953</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.174">v1.0.174</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.174">v1.0.174</a></td>
<td>2019-11-12T18:20:58Z</td> <td>2019-11-12T18:20:58Z</td>
<td>30,401</td> <td>30,407</td>
<td>11,722</td> <td>11,722</td>
<td>8,221</td> <td>8,221</td>
<td>50,344</td> <td>50,350</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.173">v1.0.173</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.173">v1.0.173</a></td>
<td>2019-11-11T08:33:35Z</td> <td>2019-11-11T08:33:35Z</td>
<td>5,072</td> <td>5,074</td>
<td>2,077</td> <td>2,077</td>
<td>743</td> <td>743</td>
<td>7,892</td> <td>7,894</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.170">v1.0.170</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.170">v1.0.170</a></td>
<td>2019-10-13T22:13:04Z</td> <td>2019-10-13T22:13:04Z</td>
<td>27,413</td> <td>27,424</td>
<td>8,752</td> <td>8,752</td>
<td>7,675</td> <td>7,675</td>
<td>43,840</td> <td>43,851</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.169">v1.0.169</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.169">v1.0.169</a></td>
<td>2019-09-27T18:35:13Z</td> <td>2019-09-27T18:35:13Z</td>
<td>17,097</td> <td>17,098</td>
<td>5,921</td> <td>5,921</td>
<td>3,754</td> <td>3,754</td>
<td>26,772</td> <td>26,773</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.168">v1.0.168</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.168">v1.0.168</a></td>
@@ -1295,10 +1303,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.167">v1.0.167</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.167">v1.0.167</a></td>
<td>2019-09-10T08:48:37Z</td> <td>2019-09-10T08:48:37Z</td>
<td>16,790</td> <td>16,791</td>
<td>5,704</td> <td>5,704</td>
<td>3,703</td> <td>3,703</td>
<td>26,197</td> <td>26,198</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.166">v1.0.166</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.166">v1.0.166</a></td>
@@ -1311,34 +1319,34 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.165">v1.0.165</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.165">v1.0.165</a></td>
<td>2019-08-14T21:46:29Z</td> <td>2019-08-14T21:46:29Z</td>
<td>18,898</td> <td>18,903</td>
<td>6,972</td> <td>6,972</td>
<td>5,462</td> <td>5,462</td>
<td>31,332</td> <td>31,337</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.161">v1.0.161</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.161">v1.0.161</a></td>
<td>2019-07-13T18:30:00Z</td> <td>2019-07-13T18:30:00Z</td>
<td>19,285</td> <td>19,287</td>
<td>6,352</td> <td>6,352</td>
<td>4,136</td> <td>4,136</td>
<td>29,773</td> <td>29,775</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.160">v1.0.160</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.160">v1.0.160</a></td>
<td>2019-06-15T00:21:40Z</td> <td>2019-06-15T00:21:40Z</td>
<td>30,531</td> <td>30,535</td>
<td>7,745</td> <td>7,746</td>
<td>8,101</td> <td>8,101</td>
<td>46,377</td> <td>46,382</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.159">v1.0.159</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.159">v1.0.159</a></td>
<td>2019-06-08T00:00:19Z</td> <td>2019-06-08T00:00:19Z</td>
<td>5,194</td> <td>5,194</td>
<td>2,178</td> <td>2,178</td>
<td>1,112</td> <td>1,113</td>
<td>8,484</td> <td>8,485</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.158">v1.0.158</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.158">v1.0.158</a></td>
@@ -1425,8 +1433,8 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td>2019-03-10T20:59:58Z</td> <td>2019-03-10T20:59:58Z</td>
<td>13,629</td> <td>13,629</td>
<td>4,171</td> <td>4,171</td>
<td>3,223</td> <td>3,227</td>
<td>21,023</td> <td>21,027</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.139">v1.0.139</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.139">v1.0.139</a> (p)</td>
@@ -1440,9 +1448,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.138">v1.0.138</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.138">v1.0.138</a> (p)</td>
<td>2019-03-03T17:23:00Z</td> <td>2019-03-03T17:23:00Z</td>
<td>150</td> <td>150</td>
<td>86</td> <td>87</td>
<td>84</td> <td>84</td>
<td>320</td> <td>321</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.137">v1.0.137</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.137">v1.0.137</a> (p)</td>
@@ -1455,10 +1463,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.135">v1.0.135</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.135">v1.0.135</a></td>
<td>2019-02-27T23:36:57Z</td> <td>2019-02-27T23:36:57Z</td>
<td>12,514</td> <td>12,515</td>
<td>3,958</td> <td>3,958</td>
<td>4,077</td> <td>4,077</td>
<td>20,549</td> <td>20,550</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.134">v1.0.134</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.134">v1.0.134</a></td>
@@ -1472,17 +1480,17 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.132">v1.0.132</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.132">v1.0.132</a></td>
<td>2019-02-26T23:02:05Z</td> <td>2019-02-26T23:02:05Z</td>
<td>1,088</td> <td>1,088</td>
<td>451</td> <td>452</td>
<td>95</td> <td>95</td>
<td>1,634</td> <td>1,635</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.127">v1.0.127</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.127">v1.0.127</a></td>
<td>2019-02-14T23:12:48Z</td> <td>2019-02-14T23:12:48Z</td>
<td>9,785</td> <td>9,786</td>
<td>3,171</td> <td>3,172</td>
<td>2,929</td> <td>2,929</td>
<td>15,885</td> <td>15,887</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.126">v1.0.126</a> (p)</td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.126">v1.0.126</a> (p)</td>
@@ -1504,9 +1512,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.120">v1.0.120</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.120">v1.0.120</a></td>
<td>2019-01-10T21:42:53Z</td> <td>2019-01-10T21:42:53Z</td>
<td>15,605</td> <td>15,605</td>
<td>5,201</td> <td>5,202</td>
<td>6,517</td> <td>6,517</td>
<td>27,323</td> <td>27,324</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.119">v1.0.119</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.119">v1.0.119</a></td>
@@ -1536,9 +1544,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.116">v1.0.116</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.116">v1.0.116</a></td>
<td>2018-11-20T19:09:24Z</td> <td>2018-11-20T19:09:24Z</td>
<td>3,474</td> <td>3,474</td>
<td>1,121</td> <td>1,122</td>
<td>714</td> <td>714</td>
<td>5,309</td> <td>5,310</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.115">v1.0.115</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.115">v1.0.115</a></td>
@@ -1559,10 +1567,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.111">v1.0.111</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.111">v1.0.111</a></td>
<td>2018-09-30T20:15:09Z</td> <td>2018-09-30T20:15:09Z</td>
<td>12,041</td> <td>12,042</td>
<td>3,307</td> <td>3,308</td>
<td>3,668</td> <td>3,669</td>
<td>19,016</td> <td>19,019</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.110">v1.0.110</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.110">v1.0.110</a></td>
@@ -1688,9 +1696,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.93">v1.0.93</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.93">v1.0.93</a></td>
<td>2018-05-14T11:36:01Z</td> <td>2018-05-14T11:36:01Z</td>
<td>1,791</td> <td>1,791</td>
<td>1,157</td> <td>1,158</td>
<td>759</td> <td>759</td>
<td>3,707</td> <td>3,708</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.91">v1.0.91</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.91">v1.0.91</a></td>
@@ -1719,10 +1727,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.83">v1.0.83</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.83">v1.0.83</a></td>
<td>2018-04-04T19:43:58Z</td> <td>2018-04-04T19:43:58Z</td>
<td>4,886</td> <td>4,892</td>
<td>2,532</td> <td>2,532</td>
<td>2,658</td> <td>2,658</td>
<td>10,076</td> <td>10,082</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.82">v1.0.82</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.82">v1.0.82</a></td>
@@ -2001,8 +2009,8 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
<td>2017-11-24T14:27:49Z</td> <td>2017-11-24T14:27:49Z</td>
<td>150</td> <td>150</td>
<td>696</td> <td>696</td>
<td>6,461</td> <td>6,463</td>
<td>7,307</td> <td>7,309</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://github.com/laurent22/joplin/releases/tag/v0.10.23">v0.10.23</a></td> <td><a href="https://github.com/laurent22/joplin/releases/tag/v0.10.23">v0.10.23</a></td>

View File

@@ -36,6 +36,7 @@
"releaseIOS": "node packages/tools/release-ios.js", "releaseIOS": "node packages/tools/release-ios.js",
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js", "releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
"releaseServer": "node packages/tools/release-server.js", "releaseServer": "node packages/tools/release-server.js",
"buildServerDocker": "node packages/tools/buildServerDocker.js",
"setupNewRelease": "node ./packages/tools/setupNewRelease", "setupNewRelease": "node ./packages/tools/setupNewRelease",
"test-ci": "lerna run test-ci --stream", "test-ci": "lerna run test-ci --stream",
"test": "lerna run test --stream", "test": "lerna run test --stream",

View File

@@ -71,20 +71,28 @@ class Command extends BaseCommand {
}; };
if (args.command === 'enable') { if (args.command === 'enable') {
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true }); const argPassword = options.password ? options.password.toString() : '';
const password = argPassword ? argPassword : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
if (!password) { if (!password) {
this.stdout(_('Operation cancelled')); this.stdout(_('Operation cancelled'));
return; return;
} }
const password2 = await this.prompt(_('Confirm password:'), { type: 'string', secure: true });
if (!password2) { // If the password was passed via command line, we don't ask for
this.stdout(_('Operation cancelled')); // confirmation. This is to allow setting up E2EE entirely from the
return; // command line.
} if (!argPassword) {
if (password !== password2) { const password2 = await this.prompt(_('Confirm password:'), { type: 'string', secure: true });
this.stdout(_('Passwords do not match!')); if (!password2) {
return; this.stdout(_('Operation cancelled'));
return;
}
if (password !== password2) {
this.stdout(_('Passwords do not match!'));
return;
}
} }
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password); await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
return; return;
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "joplin", "name": "joplin",
"version": "1.8.1", "version": "2.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -1806,80 +1806,6 @@
} }
} }
}, },
"clean-html": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/clean-html/-/clean-html-1.5.0.tgz",
"integrity": "sha512-eDu0vN44ZBvoEU0oRIKwWPIccGWXtdnUNmKJuTukZ1de00Uoqavb5pfIMKiC7/r+knQ5RbvAjGuVZiN3JwJL4Q==",
"requires": {
"htmlparser2": "^3.8.2",
"minimist": "^1.1.1"
},
"dependencies": {
"domelementtype": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
},
"domhandler": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
"requires": {
"domelementtype": "1"
}
},
"domutils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
},
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
"requires": {
"domelementtype": "^1.3.1",
"domhandler": "^2.3.0",
"domutils": "^1.5.1",
"entities": "^1.1.1",
"inherits": "^2.0.1",
"readable-stream": "^3.1.1"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
}
}
},
"cliui": { "cliui": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
@@ -2403,27 +2329,6 @@
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
"dev": true "dev": true
}, },
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
"requires": {
"domelementtype": "^2.0.1",
"entities": "^2.0.0"
},
"dependencies": {
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}
}
},
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"domexception": { "domexception": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
@@ -2545,11 +2450,6 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"entities": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
},
"error-ex": { "error-ex": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -4666,7 +4566,8 @@
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true "dev": true
}, },
"yargs": { "yargs": {

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
$katexcode$ Hello World:$katexcode$

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,5 @@
Hello World :
$$ $$
katexcode \sqrt{3x}
$$ $$

View File

@@ -4,7 +4,7 @@ import { setupDatabaseAndSynchronizer, switchClient, supportDir, createTempDir }
async function newRepoApi(): Promise<RepositoryApi> { async function newRepoApi(): Promise<RepositoryApi> {
const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir()); const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir());
await repo.loadManifests(); await repo.initialize();
return repo; return repo;
} }

View File

@@ -74,6 +74,7 @@ const commands = [
require('./gui/MainScreen/commands/toggleNoteList'), require('./gui/MainScreen/commands/toggleNoteList'),
require('./gui/MainScreen/commands/toggleSideBar'), require('./gui/MainScreen/commands/toggleSideBar'),
require('./gui/MainScreen/commands/toggleVisiblePanes'), require('./gui/MainScreen/commands/toggleVisiblePanes'),
require('./gui/MainScreen/commands/showPrompt'),
require('./gui/NoteEditor/commands/focusElementNoteBody'), require('./gui/NoteEditor/commands/focusElementNoteBody'),
require('./gui/NoteEditor/commands/focusElementNoteTitle'), require('./gui/NoteEditor/commands/focusElementNoteTitle'),
require('./gui/NoteEditor/commands/showLocalSearch'), require('./gui/NoteEditor/commands/showLocalSearch'),
@@ -96,6 +97,7 @@ const globalCommands = [
require('./commands/stopExternalEditing'), require('./commands/stopExternalEditing'),
require('./commands/toggleExternalEditing'), require('./commands/toggleExternalEditing'),
require('./commands/toggleSafeMode'), require('./commands/toggleSafeMode'),
require('./commands/restoreNoteRevision'),
require('@joplin/lib/commands/historyBackward'), require('@joplin/lib/commands/historyBackward'),
require('@joplin/lib/commands/historyForward'), require('@joplin/lib/commands/historyForward'),
require('@joplin/lib/commands/synchronize'), require('@joplin/lib/commands/synchronize'),

View File

@@ -0,0 +1,20 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import RevisionService from '@joplin/lib/services/RevisionService';
export const declaration: CommandDeclaration = {
name: 'restoreNoteRevision',
label: 'Restore a note from history',
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, noteId: string, reverseRevIndex: number = 0) => {
try {
const note = await RevisionService.instance().restoreNoteById(noteId, reverseRevIndex);
alert(RevisionService.instance().restoreSuccessMessage(note));
} catch (error) {
alert(error.message);
}
},
};
};

View File

@@ -113,7 +113,7 @@ export default function(props: Props) {
let loadError: Error = null; let loadError: Error = null;
try { try {
await repoApi().loadManifests(); await repoApi().initialize();
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
loadError = error; loadError = error;

View File

@@ -135,6 +135,7 @@ const commands = [
require('./commands/openNote'), require('./commands/openNote'),
require('./commands/openFolder'), require('./commands/openFolder'),
require('./commands/openTag'), require('./commands/openTag'),
require('./commands/showPrompt'),
]; ];
class MainScreenComponent extends React.Component<Props, State> { class MainScreenComponent extends React.Component<Props, State> {

View File

@@ -0,0 +1,41 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
export const declaration: CommandDeclaration = {
name: 'showPrompt',
};
enum PromptInputType {
Dropdown = 'dropdown',
Datetime = 'datetime',
Tags = 'tags',
Text = 'text',
}
interface PromptConfig {
label: string;
inputType?: PromptInputType;
value?: any;
autocomplete?: any[];
buttons?: string[];
}
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async (_context: CommandContext, config: PromptConfig) => {
return new Promise((resolve) => {
comp.setState({
promptOptions: {
...config,
onClose: async (answer: any, buttonType: string) => {
comp.setState({ promptOptions: null });
resolve({
answer: answer,
buttonType: buttonType,
});
},
},
});
});
},
};
};

View File

@@ -526,6 +526,14 @@ function useMenu(props: Props) {
click: () => { bridge().electronApp().hide(); }, click: () => { bridge().electronApp().hide(); },
} : noItem, } : noItem,
shim.isMac() ? {
role: 'hideothers',
} : noItem,
shim.isMac() ? {
role: 'unhide',
} : noItem,
{ {
type: 'separator', type: 'separator',
}, },

View File

@@ -224,10 +224,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
textHeading: () => addListItem('## ', ''), textHeading: () => addListItem('## ', ''),
textHorizontalRule: () => addListItem('* * *'), textHorizontalRule: () => addListItem('* * *'),
'editor.execCommand': (value: CommandValue) => { 'editor.execCommand': (value: CommandValue) => {
if (editorRef.current[value.name]) { if (!('args' in value)) value.args = [];
if (!('args' in value)) value.args = [];
if (editorRef.current[value.name]) {
editorRef.current[value.name](...value.args); editorRef.current[value.name](...value.args);
} else if (editorRef.current.commandExists(value.name)) {
editorRef.current.execCommand(value.name);
} else { } else {
reg.logger().warn('CodeMirror execCommand: unsupported command: ', value.name); reg.logger().warn('CodeMirror execCommand: unsupported command: ', value.name);
} }

View File

@@ -20,6 +20,7 @@ import useEditorSearch from './utils/useEditorSearch';
import useJoplinMode from './utils/useJoplinMode'; import useJoplinMode from './utils/useJoplinMode';
import useKeymap from './utils/useKeymap'; import useKeymap from './utils/useKeymap';
import useExternalPlugins from './utils/useExternalPlugins'; import useExternalPlugins from './utils/useExternalPlugins';
import useJoplinCommands from './utils/useJoplinCommands';
import 'codemirror/keymap/emacs'; import 'codemirror/keymap/emacs';
import 'codemirror/keymap/vim'; import 'codemirror/keymap/vim';
@@ -107,6 +108,7 @@ function Editor(props: EditorProps, ref: any) {
useJoplinMode(CodeMirror); useJoplinMode(CodeMirror);
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins); const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
useKeymap(CodeMirror); useKeymap(CodeMirror);
useJoplinCommands(CodeMirror);
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return editor; return editor;

View File

@@ -2,19 +2,76 @@ import { modifyListLines } from './useCursorUtils';
describe('useCursorUtils', () => { describe('useCursorUtils', () => {
const listWithDashes = `- item1 const listWithDashes = [
- item2 '- item1',
- item3`; '- item2',
'- item3',
];
const listNoDashes = `item1 const listWithNoPrefixes = [
item2 'item1',
item3`; 'item2',
'item3',
];
test('should remove "- " from beggining of each line of input string', () => { const listWithNumbers = [
expect(JSON.stringify(modifyListLines(listWithDashes.split('\n'), 0, '- '))).toBe(JSON.stringify(listNoDashes.split('\n'))); '1. item1',
'2. item2',
'3. item3',
];
const listWithOnes = [
'1. item1',
'1. item2',
'1. item3',
];
const listWithSomeNumbers = [
'1. item1',
'item2',
'2. item3',
];
const numberedListWithEmptyLines = [
'1. item1',
'2. item2',
'3. ' ,
'4. item3',
];
const noPrefixListWithEmptyLines = [
'item1',
'item2',
'' ,
'item3',
];
test('should remove "- " from beginning of each line of input string', () => {
expect(modifyListLines([...listWithDashes], NaN, '- ')).toStrictEqual(listWithNoPrefixes);
}); });
test('should add "- " at the beggining of each line of the input string', () => { test('should add "- " at the beginning of each line of the input string', () => {
expect(JSON.stringify(modifyListLines(listNoDashes.split('\n'), 0, '- '))).toBe(JSON.stringify(listWithDashes.split('\n'))); expect(modifyListLines([...listWithNoPrefixes], NaN, '- ')).toStrictEqual(listWithDashes);
});
test('should remove "n. " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithNumbers], 4, '1. ')).toStrictEqual(listWithNoPrefixes);
});
test('should add "n. " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithNoPrefixes], 1, '1. ')).toStrictEqual(listWithNumbers);
});
test('should remove "1. " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithOnes], 2, '1. ')).toStrictEqual(listWithNoPrefixes);
});
test('should remove "n. " from each line that has it, and ignore' +
' lines which do not', () => {
expect(modifyListLines([...listWithSomeNumbers], 2, '2. ')).toStrictEqual(listWithNoPrefixes);
});
test('should add numbers to each line including empty one', () => {
expect(modifyListLines(noPrefixListWithEmptyLines, 1, '1. ')).toStrictEqual(numberedListWithEmptyLines);
}); });
}); });

View File

@@ -1,20 +1,27 @@
import markdownUtils from '@joplin/lib/markdownUtils'; import markdownUtils from '@joplin/lib/markdownUtils';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
export function modifyListLines(lines: string[],num: number,listSymbol: string) { export function modifyListLines(lines: string[], num: number, listSymbol: string) {
const isNotNumbered = num === 1;
for (let j = 0; j < lines.length; j++) { for (let j = 0; j < lines.length; j++) {
const line = lines[j]; const line = lines[j];
if (!line && j === lines.length - 1) continue; if (!line && j === lines.length - 1) continue;
// Only add the list token if it's not already there // Only add the list token if it's not already there
// if it is, remove it // if it is, remove it
if (!line.startsWith(listSymbol)) { if (num) {
if (num) { const lineNum = markdownUtils.olLineNumber(line);
if (!lineNum && isNotNumbered) {
lines[j] = `${num.toString()}. ${line}`; lines[j] = `${num.toString()}. ${line}`;
num++; num++;
} else { } else {
lines[j] = listSymbol + line; const listToken = markdownUtils.extractListToken(line);
lines[j] = line.substr(listToken.length, line.length - listToken.length);
} }
} else { } else {
lines[j] = line.substr(listSymbol.length, line.length - listSymbol.length); if (!line.startsWith(listSymbol)) {
lines[j] = listSymbol + line;
} else {
lines[j] = line.substr(listSymbol.length, line.length - listSymbol.length);
}
} }
} }
return lines; return lines;

View File

@@ -0,0 +1,7 @@
// Helper commands added to the the CodeMirror instance
export default function useJoplinCommands(CodeMirror: any) {
CodeMirror.defineExtension('commandExists', function(name: string) {
return !!CodeMirror.commands[name];
});
}

View File

@@ -9,6 +9,7 @@ const { urlDecode } = require('@joplin/lib/string-utils');
const urlUtils = require('@joplin/lib/urlUtils'); const urlUtils = require('@joplin/lib/urlUtils');
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import { reg } from '@joplin/lib/registry'; import { reg } from '@joplin/lib/registry';
const uri2path = require('file-uri-to-path');
export default function useMessageHandler(scrollWhenReady: any, setScrollWhenReady: Function, editorRef: any, setLocalSearchResultCount: Function, dispatch: Function, formNote: FormNote) { export default function useMessageHandler(scrollWhenReady: any, setScrollWhenReady: Function, editorRef: any, setLocalSearchResultCount: Function, dispatch: Function, formNote: FormNote) {
return useCallback(async (event: any) => { return useCallback(async (event: any) => {
@@ -51,8 +52,14 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
} else if (urlUtils.urlProtocol(msg)) { } else if (urlUtils.urlProtocol(msg)) {
if (msg.indexOf('file://') === 0) { if (msg.indexOf('file://') === 0) {
// When using the file:// protocol, openPath doesn't work (does nothing) with URL-encoded paths // When using the file:// protocol, openPath doesn't work (does
require('electron').shell.openPath(urlDecode(msg)); // nothing) with URL-encoded paths.
//
// shell.openPath seems to work with file:// urls on Windows,
// but doesn't on macOS, so we need to convert it to a path
// before passing it to openPath.
const decodedPath = uri2path(urlDecode(msg));
require('electron').shell.openPath(decodedPath);
} else { } else {
require('electron').shell.openExternal(msg); require('electron').shell.openExternal(msg);
} }

View File

@@ -13,7 +13,7 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
const { MarkupToHtml } = require('@joplin/renderer'); const { MarkupToHtml } = require('@joplin/renderer');
const time = require('@joplin/lib/time').default; const time = require('@joplin/lib/time').default;
const ReactTooltip = require('react-tooltip'); const ReactTooltip = require('react-tooltip');
const { urlDecode, substrWithEllipsis } = require('@joplin/lib/string-utils'); const { urlDecode } = require('@joplin/lib/string-utils');
const bridge = require('electron').remote.require('./bridge').default; const bridge = require('electron').remote.require('./bridge').default;
const markupLanguageUtils = require('../utils/markupLanguageUtils').default; const markupLanguageUtils = require('../utils/markupLanguageUtils').default;
@@ -75,7 +75,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
this.setState({ restoring: true }); this.setState({ restoring: true });
await RevisionService.instance().importRevisionNote(this.state.note); await RevisionService.instance().importRevisionNote(this.state.note);
this.setState({ restoring: false }); this.setState({ restoring: false });
alert(_('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(this.state.note.title, 0, 32), RevisionService.instance().restoreFolderTitle())); alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
} }
backButton_click() { backButton_click() {

View File

@@ -95,7 +95,7 @@ export function ShareNoteDialog(props: Props) {
const copyLinksToClipboard = (shares: StateShare[]) => { const copyLinksToClipboard = (shares: StateShare[]) => {
const links = []; const links = [];
for (const share of shares) links.push(ShareService.instance().shareUrl(share)); for (const share of shares) links.push(ShareService.instance().shareUrl(ShareService.instance().userId, share));
clipboard.writeText(links.join('\n')); clipboard.writeText(links.join('\n'));
}; };

View File

@@ -449,7 +449,11 @@ class SidebarComponent extends React.Component<Props, State> {
renderTag(tag: any, selected: boolean) { renderTag(tag: any, selected: boolean) {
const anchorRef = this.anchorItemRef('tag', tag.id); const anchorRef = this.anchorItemRef('tag', tag.id);
const noteCount = Setting.value('showNoteCounts') ? this.renderNoteCount(tag.note_count) : ''; let noteCount = null;
if (Setting.value('showNoteCounts')) {
if (Setting.value('showCompletedTodos')) noteCount = this.renderNoteCount(tag.note_count);
else noteCount = this.renderNoteCount(tag.note_count - tag.todo_completed_count);
}
return ( return (
<StyledListItem selected={selected} <StyledListItem selected={selected}

View File

@@ -147,7 +147,10 @@ function StatusScreen(props: Props) {
} }
if (section.canRetryAll) { if (section.canRetryAll) {
itemsHtml.push(renderSectionRetryAllHtml(section.title, section.retryAllHandler)); itemsHtml.push(renderSectionRetryAllHtml(section.title, async () => {
await section.retryAllHandler();
void resfreshScreen();
}));
} }
return <div key={key}>{itemsHtml}</div>; return <div key={key}>{itemsHtml}</div>;

View File

@@ -1,6 +1,5 @@
import { utils as pluginUtils, PluginStates } from '@joplin/lib/services/plugins/reducer'; import { utils as pluginUtils, PluginStates } from '@joplin/lib/services/plugins/reducer';
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
import eventManager from '@joplin/lib/eventManager'; import eventManager from '@joplin/lib/eventManager';
import InteropService from '@joplin/lib/services/interop/InteropService'; import InteropService from '@joplin/lib/services/interop/InteropService';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils'; import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
@@ -134,7 +133,7 @@ export default class NoteListUtils {
}) })
); );
if (Setting.value('sync.target') === SyncTargetJoplinServer.id()) { if ([9, 10].includes(Setting.value('sync.target'))) {
menu.append( menu.append(
new MenuItem( new MenuItem(
menuUtils.commandToStatefulMenuItem('showShareNoteDialog', noteIds.slice()) menuUtils.commandToStatefulMenuItem('showShareNoteDialog', noteIds.slice())

View File

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

View File

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

View File

@@ -47,6 +47,12 @@ interface State {
listType: number; listType: number;
showHelp: boolean; showHelp: boolean;
resultsInBody: boolean; resultsInBody: boolean;
commandArgs: string[];
}
interface CommandQuery {
name: string;
args: string[];
} }
class GotoAnything { class GotoAnything {
@@ -87,6 +93,7 @@ class Dialog extends React.PureComponent<Props, State> {
listType: BaseModel.TYPE_NOTE, listType: BaseModel.TYPE_NOTE,
showHelp: false, showHelp: false,
resultsInBody: false, resultsInBody: false,
commandArgs: [],
}; };
this.styles_ = {}; this.styles_ = {};
@@ -250,6 +257,15 @@ class Dialog extends React.PureComponent<Props, State> {
return this.markupToHtml_; return this.markupToHtml_;
} }
private parseCommandQuery(query: string): CommandQuery {
const fullQuery = query;
const splitted = fullQuery.split(/\s+/);
return {
name: splitted.length ? splitted[0] : '',
args: splitted.slice(1),
};
}
async updateList() { async updateList() {
let resultsInBody = false; let resultsInBody = false;
@@ -260,13 +276,16 @@ class Dialog extends React.PureComponent<Props, State> {
let listType = null; let listType = null;
let searchQuery = ''; let searchQuery = '';
let keywords = null; let keywords = null;
let commandArgs: string[] = [];
if (this.state.query.indexOf(':') === 0) { // COMMANDS if (this.state.query.indexOf(':') === 0) { // COMMANDS
const query = this.state.query.substr(1); const commandQuery = this.parseCommandQuery(this.state.query.substr(1));
listType = BaseModel.TYPE_COMMAND;
keywords = [query];
const commandResults = CommandService.instance().searchCommands(query, true); listType = BaseModel.TYPE_COMMAND;
keywords = [commandQuery.name];
commandArgs = commandQuery.args;
const commandResults = CommandService.instance().searchCommands(commandQuery.name, true);
results = commandResults.map((result: CommandSearchResult) => { results = commandResults.map((result: CommandSearchResult) => {
return { return {
@@ -367,6 +386,7 @@ class Dialog extends React.PureComponent<Props, State> {
keywords: keywords ? keywords : await this.keywords(searchQuery), keywords: keywords ? keywords : await this.keywords(searchQuery),
selectedItemId: results.length === 0 ? null : results[0].id, selectedItemId: results.length === 0 ? null : results[0].id,
resultsInBody: resultsInBody, resultsInBody: resultsInBody,
commandArgs: commandArgs,
}); });
} }
} }
@@ -379,7 +399,7 @@ class Dialog extends React.PureComponent<Props, State> {
}); });
if (item.type === BaseModel.TYPE_COMMAND) { if (item.type === BaseModel.TYPE_COMMAND) {
void CommandService.instance().execute(item.id); void CommandService.instance().execute(item.id, ...item.commandArgs);
void focusEditorIfEditorCommand(item.id, CommandService.instance()); void focusEditorIfEditorCommand(item.id, CommandService.instance());
return; return;
} }
@@ -426,6 +446,7 @@ class Dialog extends React.PureComponent<Props, State> {
id: itemId, id: itemId,
parent_id: parentId, parent_id: parentId,
type: itemType, type: itemType,
commandArgs: this.state.commandArgs,
}); });
} }
@@ -466,7 +487,7 @@ class Dialog extends React.PureComponent<Props, State> {
selectedItem() { selectedItem() {
const index = this.selectedItemIndex(); const index = this.selectedItemIndex();
if (index < 0) return null; if (index < 0) return null;
return this.state.results[index]; return { ...this.state.results[index], commandArgs: this.state.commandArgs };
} }
input_onKeyDown(event: any) { input_onKeyDown(event: any) {

View File

@@ -1,48 +0,0 @@
#!/bin/bash
# Setup the sync parameters for user X and create a few folders and notes to
# allow sharing. Also calls the API to create the test users and clear the data.
set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
if [ "$1" == "" ]; then
echo "User number is required"
exit 1
fi
USER_NUM=$1
RESET_ALL=$2
PROFILE_DIR=~/.config/joplindev-desktop-$USER_NUM
if [ "$RESET_ALL" == "1" ]; then
CMD_FILE="$SCRIPT_DIR/runForSharingCommands-$USER_NUM.txt"
rm -f "$CMD_FILE"
USER_EMAIL="user$USER_NUM@example.com"
rm -rf "$PROFILE_DIR"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 9" >> "$CMD_FILE"
echo "config sync.9.path http://api-joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.9.password 123456" >> "$CMD_FILE"
if [ "$1" == "1" ]; then
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api-joplincloud.local:22300/api/debug
echo 'mkbook "shared"' >> "$CMD_FILE"
echo 'mkbook "other"' >> "$CMD_FILE"
echo 'use "shared"' >> "$CMD_FILE"
echo 'mknote "note 1"' >> "$CMD_FILE"
echo 'mknote "note 2"' >> "$CMD_FILE"
fi
cd "$ROOT_DIR/packages/app-cli"
npm start -- --profile "$PROFILE_DIR" batch "$CMD_FILE"
fi
cd "$ROOT_DIR/packages/app-desktop"
npm start -- --profile "$PROFILE_DIR"

View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Setup the sync parameters for user X and create a few folders and notes to
# allow sharing. Also calls the API to create the test users and clear the data.
set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
if [ "$1" == "" ]; then
echo "User number is required"
exit 1
fi
USER_NUM=$1
COMMANDS=($(echo $2 | tr "," "\n"))
PROFILE_DIR=~/.config/joplindev-desktop-$USER_NUM
CMD_FILE="$SCRIPT_DIR/runForSharingCommands-$USER_NUM.txt"
rm -f "$CMD_FILE"
touch "$CMD_FILE"
for CMD in "${COMMANDS[@]}"
do
if [[ $CMD == "createUsers" ]]; then
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
elif [[ $CMD == "createData" ]]; then
echo 'mkbook "shared"' >> "$CMD_FILE"
echo 'mkbook "other"' >> "$CMD_FILE"
echo 'use "shared"' >> "$CMD_FILE"
echo 'mknote "note 1"' >> "$CMD_FILE"
echo 'mknote "note 2"' >> "$CMD_FILE"
elif [[ $CMD == "reset" ]]; then
USER_EMAIL="user$USER_NUM@example.com"
rm -rf "$PROFILE_DIR"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 10" >> "$CMD_FILE"
# echo "config sync.10.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.10.password 123456" >> "$CMD_FILE"
elif [[ $CMD == "e2ee" ]]; then
echo "e2ee enable --password 111111" >> "$CMD_FILE"
else
echo "Unknown command: $CMD"
exit 1
fi
done
cd "$ROOT_DIR/packages/app-cli"
npm start -- --profile "$PROFILE_DIR" batch "$CMD_FILE"
if [[ $COMMANDS != "" ]]; then
exit 0
fi
cd "$ROOT_DIR/packages/app-desktop"
npm start -- --profile "$PROFILE_DIR"

View File

@@ -27,7 +27,7 @@ module.exports = async function() {
// Use stdio: 'pipe' so that execSync doesn't print error directly to stdout // Use stdio: 'pipe' so that execSync doesn't print error directly to stdout
branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim(); branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
hash = execSync('git log --pretty="%h" -1', { stdio: 'pipe' }).toString().trim(); hash = execSync('git log --pretty="%h" -1', { stdio: 'pipe' }).toString().trim();
// The builds in CI are done from a 'detached HEAD' state, thus the branch name will be 'HEAD' for Travis builds. // The builds in CI are done from a 'detached HEAD' state, thus the branch name will be 'HEAD' for CI builds.
} catch (err) { } catch (err) {
// Don't display error object as it's a "fatal" error, but // Don't display error object as it's a "fatal" error, but
// not for us, since is it not critical information // not for us, since is it not critical information

View File

@@ -20,13 +20,18 @@ function execCommand(command) {
}); });
} }
function isDesktopAppTag(tagName) {
if (!tagName) return false;
return tagName[0] === 'v';
}
module.exports = async function(params) { module.exports = async function(params) {
if (process.platform !== 'darwin') return; if (process.platform !== 'darwin') return;
console.info('Checking if notarization should be done...'); console.info('Checking if notarization should be done...');
if (!process.env.TRAVIS || !process.env.TRAVIS_TAG) { if (!process.env.IS_CONTINUOUS_INTEGRATION || !isDesktopAppTag(process.env.GIT_TAG_NAME)) {
console.info(`Either not running in CI or not processing a tag - skipping notarization. process.env.TRAVIS = ${process.env.TRAVIS}; process.env.TRAVIS_TAG = ${process.env.TRAVIS}`); console.info(`Either not running in CI or not processing a desktop app tag - skipping notarization. process.env.IS_CONTINUOUS_INTEGRATION = ${process.env.IS_CONTINUOUS_INTEGRATION}; process.env.GIT_TAG_NAME = ${process.env.GIT_TAG_NAME}`);
return; return;
} }
@@ -45,9 +50,8 @@ module.exports = async function(params) {
console.log(`Notarizing ${appId} found at ${appPath}`); console.log(`Notarizing ${appId} found at ${appPath}`);
// Every x seconds we print something to stdout, otherwise Travis will // Every x seconds we print something to stdout, otherwise CI may timeout
// timeout the task after 10 minutes, and Apple notarization can take more // the task after 10 minutes, and Apple notarization can take more time.
// time.
const waitingIntervalId = setInterval(() => { const waitingIntervalId = setInterval(() => {
console.log('.'); console.log('.');
}, 60000); }, 60000);

View File

@@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin" applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097632 versionCode 2097634
versionName "2.0.1" versionName "2.0.3"
ndk { ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
} }

View File

@@ -16,6 +16,7 @@ import com.facebook.soloader.SoLoader;
import net.cozic.joplin.share.SharePackage; import net.cozic.joplin.share.SharePackage;
import net.cozic.joplin.ssl.SslPackage; import net.cozic.joplin.ssl.SslPackage;
import net.cozic.joplin.textinput.TextInputPackage;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -44,6 +45,7 @@ public class MainApplication extends Application implements ReactApplication {
// Packages that cannot be autolinked yet can be added manually here, for example: // Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new SharePackage()); packages.add(new SharePackage());
packages.add(new SslPackage()); packages.add(new SslPackage());
packages.add(new TextInputPackage());
return packages; return packages;
} }

View File

@@ -0,0 +1,63 @@
package net.cozic.joplin.textinput;
import android.text.Selection;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.textinput.ReactEditText;
import com.facebook.react.views.textinput.ReactTextInputManager;
import java.util.Collections;
import java.util.List;
/**
* This class provides a workaround for <a href="https://github.com/facebook/react-native/issues/29911">
* https://github.com/facebook/react-native/issues/29911</a>
*
* The reason the editor is scrolled seems to be due to this block in
* <pre>android.widget.Editor#onFocusChanged:</pre>
*
* <pre>
* // The DecorView does not have focus when the 'Done' ExtractEditText button is
* // pressed. Since it is the ViewAncestor's mView, it requests focus before
* // ExtractEditText clears focus, which gives focus to the ExtractEditText.
* // This special case ensure that we keep current selection in that case.
* // It would be better to know why the DecorView does not have focus at that time.
* if (((mTextView.isInExtractedMode()) || mSelectionMoved)
* && selStart >= 0 && selEnd >= 0) {
* Selection.setSelection((Spannable)mTextView.getText(),selStart,selEnd);
* }
* </pre>
* When using native Android TextView mSelectionMoved is false so this block is skipped,
* with RN however it's true and this is where the scrolling comes from.
*
* The below workaround resets the selection before a focus event is passed on to the native component.
* This way when the above condition is reached <pre>selStart == selEnd == -1</pre> and no scrolling
* happens.
*/
public class TextInputPackage implements com.facebook.react.ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.singletonList(new ReactTextInputManager() {
@Override
public void receiveCommand(ReactEditText reactEditText, String commandId, @Nullable ReadableArray args) {
if ("focus".equals(commandId) || "focusTextInput".equals(commandId)) {
Selection.removeSelection(reactEditText.getText());
}
super.receiveCommand(reactEditText, commandId, args);
}
});
}
}

View File

@@ -105,7 +105,7 @@ class SearchScreenComponent extends BaseScreenComponent {
if (query) { if (query) {
if (this.props.settings['db.ftsEnabled']) { if (this.props.settings['db.ftsEnabled']) {
notes = await SearchEngineUtils.notesForQuery(query); notes = await SearchEngineUtils.notesForQuery(query, true);
} else { } else {
const p = query.split(' '); const p = query.split(' ');
const temp = []; const temp = [];

View File

@@ -486,13 +486,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 65; CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = A9BXAFS6CT; DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist; INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 20.0.0; MARKETING_VERSION = 12.0.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@@ -514,12 +514,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 65; CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = A9BXAFS6CT; DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist; INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 20.0.0; MARKETING_VERSION = 12.0.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@@ -659,14 +659,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 65; CURRENT_PROJECT_VERSION = 68;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT; DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 20.0.0; MARKETING_VERSION = 12.0.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -690,14 +690,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 65; CURRENT_PROJECT_VERSION = 68;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT; DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 20.0.0; MARKETING_VERSION = 12.0.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -25,6 +25,7 @@ import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtil
import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile'; import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile';
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale'; import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer'; import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform } = require('react-native'); const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform } = require('react-native');
@@ -90,6 +91,7 @@ SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetFilesystem); SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetAmazonS3); SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer); SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
import FsDriverRN from './utils/fs-driver-rn'; import FsDriverRN from './utils/fs-driver-rn';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker'; import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';

View File

@@ -1,6 +1,6 @@
{ {
"name": "@joplin/fork-htmlparser2", "name": "@joplin/fork-htmlparser2",
"version": "4.1.24", "version": "4.1.26",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,7 +1,7 @@
{ {
"name": "@joplin/fork-htmlparser2", "name": "@joplin/fork-htmlparser2",
"description": "Fast & forgiving HTML/XML/RSS parser", "description": "Fast & forgiving HTML/XML/RSS parser",
"version": "4.1.24", "version": "4.1.26",
"author": "Felix Boehm <me@feedic.com>", "author": "Felix Boehm <me@feedic.com>",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@joplin/fork-sax", "name": "@joplin/fork-sax",
"version": "1.2.28", "version": "1.2.30",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -143,6 +143,12 @@
} }
} }
}, },
"balanced-match": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz",
"integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=",
"dev": true
},
"bcrypt-pbkdf": { "bcrypt-pbkdf": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -175,9 +181,11 @@
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
"integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=",
"dev": true, "dev": true,
"requires": { "requires": {
"balanced-match": "^0.3.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },

View File

@@ -2,7 +2,7 @@
"name": "@joplin/fork-sax", "name": "@joplin/fork-sax",
"description": "An evented streaming XML parser in JavaScript", "description": "An evented streaming XML parser in JavaScript",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)", "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"version": "1.2.28", "version": "1.2.30",
"main": "lib/sax.js", "main": "lib/sax.js",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@@ -135,7 +135,9 @@ const pluginConfig = Object.assign({}, baseConfig, {
alias: { alias: {
api: path.resolve(__dirname, 'api'), api: path.resolve(__dirname, 'api'),
}, },
extensions: ['.tsx', '.ts', '.js'], // JSON files can also be required from scripts so we include this.
// https://github.com/joplin/plugin-bibtex/pull/2
extensions: ['.tsx', '.ts', '.js', '.json'],
}, },
output: { output: {
filename: 'index.js', filename: 'index.js',
@@ -167,7 +169,7 @@ const extraScriptConfig = Object.assign({}, baseConfig, {
alias: { alias: {
api: path.resolve(__dirname, 'api'), api: path.resolve(__dirname, 'api'),
}, },
extensions: ['.tsx', '.ts', '.js'], extensions: ['.tsx', '.ts', '.js', '.json'],
}, },
}); });

View File

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

View File

@@ -1,4 +1,4 @@
import Setting from './models/Setting'; import Setting, { Env } from './models/Setting';
import Logger, { TargetType, LoggerWrapper } from './Logger'; import Logger, { TargetType, LoggerWrapper } from './Logger';
import shim from './shim'; import shim from './shim';
import BaseService from './services/BaseService'; import BaseService from './services/BaseService';
@@ -46,6 +46,7 @@ const { loadKeychainServiceAndSettings } = require('./services/SettingUtils');
import MigrationService from './services/MigrationService'; import MigrationService from './services/MigrationService';
import ShareService from './services/share/ShareService'; import ShareService from './services/share/ShareService';
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation'; import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
const { toSystemSlashes } = require('./path-utils'); const { toSystemSlashes } = require('./path-utils');
const { setAutoFreeze } = require('immer'); const { setAutoFreeze } = require('immer');
@@ -312,7 +313,7 @@ export default class BaseApplication {
notes = await Tag.notes(parentId, options); notes = await Tag.notes(parentId, options);
} else if (parentType === BaseModel.TYPE_SEARCH) { } else if (parentType === BaseModel.TYPE_SEARCH) {
const search = BaseModel.byId(state.searches, parentId); const search = BaseModel.byId(state.searches, parentId);
notes = await SearchEngineUtils.notesForQuery(search.query_pattern); notes = await SearchEngineUtils.notesForQuery(search.query_pattern, true);
const parsedQuery = await SearchEngine.instance().parseQuery(search.query_pattern); const parsedQuery = await SearchEngine.instance().parseQuery(search.query_pattern);
highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery); highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
} else if (parentType === BaseModel.TYPE_SMART_FILTER) { } else if (parentType === BaseModel.TYPE_SMART_FILTER) {
@@ -691,6 +692,7 @@ export default class BaseApplication {
SyncTargetRegistry.addClass(SyncTargetDropbox); SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3); SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer); SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
try { try {
await shim.fsDriver().remove(tempDir); await shim.fsDriver().remove(tempDir);
@@ -763,6 +765,13 @@ export default class BaseApplication {
setLocale(Setting.value('locale')); setLocale(Setting.value('locale'));
} }
if (Setting.value('env') === Env.Dev) {
// Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
// Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');
Setting.setValue('sync.10.path', 'http://api.joplincloud.local:22300');
Setting.setValue('sync.10.userContentPath', 'http://joplinusercontent.local:22300');
}
// For now always disable fuzzy search due to performance issues: // For now always disable fuzzy search due to performance issues:
// https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11 // https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11
// https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23 // https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23

View File

@@ -343,7 +343,7 @@ export default class JoplinDatabase extends Database {
// must be set in the synchronizer too. // must be set in the synchronizer too.
// Note: v16 and v17 don't do anything. They were used to debug an issue. // Note: v16 and v17 don't do anything. They were used to debug an issue.
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37]; const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39];
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion); let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
@@ -876,6 +876,22 @@ export default class JoplinDatabase extends Database {
queries.push('ALTER TABLE resources ADD COLUMN share_id TEXT NOT NULL DEFAULT ""'); queries.push('ALTER TABLE resources ADD COLUMN share_id TEXT NOT NULL DEFAULT ""');
} }
if (targetVersion == 38) {
queries.push('DROP VIEW tags_with_note_count');
queries.push(`CREATE VIEW tags_with_note_count AS
SELECT tags.id as id, tags.title as title, tags.created_time as created_time, tags.updated_time as updated_time, COUNT(notes.id) as note_count,
SUM(CASE WHEN notes.todo_completed > 0 THEN 1 ELSE 0 END) AS todo_completed_count
FROM tags
LEFT JOIN note_tags nt on nt.tag_id = tags.id
LEFT JOIN notes on notes.id = nt.note_id
WHERE notes.id IS NOT NULL
GROUP BY tags.id`);
}
if (targetVersion == 39) {
queries.push('ALTER TABLE `notes` ADD COLUMN conflict_original_id TEXT NOT NULL DEFAULT ""');
}
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] }; const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
queries.push(updateVersionQuery); queries.push(updateVersionQuery);

View File

@@ -1,13 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class JoplinError extends Error {
constructor(message, code = null, details = null) {
super(message);
this.code = null;
this.details = '';
this.code = code;
this.details = details;
}
}
exports.default = JoplinError;
//# sourceMappingURL=JoplinError.js.map

View File

@@ -4,12 +4,14 @@ const { rtrimSlashes } = require('./path-utils.js');
import JoplinError from './JoplinError'; import JoplinError from './JoplinError';
import { Env } from './models/Setting'; import { Env } from './models/Setting';
import Logger from './Logger'; import Logger from './Logger';
import personalizedUserContentBaseUrl from './services/joplinServer/personalizedUserContentBaseUrl';
const { stringify } = require('query-string'); const { stringify } = require('query-string');
const logger = Logger.create('JoplinServerApi'); const logger = Logger.create('JoplinServerApi');
interface Options { interface Options {
baseUrl(): string; baseUrl(): string;
userContentBaseUrl(): string;
username(): string; username(): string;
password(): string; password(): string;
env?: Env; env?: Env;
@@ -47,7 +49,7 @@ export default class JoplinServerApi {
this.options_ = options; this.options_ = options;
if (options.env === Env.Dev) { if (options.env === Env.Dev) {
this.debugRequests_ = true; // this.debugRequests_ = true;
} }
} }
@@ -55,15 +57,24 @@ export default class JoplinServerApi {
return rtrimSlashes(this.options_.baseUrl()); return rtrimSlashes(this.options_.baseUrl());
} }
public personalizedUserContentBaseUrl(userId: string) {
return personalizedUserContentBaseUrl(userId, this.baseUrl(), this.options_.userContentBaseUrl());
}
private async session() { private async session() {
if (this.session_) return this.session_; if (this.session_) return this.session_;
this.session_ = await this.exec('POST', 'api/sessions', null, { try {
email: this.options_.username(), this.session_ = await this.exec('POST', 'api/sessions', null, {
password: this.options_.password(), email: this.options_.username(),
}); password: this.options_.password(),
});
return this.session_; return this.session_;
} catch (error) {
logger.error('Could not acquire session:', error.details, '\n', error);
throw error;
}
} }
private async sessionId() { private async sessionId() {
@@ -77,7 +88,7 @@ export default class JoplinServerApi {
public static connectionErrorMessage(error: any) { public static connectionErrorMessage(error: any) {
const msg = error && error.message ? error.message : 'Unknown error'; const msg = error && error.message ? error.message : 'Unknown error';
return _('Could not connect to Joplin Cloud. Please check the Synchronisation options in the config screen. Full error was:\n\n%s', msg); return _('Could not connect to Joplin Server. Please check the Synchronisation options in the config screen. Full error was:\n\n%s', msg);
} }
private requestToCurl_(url: string, options: any) { private requestToCurl_(url: string, options: any) {
@@ -136,6 +147,8 @@ export default class JoplinServerApi {
url += stringify(query); url += stringify(query);
} }
const startTime = Date.now();
try { try {
if (this.debugRequests_) { if (this.debugRequests_) {
logger.debug(this.requestToCurl_(url, fetchOptions)); logger.debug(this.requestToCurl_(url, fetchOptions));
@@ -160,16 +173,19 @@ export default class JoplinServerApi {
const responseText = await response.text(); const responseText = await response.text();
if (this.debugRequests_) { if (this.debugRequests_) {
logger.debug('Response', responseText); logger.debug('Response', Date.now() - startTime, options.responseFormat, responseText);
} }
const shortResponseText = () => {
return (`${responseText}`).substr(0, 1024);
};
// Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier // 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) => { 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 // 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. // 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); // return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code);
return new JoplinError(message, code, `${method} ${path}: ${message} (${code}): ${shortResponseText}`); return new JoplinError(message, code, `${method} ${path}: ${message} (${code}): ${shortResponseText()}`);
}; };
let responseJson_: any = null; let responseJson_: any = null;
@@ -195,7 +211,21 @@ export default class JoplinServerApi {
throw newError(`${json.error}`, json.code ? json.code : response.status); throw newError(`${json.error}`, json.code ? json.code : response.status);
} }
throw newError('Unknown error', response.status); // "Unknown error" means it probably wasn't generated by the
// application but for example by the Nginx or Apache reverse
// proxy. So in that case we attach the response content to the
// error message so that it shows up in logs. It might be for
// example an error returned by the Nginx or Apache reverse
// proxy. For example:
//
// <html>
// <head><title>413 Request Entity Too Large</title></head>
// <body>
// <center><h1>413 Request Entity Too Large</h1></center>
// <hr><center>nginx/1.18.0 (Ubuntu)</center>
// </body>
// </html>
throw newError(`Unknown error: ${shortResponseText()}`, response.status);
} }
if (options.responseFormat === 'text') return responseText; if (options.responseFormat === 'text') return responseText;

View File

@@ -0,0 +1,59 @@
import Setting from './models/Setting';
import Synchronizer from './Synchronizer';
import { _ } from './locale.js';
import BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api';
import SyncTargetJoplinServer, { initFileApi } from './SyncTargetJoplinServer';
interface FileApiOptions {
path(): string;
userContentPath(): string;
username(): string;
password(): string;
}
export default class SyncTargetJoplinCloud extends BaseSyncTarget {
public static id() {
return 10;
}
public static supportsConfigCheck() {
return SyncTargetJoplinServer.supportsConfigCheck();
}
public static targetName() {
return 'joplinCloud';
}
public static label() {
return _('Joplin Cloud');
}
public async isAuthenticated() {
return true;
}
public async fileApi(): Promise<FileApi> {
return super.fileApi();
}
public static async checkConfig(options: FileApiOptions) {
return SyncTargetJoplinServer.checkConfig({
...options,
}, SyncTargetJoplinCloud.id());
}
protected async initFileApi() {
return initFileApi(SyncTargetJoplinCloud.id(), this.logger(), {
path: () => Setting.value('sync.10.path'),
userContentPath: () => Setting.value('sync.10.userContentPath'),
username: () => Setting.value('sync.10.username'),
password: () => Setting.value('sync.10.password'),
});
}
protected async initSynchronizer() {
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
}
}

View File

@@ -5,13 +5,38 @@ import { _ } from './locale.js';
import JoplinServerApi from './JoplinServerApi'; import JoplinServerApi from './JoplinServerApi';
import BaseSyncTarget from './BaseSyncTarget'; import BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api'; import { FileApi } from './file-api';
import Logger from './Logger';
interface FileApiOptions { interface FileApiOptions {
path(): string; path(): string;
userContentPath(): string;
username(): string; username(): string;
password(): string; password(): string;
} }
export async function newFileApi(id: number, options: FileApiOptions) {
const apiOptions = {
baseUrl: () => options.path(),
userContentBaseUrl: () => options.userContentPath(),
username: () => options.username(),
password: () => options.password(),
env: Setting.value('env'),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(id);
await fileApi.initialize();
return fileApi;
}
export async function initFileApi(syncTargetId: number, logger: Logger, options: FileApiOptions) {
const fileApi = await newFileApi(syncTargetId, options);
fileApi.setLogger(logger);
return fileApi;
}
export default class SyncTargetJoplinServer extends BaseSyncTarget { export default class SyncTargetJoplinServer extends BaseSyncTarget {
public static id() { public static id() {
@@ -27,7 +52,7 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
} }
public static label() { public static label() {
return `${_('Joplin Cloud')} (Beta)`; return `${_('Joplin Server')} (Beta)`;
} }
public async isAuthenticated() { public async isAuthenticated() {
@@ -38,30 +63,16 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
return super.fileApi(); return super.fileApi();
} }
private static async newFileApi_(options: FileApiOptions) { public static async checkConfig(options: FileApiOptions, syncTargetId: number = null) {
const apiOptions = {
baseUrl: () => options.path(),
username: () => options.username(),
password: () => options.password(),
env: Setting.value('env'),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(this.id());
await fileApi.initialize();
return fileApi;
}
public static async checkConfig(options: FileApiOptions) {
const output = { const output = {
ok: false, ok: false,
errorMessage: '', errorMessage: '',
}; };
syncTargetId = syncTargetId === null ? SyncTargetJoplinServer.id() : syncTargetId;
try { try {
const fileApi = await SyncTargetJoplinServer.newFileApi_(options); const fileApi = await newFileApi(syncTargetId, options);
fileApi.requestRepeatCount_ = 0; fileApi.requestRepeatCount_ = 0;
await fileApi.put('testing.txt', 'testing'); await fileApi.put('testing.txt', 'testing');
@@ -78,15 +89,12 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
} }
protected async initFileApi() { protected async initFileApi() {
const fileApi = await SyncTargetJoplinServer.newFileApi_({ return initFileApi(SyncTargetJoplinServer.id(), this.logger(), {
path: () => Setting.value('sync.9.path'), path: () => Setting.value('sync.9.path'),
userContentPath: () => Setting.value('sync.9.userContentPath'),
username: () => Setting.value('sync.9.username'), username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'), password: () => Setting.value('sync.9.password'),
}); });
fileApi.setLogger(this.logger());
return fileApi;
} }
protected async initSynchronizer() { protected async initSynchronizer() {

View File

@@ -24,7 +24,7 @@ class SyncTargetRegistry {
if (!this.reg_.hasOwnProperty(n)) continue; if (!this.reg_.hasOwnProperty(n)) continue;
if (this.reg_[n].name === name) return this.reg_[n].id; if (this.reg_[n].name === name) return this.reg_[n].id;
} }
throw new Error(`Name not found: ${name}`); throw new Error(`Name not found: ${name}. Was the sync target registered?`);
} }
static idToMetadata(id) { static idToMetadata(id) {

View File

@@ -46,6 +46,8 @@ function isCannotSyncError(error: any): boolean {
export default class Synchronizer { export default class Synchronizer {
public static verboseMode: boolean = true;
private db_: any; private db_: any;
private api_: any; private api_: any;
private appType_: string; private appType_: string;
@@ -195,7 +197,11 @@ export default class Synchronizer {
line.push(`(Remote ${s.join(', ')})`); line.push(`(Remote ${s.join(', ')})`);
} }
this.logger().debug(line.join(': ')); if (Synchronizer.verboseMode) {
this.logger().info(line.join(': '));
} else {
this.logger().debug(line.join(': '));
}
if (!this.progressReport_[action]) this.progressReport_[action] = 0; if (!this.progressReport_[action]) this.progressReport_[action] = 0;
this.progressReport_[action] += actionCount; this.progressReport_[action] += actionCount;
@@ -610,10 +616,7 @@ export default class Synchronizer {
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
if (mustHandleConflict) { if (mustHandleConflict) {
const conflictedNote = Object.assign({}, local); await Note.createConflictNote(local, ItemChange.SOURCE_SYNC);
delete conflictedNote.id;
conflictedNote.is_conflict = 1;
await Note.save(conflictedNote, { autoTimestamp: false, changeSource: ItemChange.SOURCE_SYNC });
} }
} else if (action == 'resourceConflict') { } else if (action == 'resourceConflict') {
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
@@ -926,6 +929,7 @@ export default class Synchronizer {
this.logger().error(error); this.logger().error(error);
} else { } else {
this.logger().error(error); this.logger().error(error);
if (error.details) this.logger().error('Details:', error.details);
// Don't save to the report errors that are due to things like temporary network errors or timeout. // Don't save to the report errors that are due to things like temporary network errors or timeout.
if (!shim.fetchRequestCanBeRetried(error)) { if (!shim.fetchRequestCanBeRetried(error)) {

View File

@@ -80,7 +80,19 @@ export default class FileApiDriverJoplinServer {
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query); const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query);
const stats = response.items const stats = response.items
.filter((item: any) => { .filter((item: any) => {
return item.item_name.indexOf('locks/') !== 0 && item.item_name.indexOf('temp/') !== 0; // We don't need to know about lock changes, since this
// is handled by the LockHandler.
if (item.item_name.indexOf('locks/') === 0) return false;
// We don't need to sync what's in the temp folder
if (item.item_name.indexOf('temp/') === 0) return false;
// Although we sync the content of .resource, whether we
// fetch or upload data to it is driven by the
// associated resource item (.md) file. So at this point
// we don't want to automatically fetch from it.
if (item.item_name.indexOf('.resource/') === 0) return false;
return true;
}) })
.map((item: any) => { .map((item: any) => {
return this.metadataToStat_(item, item.item_name, item.type === 3, ''); return this.metadataToStat_(item, item.item_name, item.type === 3, '');
@@ -171,6 +183,12 @@ export default class FileApiDriverJoplinServer {
} }
public async clearRoot(path: string) { public async clearRoot(path: string) {
await this.delete(path); const response = await this.list(path);
for (const item of response.items) {
await this.delete(item.path);
}
if (response.has_more) throw new Error('has_more support not implemented');
} }
} }

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

View File

@@ -41,45 +41,45 @@ locales['uk_UA'] = require('./uk_UA.json');
locales['vi'] = require('./vi.json'); locales['vi'] = require('./vi.json');
locales['zh_CN'] = require('./zh_CN.json'); locales['zh_CN'] = require('./zh_CN.json');
locales['zh_TW'] = require('./zh_TW.json'); locales['zh_TW'] = require('./zh_TW.json');
stats['ar'] = {"percentDone":96}; stats['ar'] = {"percentDone":95};
stats['eu'] = {"percentDone":30}; stats['eu'] = {"percentDone":30};
stats['bs_BA'] = {"percentDone":75}; stats['bs_BA'] = {"percentDone":74};
stats['bg_BG'] = {"percentDone":58}; stats['bg_BG'] = {"percentDone":57};
stats['ca'] = {"percentDone":83}; stats['ca'] = {"percentDone":82};
stats['hr_HR'] = {"percentDone":96}; stats['hr_HR'] = {"percentDone":100};
stats['cs_CZ'] = {"percentDone":86}; stats['cs_CZ'] = {"percentDone":100};
stats['da_DK'] = {"percentDone":96}; stats['da_DK'] = {"percentDone":99};
stats['de_DE'] = {"percentDone":95}; stats['de_DE'] = {"percentDone":94};
stats['et_EE'] = {"percentDone":57}; stats['et_EE'] = {"percentDone":56};
stats['en_GB'] = {"percentDone":100}; stats['en_GB'] = {"percentDone":100};
stats['en_US'] = {"percentDone":100}; stats['en_US'] = {"percentDone":100};
stats['es_ES'] = {"percentDone":94}; stats['es_ES'] = {"percentDone":94};
stats['eo'] = {"percentDone":33}; stats['eo'] = {"percentDone":32};
stats['fi_FI'] = {"percentDone":94}; stats['fi_FI'] = {"percentDone":94};
stats['fr_FR'] = {"percentDone":99}; stats['fr_FR'] = {"percentDone":98};
stats['gl_ES'] = {"percentDone":38}; stats['gl_ES'] = {"percentDone":38};
stats['id_ID'] = {"percentDone":93}; stats['id_ID'] = {"percentDone":92};
stats['it_IT'] = {"percentDone":94}; stats['it_IT'] = {"percentDone":99};
stats['hu_HU'] = {"percentDone":88}; stats['hu_HU'] = {"percentDone":88};
stats['nl_BE'] = {"percentDone":92}; stats['nl_BE'] = {"percentDone":91};
stats['nl_NL'] = {"percentDone":95}; stats['nl_NL'] = {"percentDone":94};
stats['nb_NO'] = {"percentDone":76}; stats['nb_NO'] = {"percentDone":75};
stats['fa'] = {"percentDone":71}; stats['fa'] = {"percentDone":71};
stats['pl_PL'] = {"percentDone":94}; stats['pl_PL'] = {"percentDone":94};
stats['pt_BR'] = {"percentDone":94}; stats['pt_BR'] = {"percentDone":94};
stats['pt_PT'] = {"percentDone":94}; stats['pt_PT'] = {"percentDone":94};
stats['ro'] = {"percentDone":66}; stats['ro'] = {"percentDone":66};
stats['sl_SI'] = {"percentDone":96}; stats['sl_SI'] = {"percentDone":95};
stats['sv'] = {"percentDone":61}; stats['sv'] = {"percentDone":61};
stats['th_TH'] = {"percentDone":45}; stats['th_TH'] = {"percentDone":45};
stats['vi'] = {"percentDone":73}; stats['vi'] = {"percentDone":73};
stats['tr_TR'] = {"percentDone":94}; stats['tr_TR'] = {"percentDone":94};
stats['uk_UA'] = {"percentDone":94}; stats['uk_UA'] = {"percentDone":94};
stats['el_GR'] = {"percentDone":97}; stats['el_GR'] = {"percentDone":96};
stats['ru_RU'] = {"percentDone":94}; stats['ru_RU'] = {"percentDone":94};
stats['sr_RS'] = {"percentDone":71}; stats['sr_RS'] = {"percentDone":71};
stats['zh_CN'] = {"percentDone":94}; stats['zh_CN'] = {"percentDone":99};
stats['zh_TW'] = {"percentDone":92}; stats['zh_TW'] = {"percentDone":99};
stats['ja_JP'] = {"percentDone":97}; stats['ja_JP'] = {"percentDone":96};
stats['ko'] = {"percentDone":96}; stats['ko'] = {"percentDone":99};
module.exports = { locales: locales, stats: stats }; module.exports = { locales: locales, stats: stats };

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

View File

@@ -7,10 +7,18 @@ const MarkdownIt = require('markdown-it');
const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/; const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
const emptyListRegex = /^(\s*)([*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s+)$/; const emptyListRegex = /^(\s*)([*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s+)$/;
export enum MarkdownTableJustify {
Left = 'left',
Center = 'center',
Right = 'right,',
}
export interface MarkdownTableHeader { export interface MarkdownTableHeader {
name: string; name: string;
label: string; label: string;
filter?: Function; filter?: Function;
disableEscape?: boolean;
justify?: MarkdownTableJustify;
} }
export interface MarkdownTableRow { export interface MarkdownTableRow {
@@ -120,26 +128,38 @@ const markdownUtils = {
createMarkdownTable(headers: MarkdownTableHeader[], rows: MarkdownTableRow[]): string { createMarkdownTable(headers: MarkdownTableHeader[], rows: MarkdownTableRow[]): string {
const output = []; const output = [];
const minCellWidth = 5;
const headersMd = []; const headersMd = [];
const lineMd = []; const lineMd = [];
for (let i = 0; i < headers.length; i++) { for (let i = 0; i < headers.length; i++) {
const h = headers[i]; const h = headers[i];
headersMd.push(stringPadding(h.label, 3, ' ', stringPadding.RIGHT)); headersMd.push(stringPadding(h.label, minCellWidth, ' ', stringPadding.RIGHT));
lineMd.push('---');
const justify = h.justify ? h.justify : MarkdownTableJustify.Left;
if (justify === MarkdownTableJustify.Left) {
lineMd.push('-----');
} else if (justify === MarkdownTableJustify.Center) {
lineMd.push(':---:');
} else {
lineMd.push('----:');
}
} }
output.push(headersMd.join(' | ')); output.push(`| ${headersMd.join(' | ')} |`);
output.push(lineMd.join(' | ')); output.push(`| ${lineMd.join(' | ')} |`);
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
const row = rows[i]; const row = rows[i];
const rowMd = []; const rowMd = [];
for (let j = 0; j < headers.length; j++) { for (let j = 0; j < headers.length; j++) {
const h = headers[j]; const h = headers[j];
const valueMd = markdownUtils.escapeTableCell(h.filter ? h.filter(row[h.name]) : row[h.name]); const value = (h.filter ? h.filter(row[h.name]) : row[h.name]) || '';
rowMd.push(stringPadding(valueMd, 3, ' ', stringPadding.RIGHT)); const valueMd = h.disableEscape ? value : markdownUtils.escapeTableCell(value);
rowMd.push(stringPadding(valueMd, minCellWidth, ' ', stringPadding.RIGHT));
} }
output.push(rowMd.join(' | ')); output.push(`| ${rowMd.join(' | ')} |`);
} }
return output.join('\n'); return output.join('\n');

View File

@@ -1,26 +1,18 @@
import { ModelType } from '../BaseModel'; import { ModelType } from '../BaseModel';
import { NoteEntity } from '../services/database/types'; import { BaseItemEntity, NoteEntity } from '../services/database/types';
import Setting from './Setting'; import Setting from './Setting';
import BaseModel from '../BaseModel'; import BaseModel from '../BaseModel';
import time from '../time'; import time from '../time';
import markdownUtils from '../markdownUtils'; import markdownUtils from '../markdownUtils';
import { _ } from '../locale'; import { _ } from '../locale';
import Database from '../database'; import Database from '../database';
import ItemChange from './ItemChange'; import ItemChange from './ItemChange';
import ShareService from '../services/share/ShareService'; import ShareService from '../services/share/ShareService';
import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
const JoplinError = require('../JoplinError.js'); const JoplinError = require('../JoplinError.js');
const { sprintf } = require('sprintf-js'); const { sprintf } = require('sprintf-js');
const moment = require('moment'); const moment = require('moment');
export interface BaseItemEntity {
id?: string;
encryption_applied?: boolean;
is_shared?: number;
share_id?: string;
type_?: ModelType;
}
export interface ItemsThatNeedDecryptionResult { export interface ItemsThatNeedDecryptionResult {
hasMore: boolean; hasMore: boolean;
items: any[]; items: any[];
@@ -404,7 +396,7 @@ export default class BaseItem extends BaseModel {
const serialized = await ItemClass.serialize(item, shownKeys); const serialized = await ItemClass.serialize(item, shownKeys);
if (!Setting.value('encryption.enabled') || !ItemClass.encryptionSupported() || item.is_shared || item.share_id) { if (!Setting.value('encryption.enabled') || !ItemClass.encryptionSupported() || !itemCanBeEncrypted(item)) {
// Normally not possible since itemsThatNeedSync should only return decrypted items // Normally not possible since itemsThatNeedSync should only return decrypted items
if (item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted'); if (item.encryption_applied) throw new JoplinError('Item is encrypted but encryption is currently disabled', 'cannotSyncEncrypted');
return serialized; return serialized;

View File

@@ -6,6 +6,7 @@ import { sortedIds, createNTestNotes, setupDatabaseAndSynchronizer, switchClient
import Folder from './Folder'; import Folder from './Folder';
import Note from './Note'; import Note from './Note';
import Tag from './Tag'; import Tag from './Tag';
import ItemChange from './ItemChange';
const ArrayUtils = require('../ArrayUtils.js'); const ArrayUtils = require('../ArrayUtils.js');
async function allItems() { async function allItems() {
@@ -284,8 +285,20 @@ describe('models_Note', function() {
expect(externalToInternal).toBe(input); expect(externalToInternal).toBe(input);
} }
const result = await Note.replaceResourceExternalToInternalLinks(`[](joplin://${note1.id})`); {
expect(result).toBe(`[](:/${note1.id})`); const result = await Note.replaceResourceExternalToInternalLinks(`[](joplin://${note1.id})`);
expect(result).toBe(`[](:/${note1.id})`);
}
{
// This is a regular file path that contains the resourceDirName
// inside but it shouldn't be changed.
//
// https://github.com/laurent22/joplin/issues/5034
const noChangeInput = `[docs](file:///c:/foo/${resourceDirName}/docs)`;
const result = await Note.replaceResourceExternalToInternalLinks(noChangeInput, { useAbsolutePaths: false });
expect(result).toBe(noChangeInput);
}
})); }));
it('should perform natural sorting', (async () => { it('should perform natural sorting', (async () => {
@@ -332,4 +345,44 @@ describe('models_Note', function() {
expect(sortedNotes3[4].id).toBe(note2.id); expect(sortedNotes3[4].id).toBe(note2.id);
})); }));
it('should create a conflict note', async () => {
const folder = await Folder.save({ title: 'Source Folder' });
const origNote = await Note.save({ title: 'note', parent_id: folder.id });
const conflictedNote = await Note.createConflictNote(origNote, ItemChange.SOURCE_SYNC);
expect(conflictedNote.is_conflict).toBe(1);
expect(conflictedNote.conflict_original_id).toBe(origNote.id);
expect(conflictedNote.parent_id).toBe(folder.id);
});
it('should copy conflicted note to target folder and cancel conflict', (async () => {
const srcfolder = await Folder.save({ title: 'Source Folder' });
const targetfolder = await Folder.save({ title: 'Target Folder' });
const note1 = await Note.save({ title: 'note', parent_id: srcfolder.id });
const conflictedNote = await Note.createConflictNote(note1, ItemChange.SOURCE_SYNC);
const note2 = await Note.copyToFolder(conflictedNote.id, targetfolder.id);
expect(note2.id === conflictedNote.id).toBe(false);
expect(note2.title).toBe(conflictedNote.title);
expect(note2.is_conflict).toBe(0);
expect(note2.conflict_original_id).toBe('');
expect(note2.parent_id).toBe(targetfolder.id);
}));
it('should move conflicted note to target folder and cancel conflict', (async () => {
const srcFolder = await Folder.save({ title: 'Source Folder' });
const targetFolder = await Folder.save({ title: 'Target Folder' });
const note1 = await Note.save({ title: 'note', parent_id: srcFolder.id });
const conflictedNote = await Note.createConflictNote(note1, ItemChange.SOURCE_SYNC);
const movedNote = await Note.moveToFolder(conflictedNote.id, targetFolder.id);
expect(movedNote.parent_id).toBe(targetFolder.id);
expect(movedNote.is_conflict).toBe(0);
expect(movedNote.conflict_original_id).toBe('');
}));
}); });

View File

@@ -127,7 +127,7 @@ export default class Note extends BaseItem {
static async linkedItemIdsByType(type: ModelType, body: string) { static async linkedItemIdsByType(type: ModelType, body: string) {
const items = await this.linkedItems(body); const items = await this.linkedItems(body);
const output = []; const output: string[] = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const item = items[i]; const item = items[i];
@@ -208,9 +208,9 @@ export default class Note extends BaseItem {
for (const basePath of pathsToTry) { for (const basePath of pathsToTry) {
const reStrings = [ const reStrings = [
// Handles file://path/to/abcdefg.jpg?t=12345678 // Handles file://path/to/abcdefg.jpg?t=12345678
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9.]+\\?t=[0-9]+`, `${pregQuote(`${basePath}/`)}[a-zA-Z0-9]{32}\\.[a-zA-Z0-9]+\\?t=[0-9]+`,
// Handles file://path/to/abcdefg.jpg // Handles file://path/to/abcdefg.jpg
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9.]+`, `${pregQuote(`${basePath}/`)}[a-zA-Z0-9]{32}\\.[a-zA-Z0-9]+`,
]; ];
for (const reString of reStrings) { for (const reString of reStrings) {
const re = new RegExp(reString, 'gi'); const re = new RegExp(reString, 'gi');
@@ -523,6 +523,7 @@ export default class Note extends BaseItem {
changes: { changes: {
parent_id: folderId, parent_id: folderId,
is_conflict: 0, // Also reset the conflict flag in case we're moving the note out of the conflict folder is_conflict: 0, // Also reset the conflict flag in case we're moving the note out of the conflict folder
conflict_original_id: '', // Reset parent id as well.
}, },
}); });
} }
@@ -537,6 +538,7 @@ export default class Note extends BaseItem {
id: noteId, id: noteId,
parent_id: folderId, parent_id: folderId,
is_conflict: 0, is_conflict: 0,
conflict_original_id: '',
updated_time: time.unixMs(), updated_time: time.unixMs(),
}; };
@@ -911,4 +913,12 @@ export default class Note extends BaseItem {
return new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); return new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
} }
static async createConflictNote(sourceNote: NoteEntity, changeSource: number): Promise<NoteEntity> {
const conflictNote = Object.assign({}, sourceNote);
delete conflictNote.id;
conflictNote.is_conflict = 1;
conflictNote.conflict_original_id = sourceNote.id;
return await Note.save(conflictNote, { autoTimestamp: false, changeSource: changeSource });
}
} }

View File

@@ -12,6 +12,7 @@ const { mime } = require('../mime-utils.js');
const { filename, safeFilename } = require('../path-utils'); const { filename, safeFilename } = require('../path-utils');
const { FsDriverDummy } = require('../fs-driver-dummy.js'); const { FsDriverDummy } = require('../fs-driver-dummy.js');
import JoplinError from '../JoplinError'; import JoplinError from '../JoplinError';
import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
export default class Resource extends BaseItem { export default class Resource extends BaseItem {
@@ -192,10 +193,10 @@ export default class Resource extends BaseItem {
// as it should be uploaded to the sync target. Note that this may be different from what is stored // as it should be uploaded to the sync target. Note that this may be different from what is stored
// in the database. In particular, the flag encryption_blob_encrypted might be 1 on the sync target // in the database. In particular, the flag encryption_blob_encrypted might be 1 on the sync target
// if the resource is encrypted, but will be 0 locally because the device has the decrypted resource. // if the resource is encrypted, but will be 0 locally because the device has the decrypted resource.
static async fullPathForSyncUpload(resource: ResourceEntity) { public static async fullPathForSyncUpload(resource: ResourceEntity) {
const plainTextPath = this.fullPath(resource); const plainTextPath = this.fullPath(resource);
if (!Setting.value('encryption.enabled')) { if (!Setting.value('encryption.enabled') || !itemCanBeEncrypted(resource as any)) {
// Normally not possible since itemsThatNeedSync should only return decrypted items // Normally not possible since itemsThatNeedSync should only return decrypted items
if (resource.encryption_blob_encrypted) throw new Error('Trying to access encrypted resource but encryption is currently disabled'); if (resource.encryption_blob_encrypted) throw new Error('Trying to access encrypted resource but encryption is currently disabled');
return { path: plainTextPath, resource: resource }; return { path: plainTextPath, resource: resource };

View File

@@ -472,24 +472,16 @@ class Setting extends BaseModel {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer'); return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
}, },
public: true, public: true,
label: () => _('Joplin Cloud URL'), label: () => _('Joplin Server URL'),
description: () => emptyDirWarning, description: () => emptyDirWarning,
storage: SettingStorage.File, storage: SettingStorage.File,
}, },
// 'sync.9.directory': { 'sync.9.userContentPath': {
// value: 'Apps/Joplin', value: '',
// type: SettingItemType.String, type: SettingItemType.String,
// section: 'sync', public: false,
// show: (settings: any) => { storage: SettingStorage.Database,
// return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer'); },
// },
// filter: value => {
// return value ? ltrimSlashes(rtrimSlashes(value)) : '';
// },
// public: true,
// label: () => _('Joplin Cloud Directory'),
// storage: SettingStorage.File,
// },
'sync.9.username': { 'sync.9.username': {
value: '', value: '',
type: SettingItemType.String, type: SettingItemType.String,
@@ -498,7 +490,7 @@ class Setting extends BaseModel {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer'); return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
}, },
public: true, public: true,
label: () => _('Joplin Cloud email'), label: () => _('Joplin Server email'),
storage: SettingStorage.File, storage: SettingStorage.File,
}, },
'sync.9.password': { 'sync.9.password': {
@@ -509,6 +501,45 @@ class Setting extends BaseModel {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer'); return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
}, },
public: true, public: true,
label: () => _('Joplin Server password'),
secure: true,
},
// Although sync.10.path is essentially a constant, we still define
// it here so that both Joplin Server and Joplin Cloud can be
// handled in the same consistent way. Also having it a setting
// means it can be set to something else for development.
'sync.10.path': {
value: 'https://api.joplincloud.com',
type: SettingItemType.String,
public: false,
storage: SettingStorage.Database,
},
'sync.10.userContentPath': {
value: 'https://joplinusercontent.com',
type: SettingItemType.String,
public: false,
storage: SettingStorage.Database,
},
'sync.10.username': {
value: '',
type: SettingItemType.String,
section: 'sync',
show: (settings: any) => {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinCloud');
},
public: true,
label: () => _('Joplin Cloud email'),
storage: SettingStorage.File,
},
'sync.10.password': {
value: '',
type: SettingItemType.String,
section: 'sync',
show: (settings: any) => {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinCloud');
},
public: true,
label: () => _('Joplin Cloud password'), label: () => _('Joplin Cloud password'),
secure: true, secure: true,
}, },
@@ -539,6 +570,7 @@ class Setting extends BaseModel {
'sync.4.auth': { value: '', type: SettingItemType.String, public: false }, 'sync.4.auth': { value: '', type: SettingItemType.String, public: false },
'sync.7.auth': { value: '', type: SettingItemType.String, public: false }, 'sync.7.auth': { value: '', type: SettingItemType.String, public: false },
'sync.9.auth': { value: '', type: SettingItemType.String, public: false }, 'sync.9.auth': { value: '', type: SettingItemType.String, public: false },
'sync.10.auth': { value: '', type: SettingItemType.String, public: false },
'sync.1.context': { value: '', type: SettingItemType.String, public: false }, 'sync.1.context': { value: '', type: SettingItemType.String, public: false },
'sync.2.context': { value: '', type: SettingItemType.String, public: false }, 'sync.2.context': { value: '', type: SettingItemType.String, public: false },
'sync.3.context': { value: '', type: SettingItemType.String, public: false }, 'sync.3.context': { value: '', type: SettingItemType.String, public: false },
@@ -548,6 +580,7 @@ class Setting extends BaseModel {
'sync.7.context': { value: '', type: SettingItemType.String, public: false }, 'sync.7.context': { value: '', type: SettingItemType.String, public: false },
'sync.8.context': { value: '', type: SettingItemType.String, public: false }, 'sync.8.context': { value: '', type: SettingItemType.String, public: false },
'sync.9.context': { value: '', type: SettingItemType.String, public: false }, 'sync.9.context': { value: '', type: SettingItemType.String, public: false },
'sync.10.context': { value: '', type: SettingItemType.String, public: false },
'sync.maxConcurrentConnections': { value: 5, type: SettingItemType.Int, storage: SettingStorage.File, public: true, advanced: true, section: 'sync', label: () => _('Max concurrent connections'), minimum: 1, maximum: 20, step: 1 }, 'sync.maxConcurrentConnections': { value: 5, type: SettingItemType.Int, storage: SettingStorage.File, public: true, advanced: true, section: 'sync', label: () => _('Max concurrent connections'), minimum: 1, maximum: 20, step: 1 },

View File

@@ -51,11 +51,20 @@ describe('models_Tag', function() {
const folder1 = await Folder.save({ title: 'folder1' }); const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id }); const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id });
const todo1 = await Note.save({ title: 'todo 1', parent_id: folder1.id, is_todo: 1, todo_completed: 1590085027710 });
await Tag.setNoteTagsByTitles(note1.id, ['un']); await Tag.setNoteTagsByTitles(note1.id, ['un']);
await Tag.setNoteTagsByTitles(note2.id, ['un']); await Tag.setNoteTagsByTitles(note2.id, ['un']);
await Tag.setNoteTagsByTitles(todo1.id, ['un']);
let tags = await Tag.allWithNotes(); let tags = await Tag.allWithNotes();
expect(tags.length).toBe(1); expect(tags.length).toBe(1);
expect(tags[0].note_count).toBe(3);
expect(tags[0].todo_completed_count).toBe(1);
await Note.delete(todo1.id);
tags = await Tag.allWithNotes();
expect(tags.length).toBe(1);
expect(tags[0].note_count).toBe(2); expect(tags[0].note_count).toBe(2);
await Note.delete(note1.id); await Note.delete(note1.id);
@@ -74,6 +83,8 @@ describe('models_Tag', function() {
const folder1 = await Folder.save({ title: 'folder1' }); const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id }); const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id });
const todo1 = await Note.save({ title: 'todo 2', parent_id: folder1.id, is_todo: 1, todo_completed: 1590085027710 });
const todo2 = await Note.save({ title: 'todo 2', parent_id: folder1.id, is_todo: 1 });
const tag = await Tag.save({ title: 'mytag' }); const tag = await Tag.save({ title: 'mytag' });
await Tag.addNote(tag.id, note1.id); await Tag.addNote(tag.id, note1.id);
@@ -83,6 +94,12 @@ describe('models_Tag', function() {
await Tag.addNote(tag.id, note2.id); await Tag.addNote(tag.id, note2.id);
tagWithCount = await Tag.loadWithCount(tag.id); tagWithCount = await Tag.loadWithCount(tag.id);
expect(tagWithCount.note_count).toBe(2); expect(tagWithCount.note_count).toBe(2);
await Tag.addNote(tag.id, todo1.id);
await Tag.addNote(tag.id, todo2.id);
tagWithCount = await Tag.loadWithCount(tag.id);
expect(tagWithCount.note_count).toBe(4);
expect(tagWithCount.todo_completed_count).toBe(1);
})); }));
it('should get common tags for set of notes', (async () => { it('should get common tags for set of notes', (async () => {
@@ -131,6 +148,7 @@ describe('models_Tag', function() {
expect(commonTagIds.includes(tagb.id)).toBe(true); expect(commonTagIds.includes(tagb.id)).toBe(true);
commonTags = await Tag.commonTagsByNoteIds([note3.id]); commonTags = await Tag.commonTagsByNoteIds([note3.id]);
commonTagIds = commonTags.map(t => t.id); commonTagIds = commonTags.map(t => t.id);
expect(commonTags.length).toBe(3); expect(commonTags.length).toBe(3);
expect(commonTagIds.includes(taga.id)).toBe(true); expect(commonTagIds.includes(taga.id)).toBe(true);

View File

@@ -0,0 +1,5 @@
import { BaseItemEntity } from '../../services/database/types';
export default function(resource: BaseItemEntity): boolean {
return !resource.is_shared && !resource.share_id;
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@joplin/lib", "name": "@joplin/lib",
"version": "2.0.0", "version": "2.0.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@joplin/lib", "name": "@joplin/lib",
"version": "2.0.0", "version": "2.0.2",
"description": "Joplin Core library", "description": "Joplin Core library",
"author": "Laurent Cozic", "author": "Laurent Cozic",
"homepage": "", "homepage": "",
@@ -16,20 +16,20 @@
"test-ci": "npm run test" "test-ci": "npm run test"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^9.0.6",
"@types/jest": "^26.0.15", "@types/jest": "^26.0.15",
"@types/node": "^14.14.6", "@types/node": "^14.14.6",
"@types/fs-extra": "^9.0.6", "clean-html": "^1.5.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"sharp": "^0.26.2", "sharp": "^0.26.2",
"typescript": "^4.0.5", "typescript": "^4.0.5"
"clean-html": "^1.5.0"
}, },
"dependencies": { "dependencies": {
"@joplin/fork-htmlparser2": "^4.1.24", "@joplin/fork-htmlparser2": "^4.1.26",
"@joplin/fork-sax": "^1.2.28", "@joplin/fork-sax": "^1.2.30",
"@joplin/renderer": "^1.8.2", "@joplin/renderer": "^1.8.2",
"@joplin/turndown": "^4.0.46", "@joplin/turndown": "^4.0.48",
"@joplin/turndown-plugin-gfm": "^1.0.28", "@joplin/turndown-plugin-gfm": "^1.0.30",
"async-mutex": "^0.1.3", "async-mutex": "^0.1.3",
"aws-sdk": "^2.588.0", "aws-sdk": "^2.588.0",
"base-64": "^0.1.0", "base-64": "^0.1.0",

View File

@@ -140,6 +140,28 @@ export default class ReportService {
return output; return output;
} }
private addRetryAllHandler(section: ReportSection): ReportSection {
const retryHandlers: Function[] = [];
for (let i = 0; i < section.body.length; i++) {
const item: RerportItemOrString = section.body[i];
if (typeof item !== 'string' && item.canRetry) {
retryHandlers.push(item.retryHandler);
}
}
if (retryHandlers.length) {
section.canRetryAll = true;
section.retryAllHandler = async () => {
for (const retryHandler of retryHandlers) {
await retryHandler();
}
};
}
return section;
}
async status(syncTarget: number): Promise<ReportSection[]> { async status(syncTarget: number): Promise<ReportSection[]> {
const r = await this.syncStatus(syncTarget); const r = await this.syncStatus(syncTarget);
const sections: ReportSection[] = []; const sections: ReportSection[] = [];
@@ -175,6 +197,8 @@ export default class ReportService {
section.body.push({ type: ReportItemType.CloseList }); section.body.push({ type: ReportItemType.CloseList });
section = this.addRetryAllHandler(section);
sections.push(section); sections.push(section);
} }
@@ -200,23 +224,7 @@ export default class ReportService {
}); });
} }
const retryHandlers: Function[] = []; section = this.addRetryAllHandler(section);
for (let i = 0; i < section.body.length; i++) {
const item: RerportItemOrString = section.body[i];
if (typeof item !== 'string' && item.canRetry) {
retryHandlers.push(item.retryHandler);
}
}
if (retryHandlers.length > 1) {
section.canRetryAll = true;
section.retryAllHandler = async () => {
for (const retryHandler of retryHandlers) {
await retryHandler();
}
};
}
sections.push(section); sections.push(section);
} }

View File

@@ -9,6 +9,7 @@ import shim from '../shim';
import BaseService from './BaseService'; import BaseService from './BaseService';
import { _ } from '../locale'; import { _ } from '../locale';
import { ItemChangeEntity, NoteEntity, RevisionEntity } from './database/types'; import { ItemChangeEntity, NoteEntity, RevisionEntity } from './database/types';
const { substrWithEllipsis } = require('../string-utils');
const { sprintf } = require('sprintf-js'); const { sprintf } = require('sprintf-js');
const { wrapError } = require('../errorUtils'); const { wrapError } = require('../errorUtils');
@@ -230,7 +231,23 @@ export default class RevisionService extends BaseService {
return folder; return folder;
} }
async importRevisionNote(note: NoteEntity) { // reverseRevIndex = 0 means restoring the latest version. reverseRevIndex =
// 1 means the version before that, etc.
public async restoreNoteById(noteId: string, reverseRevIndex: number): Promise<NoteEntity> {
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
if (!revisions.length) throw new Error(`No revision for note "${noteId}"`);
const revIndex = revisions.length - 1 - reverseRevIndex;
const note = await this.revisionNote(revisions, revIndex);
return this.importRevisionNote(note);
}
public restoreSuccessMessage(note: NoteEntity): string {
return _('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(note.title, 0, 32), this.restoreFolderTitle());
}
async importRevisionNote(note: NoteEntity): Promise<NoteEntity> {
const toImport = Object.assign({}, note); const toImport = Object.assign({}, note);
delete toImport.id; delete toImport.id;
delete toImport.updated_time; delete toImport.updated_time;
@@ -242,7 +259,7 @@ export default class RevisionService extends BaseService {
toImport.parent_id = folder.id; toImport.parent_id = folder.id;
await Note.save(toImport); return Note.save(toImport);
} }
async maintenance() { async maintenance() {

View File

@@ -77,6 +77,6 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
folderIsShareRootAndOwnedByUser: commandFolder ? isRootSharedFolder(commandFolder) && isSharedFolderOwner(state, commandFolder.id) : false, folderIsShareRootAndOwnedByUser: commandFolder ? isRootSharedFolder(commandFolder) && isSharedFolderOwner(state, commandFolder.id) : false,
folderIsShared: commandFolder ? !!commandFolder.share_id : false, folderIsShared: commandFolder ? !!commandFolder.share_id : false,
joplinServerConnected: state.settings['sync.target'] === 9, joplinServerConnected: [9, 10].includes(state.settings['sync.target']),
}; };
} }

View File

@@ -1,3 +1,17 @@
import { ModelType } from "../../BaseModel";
export interface BaseItemEntity {
id?: string;
encryption_applied?: boolean;
is_shared?: number;
share_id?: string;
type_?: ModelType;
}
// AUTO-GENERATED BY packages/tools/generate-database-types.js // AUTO-GENERATED BY packages/tools/generate-database-types.js
/* /*
@@ -29,8 +43,6 @@ export interface FolderEntity {
"encryption_applied"?: number "encryption_applied"?: number
"parent_id"?: string "parent_id"?: string
"is_shared"?: number "is_shared"?: number
"is_linked_folder"?: number
"source_folder_owner_id"?: string
"share_id"?: string "share_id"?: string
"type_"?: number "type_"?: number
} }
@@ -117,6 +129,7 @@ export interface NoteEntity {
"markup_language"?: number "markup_language"?: number
"is_shared"?: number "is_shared"?: number
"share_id"?: string "share_id"?: string
"conflict_original_id"?: string
"type_"?: number "type_"?: number
} }
export interface NotesNormalizedEntity { export interface NotesNormalizedEntity {
@@ -226,6 +239,7 @@ export interface TagsWithNoteCountEntity {
"created_time"?: number | null "created_time"?: number | null
"updated_time"?: number | null "updated_time"?: number | null
"note_count"?: any | null "note_count"?: any | null
"todo_completed_count"?: any | null
"type_"?: number "type_"?: number
} }
export interface VersionEntity { export interface VersionEntity {

View File

@@ -0,0 +1,18 @@
// For this:
//
// userId: d67VzcrHs6zGzROagnzwhOZJI0vKbezc
// baseUrl: http://example.com
// userContentBaseUrl: http://usercontent.com
//
// => Returns http://d67Vzcrhs6.usercontent.com
//
// If the userContentBaseUrl is an empty string, the baseUrl is returned instead.
export default function(userId: string, baseUrl: string, userContentBaseUrl: string) {
if (userContentBaseUrl && baseUrl !== userContentBaseUrl) {
if (!userId) throw new Error('User ID must be specified');
const url = new URL(userContentBaseUrl);
return `${url.protocol}//${userId.substr(0, 10).toLowerCase()}.${url.host}`;
} else {
return baseUrl;
}
}

View File

@@ -1,8 +1,21 @@
import Logger from '../../Logger';
import shim from '../../shim'; import shim from '../../shim';
import { PluginManifest } from './utils/types'; import { PluginManifest } from './utils/types';
const md5 = require('md5'); const md5 = require('md5');
const compareVersions = require('compare-versions'); const compareVersions = require('compare-versions');
const logger = Logger.create('RepositoryApi');
interface ReleaseAsset {
name: string;
browser_download_url: string;
}
interface Release {
upload_url: string;
assets: ReleaseAsset[];
}
export default class RepositoryApi { export default class RepositoryApi {
// As a base URL, this class can support either a remote repository or a // As a base URL, this class can support either a remote repository or a
@@ -14,6 +27,7 @@ export default class RepositoryApi {
// Later on, other repo types could be supported. // Later on, other repo types could be supported.
private baseUrl_: string; private baseUrl_: string;
private tempDir_: string; private tempDir_: string;
private release_: Release = null;
private manifests_: PluginManifest[] = null; private manifests_: PluginManifest[] = null;
public constructor(baseUrl: string, tempDir: string) { public constructor(baseUrl: string, tempDir: string) {
@@ -21,7 +35,12 @@ export default class RepositoryApi {
this.tempDir_ = tempDir; this.tempDir_ = tempDir;
} }
public async loadManifests() { public async initialize() {
await this.loadManifests();
await this.loadRelease();
}
private async loadManifests() {
const manifestsText = await this.fetchText('manifests.json'); const manifestsText = await this.fetchText('manifests.json');
try { try {
const manifests = JSON.parse(manifestsText); const manifests = JSON.parse(manifestsText);
@@ -34,6 +53,27 @@ export default class RepositoryApi {
} }
} }
private get githubApiUrl(): string {
// https://github.com/joplin/plugins
// https://api.github.com/repos/joplin/plugins/releases
return this.baseUrl_.replace(/^(https:\/\/)(github\.com\/)(.*)$/, '$1api.$2repos/$3');
}
private async loadRelease() {
this.release_ = null;
if (this.isLocalRepo) return;
try {
const response = await fetch(`${this.githubApiUrl}/releases`);
const releases = await response.json();
if (!releases.length) throw new Error('No release was found');
this.release_ = releases[0];
} catch (error) {
logger.warn('Could not load release - files will be downloaded from the repository directly:', error);
}
}
private get isLocalRepo(): boolean { private get isLocalRepo(): boolean {
return this.baseUrl_.indexOf('http') !== 0; return this.baseUrl_.indexOf('http') !== 0;
} }
@@ -46,15 +86,34 @@ export default class RepositoryApi {
} }
} }
private fileUrl(relativePath: string): string { private assetFileUrl(pluginId: string): string {
if (this.release_) {
const asset = this.release_.assets.find(asset => {
const s = asset.name.split('@');
s.pop();
const id = s.join('@');
return id === pluginId;
});
if (asset) return asset.browser_download_url;
logger.warn(`Could not get plugin from release: ${pluginId}`);
}
// If we couldn't get the plugin file from the release, get it directly
// from the repository instead.
return this.repoFileUrl(`plugins/${pluginId}/plugin.jpl`);
}
private repoFileUrl(relativePath: string): string {
return `${this.contentBaseUrl}/${relativePath}`; return `${this.contentBaseUrl}/${relativePath}`;
} }
private async fetchText(path: string): Promise<string> { private async fetchText(path: string): Promise<string> {
if (this.isLocalRepo) { if (this.isLocalRepo) {
return shim.fsDriver().readFile(this.fileUrl(path), 'utf8'); return shim.fsDriver().readFile(this.repoFileUrl(path), 'utf8');
} else { } else {
return shim.fetchText(this.fileUrl(path)); return shim.fetchText(this.repoFileUrl(path));
} }
} }
@@ -86,7 +145,7 @@ export default class RepositoryApi {
const manifest = manifests.find(m => m.id === pluginId); const manifest = manifests.find(m => m.id === pluginId);
if (!manifest) throw new Error(`No manifest for plugin ID "${pluginId}"`); if (!manifest) throw new Error(`No manifest for plugin ID "${pluginId}"`);
const fileUrl = this.fileUrl(`plugins/${manifest.id}/plugin.jpl`); const fileUrl = this.assetFileUrl(manifest.id); // this.repoFileUrl(`plugins/${manifest.id}/plugin.jpl`);
const hash = md5(Date.now() + Math.random()); const hash = md5(Date.now() + Math.random());
const targetPath = `${this.tempDir_}/${hash}_${manifest.id}.jpl`; const targetPath = `${this.tempDir_}/${hash}_${manifest.id}.jpl`;

View File

@@ -21,6 +21,34 @@ import { Command } from './types';
* *
* To view what arguments are supported, you can open any of these files * To view what arguments are supported, you can open any of these files
* and look at the `execute()` command. * and look at the `execute()` command.
*
* ## Executing editor commands
*
* There might be a situation where you want to invoke editor commands
* without using a {@link JoplinContentScripts | contentScript}. For this
* reason Joplin provides the built in `editor.execCommand` command.
*
* `editor.execCommand` should work with any core command in both the
* [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
* [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
* as well as most functions calls directly on a CodeMirror editor object (extensions).
*
* * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
* * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
*
* `editor.execCommand` supports adding arguments for the commands.
*
* ```typescript
* await joplin.commands.execute('editor.execCommand', {
* name: 'madeUpCommand', // CodeMirror and TinyMCE
* args: [], // CodeMirror and TinyMCE
* ui: false, // TinyMCE only
* value: '', // TinyMCE only
* });
* ```
*
* [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
*
*/ */
export default class JoplinCommands { export default class JoplinCommands {

View File

@@ -28,7 +28,7 @@ export default async function(request: Request) {
options.caseInsensitive = true; options.caseInsensitive = true;
results = await ModelClass.all(options); results = await ModelClass.all(options);
} else { } else {
results = await SearchEngineUtils.notesForQuery(query, defaultLoadOptions(request, ModelType.Note)); results = await SearchEngineUtils.notesForQuery(query, false, defaultLoadOptions(request, ModelType.Note));
} }
return collectionToPaginatedResults(modelType, results, request); return collectionToPaginatedResults(modelType, results, request);

View File

@@ -386,6 +386,7 @@ describe('services_SearchEngine', function() {
expect((await engine.search('测试')).length).toBe(1); expect((await engine.search('测试')).length).toBe(1);
expect((await engine.search('测试'))[0].fields).toEqual(['body']); expect((await engine.search('测试'))[0].fields).toEqual(['body']);
expect((await engine.search('测试*'))[0].fields).toEqual(['body']); expect((await engine.search('测试*'))[0].fields).toEqual(['body']);
expect((await engine.search('any:1 type:todo 测试')).length).toBe(1);
})); }));
it('should support queries with Japanese characters', (async () => { it('should support queries with Japanese characters', (async () => {
@@ -398,7 +399,7 @@ describe('services_SearchEngine', function() {
expect((await engine.search('できません')).length).toBe(1); expect((await engine.search('できません')).length).toBe(1);
expect((await engine.search('できません*'))[0].fields.sort()).toEqual(['body', 'title']); // usually assume that keyword was matched in body expect((await engine.search('できません*'))[0].fields.sort()).toEqual(['body', 'title']); // usually assume that keyword was matched in body
expect((await engine.search('テスト'))[0].fields.sort()).toEqual(['body']); expect((await engine.search('テスト'))[0].fields.sort()).toEqual(['body']);
expect((await engine.search('any:1 type:todo テスト')).length).toBe(1);
})); }));
it('should support queries with Korean characters', (async () => { it('should support queries with Korean characters', (async () => {
@@ -409,6 +410,7 @@ describe('services_SearchEngine', function() {
expect((await engine.search('이것은')).length).toBe(1); expect((await engine.search('이것은')).length).toBe(1);
expect((await engine.search('말')).length).toBe(1); expect((await engine.search('말')).length).toBe(1);
expect((await engine.search('any:1 type:todo 말')).length).toBe(1);
})); }));
it('should support queries with Thai characters', (async () => { it('should support queries with Thai characters', (async () => {
@@ -419,28 +421,7 @@ describe('services_SearchEngine', function() {
expect((await engine.search('นี่คือค')).length).toBe(1); expect((await engine.search('นี่คือค')).length).toBe(1);
expect((await engine.search('ไทย')).length).toBe(1); expect((await engine.search('ไทย')).length).toBe(1);
})); expect((await engine.search('any:1 type:todo ไทย')).length).toBe(1);
it('should support field restricted queries with Chinese characters', (async () => {
let rows;
const n1 = await Note.save({ title: '你好', body: '我是法国人' });
await engine.syncTables();
expect((await engine.search('title:你好*')).length).toBe(1);
expect((await engine.search('title:你好*'))[0].fields).toEqual(['title']);
expect((await engine.search('body:法国人')).length).toBe(1);
expect((await engine.search('body:法国人'))[0].fields).toEqual(['body']);
expect((await engine.search('body:你好')).length).toBe(0);
expect((await engine.search('title:你好 body:法国人')).length).toBe(1);
expect((await engine.search('title:你好 body:法国人'))[0].fields.sort()).toEqual(['body', 'title']);
expect((await engine.search('title:你好 body:bla')).length).toBe(0);
expect((await engine.search('title:你好 我是')).length).toBe(1);
expect((await engine.search('title:你好 我是'))[0].fields.sort()).toEqual(['body', 'title']);
expect((await engine.search('title:bla 我是')).length).toBe(0);
// For non-alpha char, only the first field is looked at, the following ones are ignored
// expect((await engine.search('title:你好 title:hello')).length).toBe(1);
})); }));
it('should parse normal query strings', (async () => { it('should parse normal query strings', (async () => {

View File

@@ -17,6 +17,7 @@ export default class SearchEngine {
public static relevantFields = 'id, title, body, user_created_time, user_updated_time, is_todo, todo_completed, todo_due, parent_id, latitude, longitude, altitude, source_url'; public static relevantFields = 'id, title, body, user_created_time, user_updated_time, is_todo, todo_completed, todo_due, parent_id, latitude, longitude, altitude, source_url';
public static SEARCH_TYPE_AUTO = 'auto'; public static SEARCH_TYPE_AUTO = 'auto';
public static SEARCH_TYPE_BASIC = 'basic'; public static SEARCH_TYPE_BASIC = 'basic';
public static SEARCH_TYPE_NONLATIN_SCRIPT = 'nonlatin';
public static SEARCH_TYPE_FTS = 'fts'; public static SEARCH_TYPE_FTS = 'fts';
public dispatch: Function = (_o: any) => {}; public dispatch: Function = (_o: any) => {};
@@ -533,6 +534,7 @@ export default class SearchEngine {
determineSearchType_(query: string, preferredSearchType: any) { determineSearchType_(query: string, preferredSearchType: any) {
if (preferredSearchType === SearchEngine.SEARCH_TYPE_BASIC) return SearchEngine.SEARCH_TYPE_BASIC; if (preferredSearchType === SearchEngine.SEARCH_TYPE_BASIC) return SearchEngine.SEARCH_TYPE_BASIC;
if (preferredSearchType === SearchEngine.SEARCH_TYPE_NONLATIN_SCRIPT) return SearchEngine.SEARCH_TYPE_NONLATIN_SCRIPT;
// If preferredSearchType is "fts" we auto-detect anyway // If preferredSearchType is "fts" we auto-detect anyway
// because it's not always supported. // because it's not always supported.
@@ -547,10 +549,15 @@ export default class SearchEngine {
const textQuery = allTerms.filter(x => x.name === 'text' || x.name == 'title' || x.name == 'body').map(x => x.value).join(' '); const textQuery = allTerms.filter(x => x.name === 'text' || x.name == 'title' || x.name == 'body').map(x => x.value).join(' ');
const st = scriptType(textQuery); const st = scriptType(textQuery);
if (!Setting.value('db.ftsEnabled') || ['ja', 'zh', 'ko', 'th'].indexOf(st) >= 0) { if (!Setting.value('db.ftsEnabled')) {
return SearchEngine.SEARCH_TYPE_BASIC; return SearchEngine.SEARCH_TYPE_BASIC;
} }
// Non-alphabetical languages aren't support by SQLite FTS (except with extensions which are not available in all platforms)
if (['ja', 'zh', 'ko', 'th'].indexOf(st) >= 0) {
return SearchEngine.SEARCH_TYPE_NONLATIN_SCRIPT;
}
return SearchEngine.SEARCH_TYPE_FTS; return SearchEngine.SEARCH_TYPE_FTS;
} }
@@ -565,7 +572,6 @@ export default class SearchEngine {
const parsedQuery = await this.parseQuery(searchString); const parsedQuery = await this.parseQuery(searchString);
if (searchType === SearchEngine.SEARCH_TYPE_BASIC) { if (searchType === SearchEngine.SEARCH_TYPE_BASIC) {
// Non-alphabetical languages aren't support by SQLite FTS (except with extensions which are not available in all platforms)
searchString = this.normalizeText_(searchString); searchString = this.normalizeText_(searchString);
const rows = await this.basicSearch(searchString); const rows = await this.basicSearch(searchString);
@@ -579,10 +585,11 @@ export default class SearchEngine {
// when searching. // when searching.
// https://github.com/laurent22/joplin/issues/1075#issuecomment-459258856 // https://github.com/laurent22/joplin/issues/1075#issuecomment-459258856
const useFts = searchType === SearchEngine.SEARCH_TYPE_FTS;
try { try {
const { query, params } = queryBuilder(parsedQuery.allTerms); const { query, params } = queryBuilder(parsedQuery.allTerms, useFts);
const rows = await this.db().selectAll(query, params); const rows = await this.db().selectAll(query, params);
this.processResults_(rows, parsedQuery); this.processResults_(rows, parsedQuery, !useFts);
return rows; return rows;
} catch (error) { } catch (error) {
this.logger().warn(`Cannot execute MATCH query: ${searchString}: ${error.message}`); this.logger().warn(`Cannot execute MATCH query: ${searchString}: ${error.message}`);

View File

@@ -26,12 +26,21 @@ describe('services_SearchEngineUtils', function() {
Setting.setValue('showCompletedTodos', true); Setting.setValue('showCompletedTodos', true);
const rows = await SearchEngineUtils.notesForQuery('abcd', null, searchEngine); const rows = await SearchEngineUtils.notesForQuery('abcd', true, null, searchEngine);
expect(rows.length).toBe(3); expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(note1.id); expect(rows.map(r=>r.id)).toContain(note1.id);
expect(rows.map(r=>r.id)).toContain(todo1.id); expect(rows.map(r=>r.id)).toContain(todo1.id);
expect(rows.map(r=>r.id)).toContain(todo2.id); expect(rows.map(r=>r.id)).toContain(todo2.id);
const options: any = {};
options.fields = ['id', 'title'];
const rows2 = await SearchEngineUtils.notesForQuery('abcd', true, options, searchEngine);
expect(rows2.length).toBe(3);
expect(rows2.map(r=>r.id)).toContain(note1.id);
expect(rows2.map(r=>r.id)).toContain(todo1.id);
expect(rows2.map(r=>r.id)).toContain(todo2.id);
})); }));
it('hide completed', (async () => { it('hide completed', (async () => {
@@ -43,11 +52,56 @@ describe('services_SearchEngineUtils', function() {
Setting.setValue('showCompletedTodos', false); Setting.setValue('showCompletedTodos', false);
const rows = await SearchEngineUtils.notesForQuery('abcd', null, searchEngine); const rows = await SearchEngineUtils.notesForQuery('abcd', true, null, searchEngine);
expect(rows.length).toBe(2); expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(note1.id); expect(rows.map(r=>r.id)).toContain(note1.id);
expect(rows.map(r=>r.id)).toContain(todo1.id); expect(rows.map(r=>r.id)).toContain(todo1.id);
const options: any = {};
options.fields = ['id', 'title'];
const rows2 = await SearchEngineUtils.notesForQuery('abcd', true, options, searchEngine);
expect(rows2.length).toBe(2);
expect(rows2.map(r=>r.id)).toContain(note1.id);
expect(rows2.map(r=>r.id)).toContain(todo1.id);
}));
it('show completed (!applyUserSettings)', (async () => {
const note1 = await Note.save({ title: 'abcd', body: 'body 1' });
const todo1 = await Note.save({ title: 'abcd', body: 'todo 1', is_todo: 1 });
await Note.save({ title: 'qwer', body: 'body 2' });
const todo2 = await Note.save({ title: 'abcd', body: 'todo 2', is_todo: 1, todo_completed: 1590085027710 });
await searchEngine.syncTables();
Setting.setValue('showCompletedTodos', false);
const rows = await SearchEngineUtils.notesForQuery('abcd', false, null, searchEngine);
expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(note1.id);
expect(rows.map(r=>r.id)).toContain(todo1.id);
expect(rows.map(r=>r.id)).toContain(todo2.id);
})); }));
}); });
it('remove auto added fields', (async () => {
await Note.save({ title: 'abcd', body: 'body 1' });
await searchEngine.syncTables();
const testCases = [
['title', 'todo_due'],
['title', 'todo_completed'],
['title'],
['title', 'todo_completed', 'todo_due'],
];
for (const testCase of testCases) {
const rows = await SearchEngineUtils.notesForQuery('abcd', false, { fields: [...testCase] }, searchEngine);
testCase.push('type_');
expect(Object.keys(rows[0]).length).toBe(testCase.length);
for (const field of testCase) {
expect(rows[0]).toHaveProperty(field);
}
}
}));
}); });

View File

@@ -3,7 +3,7 @@ import Note from '../../models/Note';
import Setting from '../../models/Setting'; import Setting from '../../models/Setting';
export default class SearchEngineUtils { export default class SearchEngineUtils {
static async notesForQuery(query: string, options: any = null, searchEngine: SearchEngine = null) { static async notesForQuery(query: string, applyUserSettings: boolean, options: any = null, searchEngine: SearchEngine = null) {
if (!options) options = {}; if (!options) options = {};
if (!searchEngine) { if (!searchEngine) {
@@ -30,6 +30,20 @@ export default class SearchEngineUtils {
idWasAutoAdded = true; idWasAutoAdded = true;
} }
// Add fields is_todo and todo_completed for showCompletedTodos filtering.
// Also remember that the field was auto-added so that it can be removed afterwards.
let isTodoAutoAdded = false;
if (fields.indexOf('is_todo') < 0) {
fields.push('is_todo');
isTodoAutoAdded = true;
}
let todoCompletedAutoAdded = false;
if (fields.indexOf('todo_completed') < 0) {
fields.push('todo_completed');
todoCompletedAutoAdded = true;
}
const previewOptions = Object.assign({}, { const previewOptions = Object.assign({}, {
order: [], order: [],
fields: fields, fields: fields,
@@ -38,20 +52,22 @@ export default class SearchEngineUtils {
const notes = await Note.previews(null, previewOptions); const notes = await Note.previews(null, previewOptions);
// Filter completed todos
let filteredNotes = [...notes];
if (applyUserSettings && !Setting.value('showCompletedTodos')) {
filteredNotes = notes.filter(note => note.is_todo === 0 || (note.is_todo === 1 && note.todo_completed === 0));
}
// By default, the notes will be returned in reverse order // By default, the notes will be returned in reverse order
// or maybe random order so sort them here in the correct order // or maybe random order so sort them here in the correct order
// (search engine returns the results in order of relevance). // (search engine returns the results in order of relevance).
const sortedNotes = []; const sortedNotes = [];
for (let i = 0; i < notes.length; i++) { for (let i = 0; i < filteredNotes.length; i++) {
const idx = noteIds.indexOf(notes[i].id); const idx = noteIds.indexOf(filteredNotes[i].id);
sortedNotes[idx] = notes[i]; sortedNotes[idx] = filteredNotes[i];
if (idWasAutoAdded) delete sortedNotes[idx].id; if (idWasAutoAdded) delete sortedNotes[idx].id;
} if (todoCompletedAutoAdded) delete sortedNotes[idx].todo_completed;
if (isTodoAutoAdded) delete sortedNotes[idx].is_todo;
// Filter completed todos
let filteredNotes = [...sortedNotes];
if (!Setting.value('showCompletedTodos')) {
filteredNotes = sortedNotes.filter(note => note.is_todo === 0 || (note.is_todo === 1 && note.todo_completed === 0));
} }
// Note that when the search engine index is somehow corrupted, it might contain // Note that when the search engine index is somehow corrupted, it might contain
@@ -60,9 +76,9 @@ export default class SearchEngineUtils {
// issue: https://discourse.joplinapp.org/t/how-to-recover-corrupted-database/9367 // issue: https://discourse.joplinapp.org/t/how-to-recover-corrupted-database/9367
if (noteIds.length !== notes.length) { if (noteIds.length !== notes.length) {
// remove null objects // remove null objects
return filteredNotes.filter(n => n); return sortedNotes.filter(n => n);
} else { } else {
return filteredNotes; return sortedNotes;
} }
} }

File diff suppressed because it is too large Load Diff

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