You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Compare commits
197 Commits
v2.0.1
...
android-v2
Author | SHA1 | Date | |
---|---|---|---|
|
d8119bcf07 | ||
|
8bce259dc9 | ||
|
8a00eef901 | ||
|
31121c86d5 | ||
|
a4a156c7a5 | ||
|
c5b0529968 | ||
|
ba322b1f9b | ||
|
6f27eae7dd | ||
|
85cc08c0d4 | ||
|
ba38bf3490 | ||
|
2cf70675dc | ||
|
58f8d7e1b4 | ||
|
b55b35e53f | ||
|
c7194bf243 | ||
|
48abe2316e | ||
|
7aca380cfa | ||
|
551033f8ba | ||
|
3b6a66a016 | ||
|
5d7d1be363 | ||
|
2af3bf61ea | ||
|
6803f1c6a7 | ||
|
1aa70dd6b4 | ||
|
feaecf7653 | ||
|
af9f3eedd3 | ||
|
815800827b | ||
|
8f1e3ba43c | ||
|
8459b46cd0 | ||
|
c5c38a323f | ||
|
01e6ca4616 | ||
|
24a586c537 | ||
|
5d233a7387 | ||
|
054e5428d5 | ||
|
0120df7bdb | ||
|
a36b13dcb4 | ||
|
b81c300907 | ||
|
1ded589eeb | ||
|
315216132f | ||
|
2eaa821272 | ||
|
7c93e268e4 | ||
|
d0c4de92e2 | ||
|
91ce465535 | ||
|
4098c01e7c | ||
|
e617e6fab3 | ||
|
5fd6571bf1 | ||
|
00dc1d881b | ||
|
c37eb56ed7 | ||
|
b2b6ad479a | ||
|
0e4c545e14 | ||
|
bbae1aef28 | ||
|
cf86ffc36e | ||
|
9d80a79cda | ||
|
ca487ade9a | ||
|
75b66a9fff | ||
|
56fdf97693 | ||
|
ce02a30441 | ||
|
a058e09183 | ||
|
594084e274 | ||
|
5614eb9442 | ||
|
7a3a2084db | ||
|
95d7ccccea | ||
|
f7a7009b3c | ||
|
de7579a14e | ||
|
c8d7ecbf6c | ||
|
3c41b45e8e | ||
|
62a371b9f3 | ||
|
5528ab7cc8 | ||
|
824afd4809 | ||
|
8ed1330d68 | ||
|
fec5d4b335 | ||
|
e7b9103bfc | ||
|
dd1c9e3c2a | ||
|
7c45b95f6f | ||
|
a7e67952b8 | ||
|
1b7d40387d | ||
|
7921e70c4f | ||
|
8afac643ba | ||
|
23cfbc2367 | ||
|
de45740129 | ||
|
a04d8ef441 | ||
|
db7b802803 | ||
|
75d79f373a | ||
|
e8a02c26d0 | ||
|
147b6b13ab | ||
|
a496a3d90d | ||
|
69a8ada2ec | ||
|
87257870f4 | ||
|
21ea3253db | ||
|
770af6a53b | ||
|
c88e4f6628 | ||
|
2f79492192 | ||
|
69aa749205 | ||
|
87a5f18c7b | ||
|
1d2a3a97d2 | ||
|
42891e37a1 | ||
|
fe802b8ebc | ||
|
3cb6d4568c | ||
|
a9f0a75d9d | ||
|
07d30eb5d2 | ||
|
8f6a47536c | ||
|
d8d83b236e | ||
|
a355600e76 | ||
|
2a58664735 | ||
|
89bc181072 | ||
|
ab7380a09f | ||
|
f8a26cf8f9 | ||
|
3505a2a973 | ||
|
5f94de0f24 | ||
|
6811ea1eb9 | ||
|
7be59a7435 | ||
|
c0683ca4c3 | ||
|
2b286410f6 | ||
|
907ac7c1f8 | ||
|
8bc27021db | ||
|
41ed66d323 | ||
|
0ef7e98479 | ||
|
161c77cb48 | ||
|
50d17bfb36 | ||
|
ee0f23718b | ||
|
cfe4546a0b | ||
|
f45e0d106f | ||
|
12a66342db | ||
|
f2b17560e6 | ||
|
ba30dce6c8 | ||
|
f5984313be | ||
|
df058352a5 | ||
|
cde25fad92 | ||
|
d89bbc5571 | ||
|
71a7fc015a | ||
|
83cef7a824 | ||
|
f65de0c9eb | ||
|
3edf74e6d2 | ||
|
b01aa7eb45 | ||
|
e59e3aa7d1 | ||
|
51051e0ee0 | ||
|
b20ab19f13 | ||
|
68e79f1573 | ||
|
ed8ee67048 | ||
|
68b516998d | ||
|
0fa7a66fb6 | ||
|
13f39b9bd5 | ||
|
013d37bd09 | ||
|
4760e5e8ba | ||
|
8930dac40e | ||
|
3f0586ef63 | ||
|
e94503abbe | ||
|
f8253cc2f0 | ||
|
2806aa1b19 | ||
|
8f57e07279 | ||
|
6ff560f22f | ||
|
2226b79c46 | ||
|
9e9bf63d70 | ||
|
5d9419be5d | ||
|
9f37aa96c6 | ||
|
eceb14ff9e | ||
|
85211e8d5c | ||
|
bd08041f53 | ||
|
77b284f01f | ||
|
daaaa133ab | ||
|
e6c4eb7cdf | ||
|
dc2cdb7d3a | ||
|
f3e03d48bb | ||
|
6577f4f35d | ||
|
d29624c816 | ||
|
6afde54bda | ||
|
ec7f0f479a | ||
|
7f05420fda | ||
|
a3f8cd4850 | ||
|
01ccf5170a | ||
|
6ddb69e1ea | ||
|
b01f82bb33 | ||
|
b6c9edba21 | ||
|
f7d164be6e | ||
|
6f2f24171d | ||
|
12cc64008b | ||
|
b9955f58d3 | ||
|
489995daef | ||
|
e156ee1b58 | ||
|
a24b0091ad | ||
|
2655b6deee | ||
|
45c40f7395 | ||
|
ecb0eee355 | ||
|
4916f4cc92 | ||
|
15fe119256 | ||
|
0b46880a00 | ||
|
deaa731983 | ||
|
d061bb1a4f | ||
|
03db0c5486 | ||
|
bb275e671d | ||
|
2d0580ff71 | ||
|
2331d3487b | ||
|
f1380fd51d | ||
|
d462dab8eb | ||
|
cf37b74d9a | ||
|
aec3ea9c0c | ||
|
1f5aa70acd | ||
|
416637ce83 | ||
|
99c4b0bc01 |
249
.eslintignore
249
.eslintignore
@@ -74,69 +74,21 @@ packages/app-cli/app/command-settingschema.js.map
|
||||
packages/app-cli/app/services/plugins/PluginRunner.d.ts
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js.map
|
||||
packages/app-cli/tests/EnexToMd.d.ts
|
||||
packages/app-cli/tests/EnexToMd.js
|
||||
packages/app-cli/tests/EnexToMd.js.map
|
||||
packages/app-cli/tests/HtmlToMd.d.ts
|
||||
packages/app-cli/tests/HtmlToMd.js
|
||||
packages/app-cli/tests/HtmlToMd.js.map
|
||||
packages/app-cli/tests/InMemoryCache.d.ts
|
||||
packages/app-cli/tests/InMemoryCache.js
|
||||
packages/app-cli/tests/InMemoryCache.js.map
|
||||
packages/app-cli/tests/MdToHtml.d.ts
|
||||
packages/app-cli/tests/MdToHtml.js
|
||||
packages/app-cli/tests/MdToHtml.js.map
|
||||
packages/app-cli/tests/Synchronizer.basics.d.ts
|
||||
packages/app-cli/tests/Synchronizer.basics.js
|
||||
packages/app-cli/tests/Synchronizer.basics.js.map
|
||||
packages/app-cli/tests/Synchronizer.conflicts.d.ts
|
||||
packages/app-cli/tests/Synchronizer.conflicts.js
|
||||
packages/app-cli/tests/Synchronizer.conflicts.js.map
|
||||
packages/app-cli/tests/Synchronizer.e2ee.d.ts
|
||||
packages/app-cli/tests/Synchronizer.e2ee.js
|
||||
packages/app-cli/tests/Synchronizer.e2ee.js.map
|
||||
packages/app-cli/tests/Synchronizer.resources.d.ts
|
||||
packages/app-cli/tests/Synchronizer.resources.js
|
||||
packages/app-cli/tests/Synchronizer.resources.js.map
|
||||
packages/app-cli/tests/Synchronizer.revisions.d.ts
|
||||
packages/app-cli/tests/Synchronizer.revisions.js
|
||||
packages/app-cli/tests/Synchronizer.revisions.js.map
|
||||
packages/app-cli/tests/Synchronizer.sharing.d.ts
|
||||
packages/app-cli/tests/Synchronizer.sharing.js
|
||||
packages/app-cli/tests/Synchronizer.sharing.js.map
|
||||
packages/app-cli/tests/Synchronizer.tags.d.ts
|
||||
packages/app-cli/tests/Synchronizer.tags.js
|
||||
packages/app-cli/tests/Synchronizer.tags.js.map
|
||||
packages/app-cli/tests/Synchronizer.tools.d.ts
|
||||
packages/app-cli/tests/Synchronizer.tools.js
|
||||
packages/app-cli/tests/Synchronizer.tools.js.map
|
||||
packages/app-cli/tests/dateTimeFormats.d.ts
|
||||
packages/app-cli/tests/dateTimeFormats.js
|
||||
packages/app-cli/tests/dateTimeFormats.js.map
|
||||
packages/app-cli/tests/file-api-driver.d.ts
|
||||
packages/app-cli/tests/file-api-driver.js
|
||||
packages/app-cli/tests/file-api-driver.js.map
|
||||
packages/app-cli/tests/fsDriver.d.ts
|
||||
packages/app-cli/tests/fsDriver.js
|
||||
packages/app-cli/tests/fsDriver.js.map
|
||||
packages/app-cli/tests/htmlUtils.d.ts
|
||||
packages/app-cli/tests/htmlUtils.js
|
||||
packages/app-cli/tests/htmlUtils.js.map
|
||||
packages/app-cli/tests/models_Folder.d.ts
|
||||
packages/app-cli/tests/models_Folder.js
|
||||
packages/app-cli/tests/models_Folder.js.map
|
||||
packages/app-cli/tests/models_Folder.sharing.d.ts
|
||||
packages/app-cli/tests/models_Folder.sharing.js
|
||||
packages/app-cli/tests/models_Folder.sharing.js.map
|
||||
packages/app-cli/tests/models_Note.d.ts
|
||||
packages/app-cli/tests/models_Note.js
|
||||
packages/app-cli/tests/models_Note.js.map
|
||||
packages/app-cli/tests/models_Setting.d.ts
|
||||
packages/app-cli/tests/models_Setting.js
|
||||
packages/app-cli/tests/models_Setting.js.map
|
||||
packages/app-cli/tests/registry.d.ts
|
||||
packages/app-cli/tests/registry.js
|
||||
packages/app-cli/tests/registry.js.map
|
||||
packages/app-cli/tests/MdToMd.d.ts
|
||||
packages/app-cli/tests/MdToMd.js
|
||||
packages/app-cli/tests/MdToMd.js.map
|
||||
packages/app-cli/tests/services/keychain/KeychainService.d.ts
|
||||
packages/app-cli/tests/services/keychain/KeychainService.js
|
||||
packages/app-cli/tests/services/keychain/KeychainService.js.map
|
||||
packages/app-cli/tests/services/plugins/PluginService.d.ts
|
||||
packages/app-cli/tests/services/plugins/PluginService.js
|
||||
packages/app-cli/tests/services/plugins/PluginService.js.map
|
||||
packages/app-cli/tests/services/plugins/RepositoryApi.d.ts
|
||||
packages/app-cli/tests/services/plugins/RepositoryApi.js
|
||||
packages/app-cli/tests/services/plugins/RepositoryApi.js.map
|
||||
@@ -152,42 +104,9 @@ packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js.map
|
||||
packages/app-cli/tests/services_CommandService.d.ts
|
||||
packages/app-cli/tests/services_CommandService.js
|
||||
packages/app-cli/tests/services_CommandService.js.map
|
||||
packages/app-cli/tests/services_InteropService.d.ts
|
||||
packages/app-cli/tests/services_InteropService.js
|
||||
packages/app-cli/tests/services_InteropService.js.map
|
||||
packages/app-cli/tests/services_InteropService_Exporter_Html.d.ts
|
||||
packages/app-cli/tests/services_InteropService_Exporter_Html.js
|
||||
packages/app-cli/tests/services_InteropService_Exporter_Html.js.map
|
||||
packages/app-cli/tests/services_PluginService.d.ts
|
||||
packages/app-cli/tests/services_PluginService.js
|
||||
packages/app-cli/tests/services_PluginService.js.map
|
||||
packages/app-cli/tests/services_ResourceService.d.ts
|
||||
packages/app-cli/tests/services_ResourceService.js
|
||||
packages/app-cli/tests/services_ResourceService.js.map
|
||||
packages/app-cli/tests/services_SearchEngineUtils.d.ts
|
||||
packages/app-cli/tests/services_SearchEngineUtils.js
|
||||
packages/app-cli/tests/services_SearchEngineUtils.js.map
|
||||
packages/app-cli/tests/services_keychainService.d.ts
|
||||
packages/app-cli/tests/services_keychainService.js
|
||||
packages/app-cli/tests/services_keychainService.js.map
|
||||
packages/app-cli/tests/services_rest_Api.d.ts
|
||||
packages/app-cli/tests/services_rest_Api.js
|
||||
packages/app-cli/tests/services_rest_Api.js.map
|
||||
packages/app-cli/tests/synchronizer_LockHandler.d.ts
|
||||
packages/app-cli/tests/synchronizer_LockHandler.js
|
||||
packages/app-cli/tests/synchronizer_LockHandler.js.map
|
||||
packages/app-cli/tests/synchronizer_MigrationHandler.d.ts
|
||||
packages/app-cli/tests/synchronizer_MigrationHandler.js
|
||||
packages/app-cli/tests/synchronizer_MigrationHandler.js.map
|
||||
packages/app-cli/tests/test-utils-synchronizer.d.ts
|
||||
packages/app-cli/tests/test-utils-synchronizer.js
|
||||
packages/app-cli/tests/test-utils-synchronizer.js.map
|
||||
packages/app-cli/tests/test-utils.d.ts
|
||||
packages/app-cli/tests/test-utils.js
|
||||
packages/app-cli/tests/test-utils.js.map
|
||||
packages/app-cli/tests/testUtils.d.ts
|
||||
packages/app-cli/tests/testUtils.js
|
||||
packages/app-cli/tests/testUtils.js.map
|
||||
packages/app-desktop/ElectronAppWrapper.d.ts
|
||||
packages/app-desktop/ElectronAppWrapper.js
|
||||
packages/app-desktop/ElectronAppWrapper.js.map
|
||||
@@ -221,6 +140,9 @@ packages/app-desktop/commands/openProfileDirectory.js.map
|
||||
packages/app-desktop/commands/replaceMisspelling.d.ts
|
||||
packages/app-desktop/commands/replaceMisspelling.js
|
||||
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.js
|
||||
packages/app-desktop/commands/startExternalEditing.js.map
|
||||
@@ -356,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.js
|
||||
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.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
|
||||
@@ -419,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.js
|
||||
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.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map
|
||||
@@ -578,6 +506,9 @@ packages/app-desktop/gui/ResizableLayout/utils/persist.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js.map
|
||||
@@ -677,6 +608,9 @@ packages/app-desktop/gui/style/StyledFormLabel.js.map
|
||||
packages/app-desktop/gui/style/StyledInput.d.ts
|
||||
packages/app-desktop/gui/style/StyledInput.js
|
||||
packages/app-desktop/gui/style/StyledInput.js.map
|
||||
packages/app-desktop/gui/style/StyledLink.d.ts
|
||||
packages/app-desktop/gui/style/StyledLink.js
|
||||
packages/app-desktop/gui/style/StyledLink.js.map
|
||||
packages/app-desktop/gui/style/StyledMessage.d.ts
|
||||
packages/app-desktop/gui/style/StyledMessage.js
|
||||
packages/app-desktop/gui/style/StyledMessage.js.map
|
||||
@@ -782,9 +716,9 @@ packages/app-mobile/services/AlarmServiceDriver.android.js.map
|
||||
packages/app-mobile/services/AlarmServiceDriver.ios.d.ts
|
||||
packages/app-mobile/services/AlarmServiceDriver.ios.js
|
||||
packages/app-mobile/services/AlarmServiceDriver.ios.js.map
|
||||
packages/app-mobile/setUpQuickActions.d.ts
|
||||
packages/app-mobile/setUpQuickActions.js
|
||||
packages/app-mobile/setUpQuickActions.js.map
|
||||
packages/app-mobile/setupQuickActions.d.ts
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
packages/app-mobile/setupQuickActions.js.map
|
||||
packages/app-mobile/utils/ShareExtension.d.ts
|
||||
packages/app-mobile/utils/ShareExtension.js
|
||||
packages/app-mobile/utils/ShareExtension.js.map
|
||||
@@ -800,6 +734,9 @@ packages/app-mobile/utils/checkPermissions.js.map
|
||||
packages/app-mobile/utils/fs-driver-rn.d.ts
|
||||
packages/app-mobile/utils/fs-driver-rn.js
|
||||
packages/app-mobile/utils/fs-driver-rn.js.map
|
||||
packages/app-mobile/utils/setupNotifications.d.ts
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/setupNotifications.js.map
|
||||
packages/app-mobile/utils/shareHandler.d.ts
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
packages/app-mobile/utils/shareHandler.js.map
|
||||
@@ -875,6 +812,9 @@ packages/lib/HtmlToMd.js.map
|
||||
packages/lib/InMemoryCache.d.ts
|
||||
packages/lib/InMemoryCache.js
|
||||
packages/lib/InMemoryCache.js.map
|
||||
packages/lib/InMemoryCache.test.d.ts
|
||||
packages/lib/InMemoryCache.test.js
|
||||
packages/lib/InMemoryCache.test.js.map
|
||||
packages/lib/JoplinDatabase.d.ts
|
||||
packages/lib/JoplinDatabase.js
|
||||
packages/lib/JoplinDatabase.js.map
|
||||
@@ -890,6 +830,9 @@ packages/lib/Logger.js.map
|
||||
packages/lib/PoorManIntervals.d.ts
|
||||
packages/lib/PoorManIntervals.js
|
||||
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.js
|
||||
packages/lib/SyncTargetJoplinServer.js.map
|
||||
@@ -926,6 +869,9 @@ packages/lib/eventManager.js.map
|
||||
packages/lib/file-api-driver-joplinServer.d.ts
|
||||
packages/lib/file-api-driver-joplinServer.js
|
||||
packages/lib/file-api-driver-joplinServer.js.map
|
||||
packages/lib/file-api-driver.test.d.ts
|
||||
packages/lib/file-api-driver.test.js
|
||||
packages/lib/file-api-driver.test.js.map
|
||||
packages/lib/file-api.d.ts
|
||||
packages/lib/file-api.js
|
||||
packages/lib/file-api.js.map
|
||||
@@ -935,12 +881,24 @@ packages/lib/fs-driver-base.js.map
|
||||
packages/lib/fs-driver-node.d.ts
|
||||
packages/lib/fs-driver-node.js
|
||||
packages/lib/fs-driver-node.js.map
|
||||
packages/lib/fsDriver.test.d.ts
|
||||
packages/lib/fsDriver.test.js
|
||||
packages/lib/fsDriver.test.js.map
|
||||
packages/lib/htmlUtils.d.ts
|
||||
packages/lib/htmlUtils.js
|
||||
packages/lib/htmlUtils.js.map
|
||||
packages/lib/htmlUtils.test.d.ts
|
||||
packages/lib/htmlUtils.test.js
|
||||
packages/lib/htmlUtils.test.js.map
|
||||
packages/lib/htmlUtils2.test.d.ts
|
||||
packages/lib/htmlUtils2.test.js
|
||||
packages/lib/htmlUtils2.test.js.map
|
||||
packages/lib/import-enex-md-gen.d.ts
|
||||
packages/lib/import-enex-md-gen.js
|
||||
packages/lib/import-enex-md-gen.js.map
|
||||
packages/lib/import-enex-md-gen.test.d.ts
|
||||
packages/lib/import-enex-md-gen.test.js
|
||||
packages/lib/import-enex-md-gen.test.js.map
|
||||
packages/lib/locale.d.ts
|
||||
packages/lib/locale.js
|
||||
packages/lib/locale.js.map
|
||||
@@ -950,6 +908,9 @@ packages/lib/markdownUtils.js.map
|
||||
packages/lib/markdownUtils.test.d.ts
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils.test.js.map
|
||||
packages/lib/markdownUtils2.test.d.ts
|
||||
packages/lib/markdownUtils2.test.js
|
||||
packages/lib/markdownUtils2.test.js.map
|
||||
packages/lib/markupLanguageUtils.d.ts
|
||||
packages/lib/markupLanguageUtils.js
|
||||
packages/lib/markupLanguageUtils.js.map
|
||||
@@ -962,6 +923,12 @@ packages/lib/models/BaseItem.js.map
|
||||
packages/lib/models/Folder.d.ts
|
||||
packages/lib/models/Folder.js
|
||||
packages/lib/models/Folder.js.map
|
||||
packages/lib/models/Folder.sharing.test.d.ts
|
||||
packages/lib/models/Folder.sharing.test.js
|
||||
packages/lib/models/Folder.sharing.test.js.map
|
||||
packages/lib/models/Folder.test.d.ts
|
||||
packages/lib/models/Folder.test.js
|
||||
packages/lib/models/Folder.test.js.map
|
||||
packages/lib/models/ItemChange.d.ts
|
||||
packages/lib/models/ItemChange.js
|
||||
packages/lib/models/ItemChange.js.map
|
||||
@@ -974,6 +941,9 @@ packages/lib/models/Migration.js.map
|
||||
packages/lib/models/Note.d.ts
|
||||
packages/lib/models/Note.js
|
||||
packages/lib/models/Note.js.map
|
||||
packages/lib/models/Note.test.d.ts
|
||||
packages/lib/models/Note.test.js
|
||||
packages/lib/models/Note.test.js.map
|
||||
packages/lib/models/NoteResource.d.ts
|
||||
packages/lib/models/NoteResource.js
|
||||
packages/lib/models/NoteResource.js.map
|
||||
@@ -995,15 +965,24 @@ packages/lib/models/Search.js.map
|
||||
packages/lib/models/Setting.d.ts
|
||||
packages/lib/models/Setting.js
|
||||
packages/lib/models/Setting.js.map
|
||||
packages/lib/models/Setting.test.d.ts
|
||||
packages/lib/models/Setting.test.js
|
||||
packages/lib/models/Setting.test.js.map
|
||||
packages/lib/models/SmartFilter.d.ts
|
||||
packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/SmartFilter.js.map
|
||||
packages/lib/models/Tag.d.ts
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/Tag.js.map
|
||||
packages/lib/models/dateTimeFormats.test.d.ts
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/dateTimeFormats.test.js.map
|
||||
packages/lib/models/settings/FileHandler.d.ts
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
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.js
|
||||
packages/lib/models/utils/paginatedFeed.js.map
|
||||
@@ -1028,6 +1007,9 @@ packages/lib/reducer.js.map
|
||||
packages/lib/registry.d.ts
|
||||
packages/lib/registry.js
|
||||
packages/lib/registry.js.map
|
||||
packages/lib/registry.test.d.ts
|
||||
packages/lib/registry.test.js
|
||||
packages/lib/registry.test.js.map
|
||||
packages/lib/services/AlarmService.d.ts
|
||||
packages/lib/services/AlarmService.js
|
||||
packages/lib/services/AlarmService.js.map
|
||||
@@ -1040,6 +1022,9 @@ packages/lib/services/BaseService.js.map
|
||||
packages/lib/services/CommandService.d.ts
|
||||
packages/lib/services/CommandService.js
|
||||
packages/lib/services/CommandService.js.map
|
||||
packages/lib/services/CommandService.test.d.ts
|
||||
packages/lib/services/CommandService.test.js
|
||||
packages/lib/services/CommandService.test.js.map
|
||||
packages/lib/services/DecryptionWorker.d.ts
|
||||
packages/lib/services/DecryptionWorker.js
|
||||
packages/lib/services/DecryptionWorker.js.map
|
||||
@@ -1088,6 +1073,9 @@ packages/lib/services/ResourceFetcher.js.map
|
||||
packages/lib/services/ResourceService.d.ts
|
||||
packages/lib/services/ResourceService.js
|
||||
packages/lib/services/ResourceService.js.map
|
||||
packages/lib/services/ResourceService.test.d.ts
|
||||
packages/lib/services/ResourceService.test.js
|
||||
packages/lib/services/ResourceService.test.js.map
|
||||
packages/lib/services/RevisionService.d.ts
|
||||
packages/lib/services/RevisionService.js
|
||||
packages/lib/services/RevisionService.js.map
|
||||
@@ -1100,6 +1088,9 @@ packages/lib/services/UndoRedoService.js.map
|
||||
packages/lib/services/WhenClause.d.ts
|
||||
packages/lib/services/WhenClause.js
|
||||
packages/lib/services/WhenClause.js.map
|
||||
packages/lib/services/WhenClause.test.d.ts
|
||||
packages/lib/services/WhenClause.test.js
|
||||
packages/lib/services/WhenClause.test.js.map
|
||||
packages/lib/services/commands/MenuUtils.d.ts
|
||||
packages/lib/services/commands/MenuUtils.js
|
||||
packages/lib/services/commands/MenuUtils.js.map
|
||||
@@ -1133,6 +1124,9 @@ packages/lib/services/debug/populateDatabase.js.map
|
||||
packages/lib/services/interop/InteropService.d.ts
|
||||
packages/lib/services/interop/InteropService.js
|
||||
packages/lib/services/interop/InteropService.js.map
|
||||
packages/lib/services/interop/InteropService.test.d.ts
|
||||
packages/lib/services/interop/InteropService.test.js
|
||||
packages/lib/services/interop/InteropService.test.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Base.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Base.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Base.js.map
|
||||
@@ -1142,6 +1136,9 @@ packages/lib/services/interop/InteropService_Exporter_Custom.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.test.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.test.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.test.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Jex.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Jex.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Jex.js.map
|
||||
@@ -1175,6 +1172,9 @@ packages/lib/services/interop/InteropService_Importer_Raw.js.map
|
||||
packages/lib/services/interop/types.d.ts
|
||||
packages/lib/services/interop/types.js
|
||||
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.js
|
||||
packages/lib/services/keychain/KeychainService.js.map
|
||||
@@ -1304,6 +1304,9 @@ packages/lib/services/plugins/utils/validatePluginId.test.js.map
|
||||
packages/lib/services/rest/Api.d.ts
|
||||
packages/lib/services/rest/Api.js
|
||||
packages/lib/services/rest/Api.js.map
|
||||
packages/lib/services/rest/Api.test.d.ts
|
||||
packages/lib/services/rest/Api.test.js
|
||||
packages/lib/services/rest/Api.test.js.map
|
||||
packages/lib/services/rest/ApiResponse.d.ts
|
||||
packages/lib/services/rest/ApiResponse.js
|
||||
packages/lib/services/rest/ApiResponse.js.map
|
||||
@@ -1364,6 +1367,9 @@ packages/lib/services/searchengine/SearchEngine.js.map
|
||||
packages/lib/services/searchengine/SearchEngineUtils.d.ts
|
||||
packages/lib/services/searchengine/SearchEngineUtils.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.js.map
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.d.ts
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.js.map
|
||||
packages/lib/services/searchengine/filterParser.d.ts
|
||||
packages/lib/services/searchengine/filterParser.js
|
||||
packages/lib/services/searchengine/filterParser.js.map
|
||||
@@ -1388,6 +1394,30 @@ packages/lib/services/synchronizer/LockHandler.js.map
|
||||
packages/lib/services/synchronizer/MigrationHandler.d.ts
|
||||
packages/lib/services/synchronizer/MigrationHandler.js
|
||||
packages/lib/services/synchronizer/MigrationHandler.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.basics.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.conflicts.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.conflicts.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.conflicts.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.e2ee.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.e2ee.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.e2ee.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.resources.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.resources.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.resources.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.revisions.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.revisions.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.revisions.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.sharing.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.sharing.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.sharing.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js.map
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.d.ts
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js.map
|
||||
@@ -1397,6 +1427,12 @@ packages/lib/services/synchronizer/migrations/1.js.map
|
||||
packages/lib/services/synchronizer/migrations/2.d.ts
|
||||
packages/lib/services/synchronizer/migrations/2.js
|
||||
packages/lib/services/synchronizer/migrations/2.js.map
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.d.ts
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js.map
|
||||
packages/lib/services/synchronizer/tools.d.ts
|
||||
packages/lib/services/synchronizer/tools.js
|
||||
packages/lib/services/synchronizer/tools.js.map
|
||||
@@ -1409,6 +1445,12 @@ packages/lib/services/synchronizer/utils/types.js.map
|
||||
packages/lib/shim.d.ts
|
||||
packages/lib/shim.js
|
||||
packages/lib/shim.js.map
|
||||
packages/lib/testing/test-utils-synchronizer.d.ts
|
||||
packages/lib/testing/test-utils-synchronizer.js
|
||||
packages/lib/testing/test-utils-synchronizer.js.map
|
||||
packages/lib/testing/test-utils.d.ts
|
||||
packages/lib/testing/test-utils.js
|
||||
packages/lib/testing/test-utils.js.map
|
||||
packages/lib/theme.d.ts
|
||||
packages/lib/theme.js
|
||||
packages/lib/theme.js.map
|
||||
@@ -1442,12 +1484,18 @@ packages/lib/themes/type.js.map
|
||||
packages/lib/time.d.ts
|
||||
packages/lib/time.js
|
||||
packages/lib/time.js.map
|
||||
packages/lib/utils/credentialFiles.d.ts
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/credentialFiles.js.map
|
||||
packages/lib/uuid.d.ts
|
||||
packages/lib/uuid.js
|
||||
packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
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.js
|
||||
packages/plugin-repo-cli/index.js.map
|
||||
@@ -1562,12 +1610,18 @@ packages/renderer/pathUtils.js.map
|
||||
packages/renderer/utils.d.ts
|
||||
packages/renderer/utils.js
|
||||
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.js
|
||||
packages/tools/generate-database-types.js.map
|
||||
packages/tools/lerna-add.d.ts
|
||||
packages/tools/lerna-add.js
|
||||
packages/tools/lerna-add.js.map
|
||||
packages/tools/release-android.d.ts
|
||||
packages/tools/release-android.js
|
||||
packages/tools/release-android.js.map
|
||||
packages/tools/release-cli.d.ts
|
||||
packages/tools/release-cli.js
|
||||
packages/tools/release-cli.js.map
|
||||
@@ -1583,4 +1637,7 @@ packages/tools/release-server.js.map
|
||||
packages/tools/tool-utils.d.ts
|
||||
packages/tools/tool-utils.js
|
||||
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
|
||||
|
@@ -76,7 +76,7 @@ module.exports = {
|
||||
|
||||
// Warn only for now because fixing everything would take too much
|
||||
// refactoring, but new code should try to stick to it.
|
||||
'complexity': ['warn', { max: 10 }],
|
||||
// 'complexity': ['warn', { max: 10 }],
|
||||
|
||||
// Checks rules of Hooks
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
|
134
.github/scripts/run_ci.sh
vendored
Executable file
134
.github/scripts/run_ci.sh
vendored
Executable 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
|
63
.github/workflows/github-actions-main.yml
vendored
Normal file
63
.github/workflows/github-actions-main.yml
vendored
Normal 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"
|
249
.gitignore
vendored
249
.gitignore
vendored
@@ -60,69 +60,21 @@ packages/app-cli/app/command-settingschema.js.map
|
||||
packages/app-cli/app/services/plugins/PluginRunner.d.ts
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js.map
|
||||
packages/app-cli/tests/EnexToMd.d.ts
|
||||
packages/app-cli/tests/EnexToMd.js
|
||||
packages/app-cli/tests/EnexToMd.js.map
|
||||
packages/app-cli/tests/HtmlToMd.d.ts
|
||||
packages/app-cli/tests/HtmlToMd.js
|
||||
packages/app-cli/tests/HtmlToMd.js.map
|
||||
packages/app-cli/tests/InMemoryCache.d.ts
|
||||
packages/app-cli/tests/InMemoryCache.js
|
||||
packages/app-cli/tests/InMemoryCache.js.map
|
||||
packages/app-cli/tests/MdToHtml.d.ts
|
||||
packages/app-cli/tests/MdToHtml.js
|
||||
packages/app-cli/tests/MdToHtml.js.map
|
||||
packages/app-cli/tests/Synchronizer.basics.d.ts
|
||||
packages/app-cli/tests/Synchronizer.basics.js
|
||||
packages/app-cli/tests/Synchronizer.basics.js.map
|
||||
packages/app-cli/tests/Synchronizer.conflicts.d.ts
|
||||
packages/app-cli/tests/Synchronizer.conflicts.js
|
||||
packages/app-cli/tests/Synchronizer.conflicts.js.map
|
||||
packages/app-cli/tests/Synchronizer.e2ee.d.ts
|
||||
packages/app-cli/tests/Synchronizer.e2ee.js
|
||||
packages/app-cli/tests/Synchronizer.e2ee.js.map
|
||||
packages/app-cli/tests/Synchronizer.resources.d.ts
|
||||
packages/app-cli/tests/Synchronizer.resources.js
|
||||
packages/app-cli/tests/Synchronizer.resources.js.map
|
||||
packages/app-cli/tests/Synchronizer.revisions.d.ts
|
||||
packages/app-cli/tests/Synchronizer.revisions.js
|
||||
packages/app-cli/tests/Synchronizer.revisions.js.map
|
||||
packages/app-cli/tests/Synchronizer.sharing.d.ts
|
||||
packages/app-cli/tests/Synchronizer.sharing.js
|
||||
packages/app-cli/tests/Synchronizer.sharing.js.map
|
||||
packages/app-cli/tests/Synchronizer.tags.d.ts
|
||||
packages/app-cli/tests/Synchronizer.tags.js
|
||||
packages/app-cli/tests/Synchronizer.tags.js.map
|
||||
packages/app-cli/tests/Synchronizer.tools.d.ts
|
||||
packages/app-cli/tests/Synchronizer.tools.js
|
||||
packages/app-cli/tests/Synchronizer.tools.js.map
|
||||
packages/app-cli/tests/dateTimeFormats.d.ts
|
||||
packages/app-cli/tests/dateTimeFormats.js
|
||||
packages/app-cli/tests/dateTimeFormats.js.map
|
||||
packages/app-cli/tests/file-api-driver.d.ts
|
||||
packages/app-cli/tests/file-api-driver.js
|
||||
packages/app-cli/tests/file-api-driver.js.map
|
||||
packages/app-cli/tests/fsDriver.d.ts
|
||||
packages/app-cli/tests/fsDriver.js
|
||||
packages/app-cli/tests/fsDriver.js.map
|
||||
packages/app-cli/tests/htmlUtils.d.ts
|
||||
packages/app-cli/tests/htmlUtils.js
|
||||
packages/app-cli/tests/htmlUtils.js.map
|
||||
packages/app-cli/tests/models_Folder.d.ts
|
||||
packages/app-cli/tests/models_Folder.js
|
||||
packages/app-cli/tests/models_Folder.js.map
|
||||
packages/app-cli/tests/models_Folder.sharing.d.ts
|
||||
packages/app-cli/tests/models_Folder.sharing.js
|
||||
packages/app-cli/tests/models_Folder.sharing.js.map
|
||||
packages/app-cli/tests/models_Note.d.ts
|
||||
packages/app-cli/tests/models_Note.js
|
||||
packages/app-cli/tests/models_Note.js.map
|
||||
packages/app-cli/tests/models_Setting.d.ts
|
||||
packages/app-cli/tests/models_Setting.js
|
||||
packages/app-cli/tests/models_Setting.js.map
|
||||
packages/app-cli/tests/registry.d.ts
|
||||
packages/app-cli/tests/registry.js
|
||||
packages/app-cli/tests/registry.js.map
|
||||
packages/app-cli/tests/MdToMd.d.ts
|
||||
packages/app-cli/tests/MdToMd.js
|
||||
packages/app-cli/tests/MdToMd.js.map
|
||||
packages/app-cli/tests/services/keychain/KeychainService.d.ts
|
||||
packages/app-cli/tests/services/keychain/KeychainService.js
|
||||
packages/app-cli/tests/services/keychain/KeychainService.js.map
|
||||
packages/app-cli/tests/services/plugins/PluginService.d.ts
|
||||
packages/app-cli/tests/services/plugins/PluginService.js
|
||||
packages/app-cli/tests/services/plugins/PluginService.js.map
|
||||
packages/app-cli/tests/services/plugins/RepositoryApi.d.ts
|
||||
packages/app-cli/tests/services/plugins/RepositoryApi.js
|
||||
packages/app-cli/tests/services/plugins/RepositoryApi.js.map
|
||||
@@ -138,42 +90,9 @@ packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js
|
||||
packages/app-cli/tests/services/plugins/sandboxProxy.js.map
|
||||
packages/app-cli/tests/services_CommandService.d.ts
|
||||
packages/app-cli/tests/services_CommandService.js
|
||||
packages/app-cli/tests/services_CommandService.js.map
|
||||
packages/app-cli/tests/services_InteropService.d.ts
|
||||
packages/app-cli/tests/services_InteropService.js
|
||||
packages/app-cli/tests/services_InteropService.js.map
|
||||
packages/app-cli/tests/services_InteropService_Exporter_Html.d.ts
|
||||
packages/app-cli/tests/services_InteropService_Exporter_Html.js
|
||||
packages/app-cli/tests/services_InteropService_Exporter_Html.js.map
|
||||
packages/app-cli/tests/services_PluginService.d.ts
|
||||
packages/app-cli/tests/services_PluginService.js
|
||||
packages/app-cli/tests/services_PluginService.js.map
|
||||
packages/app-cli/tests/services_ResourceService.d.ts
|
||||
packages/app-cli/tests/services_ResourceService.js
|
||||
packages/app-cli/tests/services_ResourceService.js.map
|
||||
packages/app-cli/tests/services_SearchEngineUtils.d.ts
|
||||
packages/app-cli/tests/services_SearchEngineUtils.js
|
||||
packages/app-cli/tests/services_SearchEngineUtils.js.map
|
||||
packages/app-cli/tests/services_keychainService.d.ts
|
||||
packages/app-cli/tests/services_keychainService.js
|
||||
packages/app-cli/tests/services_keychainService.js.map
|
||||
packages/app-cli/tests/services_rest_Api.d.ts
|
||||
packages/app-cli/tests/services_rest_Api.js
|
||||
packages/app-cli/tests/services_rest_Api.js.map
|
||||
packages/app-cli/tests/synchronizer_LockHandler.d.ts
|
||||
packages/app-cli/tests/synchronizer_LockHandler.js
|
||||
packages/app-cli/tests/synchronizer_LockHandler.js.map
|
||||
packages/app-cli/tests/synchronizer_MigrationHandler.d.ts
|
||||
packages/app-cli/tests/synchronizer_MigrationHandler.js
|
||||
packages/app-cli/tests/synchronizer_MigrationHandler.js.map
|
||||
packages/app-cli/tests/test-utils-synchronizer.d.ts
|
||||
packages/app-cli/tests/test-utils-synchronizer.js
|
||||
packages/app-cli/tests/test-utils-synchronizer.js.map
|
||||
packages/app-cli/tests/test-utils.d.ts
|
||||
packages/app-cli/tests/test-utils.js
|
||||
packages/app-cli/tests/test-utils.js.map
|
||||
packages/app-cli/tests/testUtils.d.ts
|
||||
packages/app-cli/tests/testUtils.js
|
||||
packages/app-cli/tests/testUtils.js.map
|
||||
packages/app-desktop/ElectronAppWrapper.d.ts
|
||||
packages/app-desktop/ElectronAppWrapper.js
|
||||
packages/app-desktop/ElectronAppWrapper.js.map
|
||||
@@ -207,6 +126,9 @@ packages/app-desktop/commands/openProfileDirectory.js.map
|
||||
packages/app-desktop/commands/replaceMisspelling.d.ts
|
||||
packages/app-desktop/commands/replaceMisspelling.js
|
||||
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.js
|
||||
packages/app-desktop/commands/startExternalEditing.js.map
|
||||
@@ -342,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.js
|
||||
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.js
|
||||
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
|
||||
@@ -405,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.js
|
||||
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.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map
|
||||
@@ -564,6 +492,9 @@ packages/app-desktop/gui/ResizableLayout/utils/persist.test.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeItem.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/removeKeylessItems.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js
|
||||
packages/app-desktop/gui/ResizableLayout/utils/setLayoutItemProps.js.map
|
||||
@@ -663,6 +594,9 @@ packages/app-desktop/gui/style/StyledFormLabel.js.map
|
||||
packages/app-desktop/gui/style/StyledInput.d.ts
|
||||
packages/app-desktop/gui/style/StyledInput.js
|
||||
packages/app-desktop/gui/style/StyledInput.js.map
|
||||
packages/app-desktop/gui/style/StyledLink.d.ts
|
||||
packages/app-desktop/gui/style/StyledLink.js
|
||||
packages/app-desktop/gui/style/StyledLink.js.map
|
||||
packages/app-desktop/gui/style/StyledMessage.d.ts
|
||||
packages/app-desktop/gui/style/StyledMessage.js
|
||||
packages/app-desktop/gui/style/StyledMessage.js.map
|
||||
@@ -768,9 +702,9 @@ packages/app-mobile/services/AlarmServiceDriver.android.js.map
|
||||
packages/app-mobile/services/AlarmServiceDriver.ios.d.ts
|
||||
packages/app-mobile/services/AlarmServiceDriver.ios.js
|
||||
packages/app-mobile/services/AlarmServiceDriver.ios.js.map
|
||||
packages/app-mobile/setUpQuickActions.d.ts
|
||||
packages/app-mobile/setUpQuickActions.js
|
||||
packages/app-mobile/setUpQuickActions.js.map
|
||||
packages/app-mobile/setupQuickActions.d.ts
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
packages/app-mobile/setupQuickActions.js.map
|
||||
packages/app-mobile/utils/ShareExtension.d.ts
|
||||
packages/app-mobile/utils/ShareExtension.js
|
||||
packages/app-mobile/utils/ShareExtension.js.map
|
||||
@@ -786,6 +720,9 @@ packages/app-mobile/utils/checkPermissions.js.map
|
||||
packages/app-mobile/utils/fs-driver-rn.d.ts
|
||||
packages/app-mobile/utils/fs-driver-rn.js
|
||||
packages/app-mobile/utils/fs-driver-rn.js.map
|
||||
packages/app-mobile/utils/setupNotifications.d.ts
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/setupNotifications.js.map
|
||||
packages/app-mobile/utils/shareHandler.d.ts
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
packages/app-mobile/utils/shareHandler.js.map
|
||||
@@ -861,6 +798,9 @@ packages/lib/HtmlToMd.js.map
|
||||
packages/lib/InMemoryCache.d.ts
|
||||
packages/lib/InMemoryCache.js
|
||||
packages/lib/InMemoryCache.js.map
|
||||
packages/lib/InMemoryCache.test.d.ts
|
||||
packages/lib/InMemoryCache.test.js
|
||||
packages/lib/InMemoryCache.test.js.map
|
||||
packages/lib/JoplinDatabase.d.ts
|
||||
packages/lib/JoplinDatabase.js
|
||||
packages/lib/JoplinDatabase.js.map
|
||||
@@ -876,6 +816,9 @@ packages/lib/Logger.js.map
|
||||
packages/lib/PoorManIntervals.d.ts
|
||||
packages/lib/PoorManIntervals.js
|
||||
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.js
|
||||
packages/lib/SyncTargetJoplinServer.js.map
|
||||
@@ -912,6 +855,9 @@ packages/lib/eventManager.js.map
|
||||
packages/lib/file-api-driver-joplinServer.d.ts
|
||||
packages/lib/file-api-driver-joplinServer.js
|
||||
packages/lib/file-api-driver-joplinServer.js.map
|
||||
packages/lib/file-api-driver.test.d.ts
|
||||
packages/lib/file-api-driver.test.js
|
||||
packages/lib/file-api-driver.test.js.map
|
||||
packages/lib/file-api.d.ts
|
||||
packages/lib/file-api.js
|
||||
packages/lib/file-api.js.map
|
||||
@@ -921,12 +867,24 @@ packages/lib/fs-driver-base.js.map
|
||||
packages/lib/fs-driver-node.d.ts
|
||||
packages/lib/fs-driver-node.js
|
||||
packages/lib/fs-driver-node.js.map
|
||||
packages/lib/fsDriver.test.d.ts
|
||||
packages/lib/fsDriver.test.js
|
||||
packages/lib/fsDriver.test.js.map
|
||||
packages/lib/htmlUtils.d.ts
|
||||
packages/lib/htmlUtils.js
|
||||
packages/lib/htmlUtils.js.map
|
||||
packages/lib/htmlUtils.test.d.ts
|
||||
packages/lib/htmlUtils.test.js
|
||||
packages/lib/htmlUtils.test.js.map
|
||||
packages/lib/htmlUtils2.test.d.ts
|
||||
packages/lib/htmlUtils2.test.js
|
||||
packages/lib/htmlUtils2.test.js.map
|
||||
packages/lib/import-enex-md-gen.d.ts
|
||||
packages/lib/import-enex-md-gen.js
|
||||
packages/lib/import-enex-md-gen.js.map
|
||||
packages/lib/import-enex-md-gen.test.d.ts
|
||||
packages/lib/import-enex-md-gen.test.js
|
||||
packages/lib/import-enex-md-gen.test.js.map
|
||||
packages/lib/locale.d.ts
|
||||
packages/lib/locale.js
|
||||
packages/lib/locale.js.map
|
||||
@@ -936,6 +894,9 @@ packages/lib/markdownUtils.js.map
|
||||
packages/lib/markdownUtils.test.d.ts
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils.test.js.map
|
||||
packages/lib/markdownUtils2.test.d.ts
|
||||
packages/lib/markdownUtils2.test.js
|
||||
packages/lib/markdownUtils2.test.js.map
|
||||
packages/lib/markupLanguageUtils.d.ts
|
||||
packages/lib/markupLanguageUtils.js
|
||||
packages/lib/markupLanguageUtils.js.map
|
||||
@@ -948,6 +909,12 @@ packages/lib/models/BaseItem.js.map
|
||||
packages/lib/models/Folder.d.ts
|
||||
packages/lib/models/Folder.js
|
||||
packages/lib/models/Folder.js.map
|
||||
packages/lib/models/Folder.sharing.test.d.ts
|
||||
packages/lib/models/Folder.sharing.test.js
|
||||
packages/lib/models/Folder.sharing.test.js.map
|
||||
packages/lib/models/Folder.test.d.ts
|
||||
packages/lib/models/Folder.test.js
|
||||
packages/lib/models/Folder.test.js.map
|
||||
packages/lib/models/ItemChange.d.ts
|
||||
packages/lib/models/ItemChange.js
|
||||
packages/lib/models/ItemChange.js.map
|
||||
@@ -960,6 +927,9 @@ packages/lib/models/Migration.js.map
|
||||
packages/lib/models/Note.d.ts
|
||||
packages/lib/models/Note.js
|
||||
packages/lib/models/Note.js.map
|
||||
packages/lib/models/Note.test.d.ts
|
||||
packages/lib/models/Note.test.js
|
||||
packages/lib/models/Note.test.js.map
|
||||
packages/lib/models/NoteResource.d.ts
|
||||
packages/lib/models/NoteResource.js
|
||||
packages/lib/models/NoteResource.js.map
|
||||
@@ -981,15 +951,24 @@ packages/lib/models/Search.js.map
|
||||
packages/lib/models/Setting.d.ts
|
||||
packages/lib/models/Setting.js
|
||||
packages/lib/models/Setting.js.map
|
||||
packages/lib/models/Setting.test.d.ts
|
||||
packages/lib/models/Setting.test.js
|
||||
packages/lib/models/Setting.test.js.map
|
||||
packages/lib/models/SmartFilter.d.ts
|
||||
packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/SmartFilter.js.map
|
||||
packages/lib/models/Tag.d.ts
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/Tag.js.map
|
||||
packages/lib/models/dateTimeFormats.test.d.ts
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/dateTimeFormats.test.js.map
|
||||
packages/lib/models/settings/FileHandler.d.ts
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
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.js
|
||||
packages/lib/models/utils/paginatedFeed.js.map
|
||||
@@ -1014,6 +993,9 @@ packages/lib/reducer.js.map
|
||||
packages/lib/registry.d.ts
|
||||
packages/lib/registry.js
|
||||
packages/lib/registry.js.map
|
||||
packages/lib/registry.test.d.ts
|
||||
packages/lib/registry.test.js
|
||||
packages/lib/registry.test.js.map
|
||||
packages/lib/services/AlarmService.d.ts
|
||||
packages/lib/services/AlarmService.js
|
||||
packages/lib/services/AlarmService.js.map
|
||||
@@ -1026,6 +1008,9 @@ packages/lib/services/BaseService.js.map
|
||||
packages/lib/services/CommandService.d.ts
|
||||
packages/lib/services/CommandService.js
|
||||
packages/lib/services/CommandService.js.map
|
||||
packages/lib/services/CommandService.test.d.ts
|
||||
packages/lib/services/CommandService.test.js
|
||||
packages/lib/services/CommandService.test.js.map
|
||||
packages/lib/services/DecryptionWorker.d.ts
|
||||
packages/lib/services/DecryptionWorker.js
|
||||
packages/lib/services/DecryptionWorker.js.map
|
||||
@@ -1074,6 +1059,9 @@ packages/lib/services/ResourceFetcher.js.map
|
||||
packages/lib/services/ResourceService.d.ts
|
||||
packages/lib/services/ResourceService.js
|
||||
packages/lib/services/ResourceService.js.map
|
||||
packages/lib/services/ResourceService.test.d.ts
|
||||
packages/lib/services/ResourceService.test.js
|
||||
packages/lib/services/ResourceService.test.js.map
|
||||
packages/lib/services/RevisionService.d.ts
|
||||
packages/lib/services/RevisionService.js
|
||||
packages/lib/services/RevisionService.js.map
|
||||
@@ -1086,6 +1074,9 @@ packages/lib/services/UndoRedoService.js.map
|
||||
packages/lib/services/WhenClause.d.ts
|
||||
packages/lib/services/WhenClause.js
|
||||
packages/lib/services/WhenClause.js.map
|
||||
packages/lib/services/WhenClause.test.d.ts
|
||||
packages/lib/services/WhenClause.test.js
|
||||
packages/lib/services/WhenClause.test.js.map
|
||||
packages/lib/services/commands/MenuUtils.d.ts
|
||||
packages/lib/services/commands/MenuUtils.js
|
||||
packages/lib/services/commands/MenuUtils.js.map
|
||||
@@ -1119,6 +1110,9 @@ packages/lib/services/debug/populateDatabase.js.map
|
||||
packages/lib/services/interop/InteropService.d.ts
|
||||
packages/lib/services/interop/InteropService.js
|
||||
packages/lib/services/interop/InteropService.js.map
|
||||
packages/lib/services/interop/InteropService.test.d.ts
|
||||
packages/lib/services/interop/InteropService.test.js
|
||||
packages/lib/services/interop/InteropService.test.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Base.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Base.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Base.js.map
|
||||
@@ -1128,6 +1122,9 @@ packages/lib/services/interop/InteropService_Exporter_Custom.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.test.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.test.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Html.test.js.map
|
||||
packages/lib/services/interop/InteropService_Exporter_Jex.d.ts
|
||||
packages/lib/services/interop/InteropService_Exporter_Jex.js
|
||||
packages/lib/services/interop/InteropService_Exporter_Jex.js.map
|
||||
@@ -1161,6 +1158,9 @@ packages/lib/services/interop/InteropService_Importer_Raw.js.map
|
||||
packages/lib/services/interop/types.d.ts
|
||||
packages/lib/services/interop/types.js
|
||||
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.js
|
||||
packages/lib/services/keychain/KeychainService.js.map
|
||||
@@ -1290,6 +1290,9 @@ packages/lib/services/plugins/utils/validatePluginId.test.js.map
|
||||
packages/lib/services/rest/Api.d.ts
|
||||
packages/lib/services/rest/Api.js
|
||||
packages/lib/services/rest/Api.js.map
|
||||
packages/lib/services/rest/Api.test.d.ts
|
||||
packages/lib/services/rest/Api.test.js
|
||||
packages/lib/services/rest/Api.test.js.map
|
||||
packages/lib/services/rest/ApiResponse.d.ts
|
||||
packages/lib/services/rest/ApiResponse.js
|
||||
packages/lib/services/rest/ApiResponse.js.map
|
||||
@@ -1350,6 +1353,9 @@ packages/lib/services/searchengine/SearchEngine.js.map
|
||||
packages/lib/services/searchengine/SearchEngineUtils.d.ts
|
||||
packages/lib/services/searchengine/SearchEngineUtils.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.js.map
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.d.ts
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.js
|
||||
packages/lib/services/searchengine/SearchEngineUtils.test.js.map
|
||||
packages/lib/services/searchengine/filterParser.d.ts
|
||||
packages/lib/services/searchengine/filterParser.js
|
||||
packages/lib/services/searchengine/filterParser.js.map
|
||||
@@ -1374,6 +1380,30 @@ packages/lib/services/synchronizer/LockHandler.js.map
|
||||
packages/lib/services/synchronizer/MigrationHandler.d.ts
|
||||
packages/lib/services/synchronizer/MigrationHandler.js
|
||||
packages/lib/services/synchronizer/MigrationHandler.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.basics.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.basics.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.conflicts.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.conflicts.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.conflicts.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.e2ee.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.e2ee.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.e2ee.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.resources.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.resources.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.resources.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.revisions.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.revisions.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.revisions.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.sharing.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.sharing.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.sharing.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js.map
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.d.ts
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js.map
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.d.ts
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js.map
|
||||
@@ -1383,6 +1413,12 @@ packages/lib/services/synchronizer/migrations/1.js.map
|
||||
packages/lib/services/synchronizer/migrations/2.d.ts
|
||||
packages/lib/services/synchronizer/migrations/2.js
|
||||
packages/lib/services/synchronizer/migrations/2.js.map
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.d.ts
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js.map
|
||||
packages/lib/services/synchronizer/tools.d.ts
|
||||
packages/lib/services/synchronizer/tools.js
|
||||
packages/lib/services/synchronizer/tools.js.map
|
||||
@@ -1395,6 +1431,12 @@ packages/lib/services/synchronizer/utils/types.js.map
|
||||
packages/lib/shim.d.ts
|
||||
packages/lib/shim.js
|
||||
packages/lib/shim.js.map
|
||||
packages/lib/testing/test-utils-synchronizer.d.ts
|
||||
packages/lib/testing/test-utils-synchronizer.js
|
||||
packages/lib/testing/test-utils-synchronizer.js.map
|
||||
packages/lib/testing/test-utils.d.ts
|
||||
packages/lib/testing/test-utils.js
|
||||
packages/lib/testing/test-utils.js.map
|
||||
packages/lib/theme.d.ts
|
||||
packages/lib/theme.js
|
||||
packages/lib/theme.js.map
|
||||
@@ -1428,12 +1470,18 @@ packages/lib/themes/type.js.map
|
||||
packages/lib/time.d.ts
|
||||
packages/lib/time.js
|
||||
packages/lib/time.js.map
|
||||
packages/lib/utils/credentialFiles.d.ts
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/credentialFiles.js.map
|
||||
packages/lib/uuid.d.ts
|
||||
packages/lib/uuid.js
|
||||
packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
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.js
|
||||
packages/plugin-repo-cli/index.js.map
|
||||
@@ -1548,12 +1596,18 @@ packages/renderer/pathUtils.js.map
|
||||
packages/renderer/utils.d.ts
|
||||
packages/renderer/utils.js
|
||||
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.js
|
||||
packages/tools/generate-database-types.js.map
|
||||
packages/tools/lerna-add.d.ts
|
||||
packages/tools/lerna-add.js
|
||||
packages/tools/lerna-add.js.map
|
||||
packages/tools/release-android.d.ts
|
||||
packages/tools/release-android.js
|
||||
packages/tools/release-android.js.map
|
||||
packages/tools/release-cli.d.ts
|
||||
packages/tools/release-cli.js
|
||||
packages/tools/release-cli.js.map
|
||||
@@ -1569,4 +1623,7 @@ packages/tools/release-server.js.map
|
||||
packages/tools/tool-utils.d.ts
|
||||
packages/tools/tool-utils.js
|
||||
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
|
||||
|
138
.travis.yml
138
.travis.yml
@@ -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
|
BIN
Assets/WebsiteAssets/images/sponsors/Tranio.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/Tranio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
4
BUILD.md
4
BUILD.md
@@ -1,5 +1,3 @@
|
||||
[](https://travis-ci.org/laurent22/joplin) [](https://ci.appveyor.com/project/laurent22/joplin)
|
||||
|
||||
# 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.
|
||||
@@ -64,7 +62,7 @@ Normally the **bundler** should start automatically with the application. If it
|
||||
npm install
|
||||
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
|
||||
|
||||
|
234
README.md
234
README.md
@@ -36,7 +36,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v1.8.5/Jo
|
||||
|
||||
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> | -
|
||||
|
||||
## Terminal application
|
||||
@@ -64,17 +64,19 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
|
||||
|
||||
# Sponsors
|
||||
|
||||
<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://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>
|
||||
|
||||
* * *
|
||||
|
||||
| | | |
|
||||
| :---: | :---: | :---: |
|
||||
| <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/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://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://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)
|
||||
<!-- SPONSORS -->
|
||||
| | | | |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| <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/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://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://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 -->
|
||||
# Table of contents
|
||||
@@ -292,7 +294,7 @@ To add a **Bucket Policy** from the AWS S3 Web Console, navigate to the **Permis
|
||||
"s3:DeleteObject",
|
||||
"s3:DeleteObjectVersion",
|
||||
"s3:PutObject"
|
||||
]
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::joplin-bucket",
|
||||
"arn:aws:s3:::joplin-bucket/*"
|
||||
@@ -342,7 +344,7 @@ In the desktop and mobile apps, an alarm can be associated with any to-do. It wi
|
||||
|
||||
- **Windows**: >= 8. Make sure the Action Center is enabled on Windows. Task bar balloon for Windows < 8. Growl as fallback. Growl takes precedence over Windows balloons.
|
||||
- **macOS**: >= 10.8 or Growl if earlier.
|
||||
- **Linux**: `notify-osd` or `libnotify-bin` installed (Ubuntu should have this by default). Growl otherwise
|
||||
- **Linux**: `notify-send` tool, delivered through packages `notify-osd`, `libnotify-bin` or `libnotify-tools`. GNOME should have this by default, but install `libnotify-tools` if using KDE Plasma.
|
||||
|
||||
See [documentation and flow chart for reporter choice](https://github.com/mikaelbr/node-notifier/blob/master/DECISION_FLOW.md)
|
||||
|
||||
@@ -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:
|
||||
|
||||
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
|
||||
|
||||
Search type | Description | Example
|
||||
@@ -511,47 +519,47 @@ Current translations:
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
 | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 98%
|
||||
 | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 31%
|
||||
 | 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) | 76%
|
||||
 | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 59%
|
||||
 | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 84%
|
||||
 | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 99%
|
||||
 | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 88%
|
||||
 | 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: | 98%
|
||||
 | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 98%
|
||||
 | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 58%
|
||||
 | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||
 | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||
 | 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) | 97%
|
||||
 | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 33%
|
||||
 | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 97%
|
||||
 | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 94%
|
||||
 | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 39%
|
||||
 | 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) | 95%
|
||||
 | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 97%
|
||||
 | 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) | 90%
|
||||
 | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 94%
|
||||
 | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 97%
|
||||
 | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 78%
|
||||
 | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 73%
|
||||
 | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 97%
|
||||
 | 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) | 97%
|
||||
 | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 97%
|
||||
 | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 68%
|
||||
 | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 98%
|
||||
 | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 63%
|
||||
 | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 46%
|
||||
 | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 75%
|
||||
 | 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) | 97%
|
||||
 | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 97%
|
||||
 | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 84%
|
||||
 | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 97%
|
||||
 | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 73%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 97%
|
||||
 | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
|
||||
 | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 98%
|
||||
 | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 99%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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%
|
||||
<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 -->
|
||||
|
||||
# Contributors
|
||||
@@ -561,52 +569,80 @@ Thank you to everyone who've contributed to Joplin's source code!
|
||||
<!-- 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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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/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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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 -->
|
||||
|
||||
# Known bugs
|
||||
@@ -618,7 +654,7 @@ Thank you to everyone who've contributed to Joplin's source code!
|
||||
|
||||
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:
|
||||
|
||||
|
@@ -9,6 +9,8 @@ version: '3'
|
||||
services:
|
||||
db:
|
||||
image: postgres:13.1
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
restart: unless-stopped
|
||||
|
@@ -86,6 +86,28 @@
|
||||
</ul>
|
||||
<p>To view what arguments are supported, you can open any of these files
|
||||
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">'editor.execCommand'</span>, {
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'madeUpCommand'</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">''</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>
|
||||
</section>
|
||||
<!--
|
||||
|
@@ -108,11 +108,12 @@
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Defines whether the command should be enabled or disabled, which in turns affects
|
||||
the enabled state of any associated button or menu item.</p>
|
||||
<p>Defines whether the command should be enabled or disabled, which in turns
|
||||
affects the enabled state of any associated button or menu item.</p>
|
||||
</div>
|
||||
<p>The condition should be expressed as a "when-clause" (as in Visual Studio Code). It's a simple boolean expression that evaluates to
|
||||
<code>true</code> or <code>false</code>. It supports the following operators:</p>
|
||||
<p>The condition should be expressed as a "when-clause" (as in Visual Studio
|
||||
Code). It's a simple boolean expression that evaluates to <code>true</code> or
|
||||
<code>false</code>. It supports the following operators:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -142,7 +143,15 @@
|
||||
<td>"oneNoteSelected && !inConflictFolder"</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Currently the supported context variables aren't documented, but you can <a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">find the list here</a>.</p>
|
||||
<p>Joplin, unlike VSCode, also supports parenthesis, which allows creating
|
||||
more complex expressions such as <code>cond1 || (cond2 && cond3)</code>. Only one
|
||||
level of parenthesis is possible (nested ones aren't supported).</p>
|
||||
<p>Currently the supported context variables aren't documented, but you can
|
||||
find the list below:</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">Global When Clauses</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>
|
||||
</ul>
|
||||
<p>Note: Commands are enabled by default unless you use this property.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
@@ -721,6 +721,11 @@ async function fetchAllNotes() {
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>share_id</td>
|
||||
<td>text</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>body_html</td>
|
||||
<td>text</td>
|
||||
<td>Note body, in HTML format</td>
|
||||
@@ -841,6 +846,11 @@ async function fetchAllNotes() {
|
||||
<td>int</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>share_id</td>
|
||||
<td>text</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>GET /folders<a name="get-folders" href="#get-folders" class="heading-anchor">🔗</a></h2>
|
||||
@@ -937,6 +947,11 @@ async function fetchAllNotes() {
|
||||
<td>int</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>share_id</td>
|
||||
<td>text</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>GET /resources<a name="get-resources" href="#get-resources" class="heading-anchor">🔗</a></h2>
|
||||
|
@@ -405,6 +405,52 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog.md
|
||||
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&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>
|
||||
<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>
|
||||
<ul>
|
||||
<li>New: Add Share Notebook menu item (6f2f241)</li>
|
||||
<li>New: Add classnames to DOM elements for theming purposes (<a href="https://github.com/laurent22/joplin/issues/4933">#4933</a> by <a href="https://github.com/ajilderda">@ajilderda</a>)</li>
|
||||
<li>Improved: Allow unsharing a note (f7d164b)</li>
|
||||
<li>Improved: Displays error info when Joplin Server fails (3f0586e)</li>
|
||||
<li>Improved: Handle too large items for Joplin Server (d29624c)</li>
|
||||
<li>Improved: Import SVG as images when importing ENEX files (<a href="https://github.com/laurent22/joplin/issues/4968">#4968</a>)</li>
|
||||
<li>Improved: Import linked local files when importing Markdown files (<a href="https://github.com/laurent22/joplin/issues/4966">#4966</a>) (<a href="https://github.com/laurent22/joplin/issues/4433">#4433</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
|
||||
<li>Improved: Improved usability when plugin repository cannot be connected to (<a href="https://github.com/laurent22/joplin/issues/4462">#4462</a>)</li>
|
||||
<li>Improved: Made sync more reliable by making it skip items that time out, and improved sync status screen (15fe119)</li>
|
||||
<li>Improved: Pass custom CSS property to all export handlers and renderers (bd08041)</li>
|
||||
<li>Improved: Regression: It was no longer possible to add list items in an empty note (6577f4f)</li>
|
||||
<li>Improved: Regression: Pasting plain text in Rich Text editor was broken (9e9bf63)</li>
|
||||
<li>Fixed: Fixed issue with empty panels being created by plugins (<a href="https://github.com/laurent22/joplin/issues/4926">#4926</a>)</li>
|
||||
<li>Fixed: Fixed pasting HTML in Rich Text editor, and improved pasting plain text (2226b79)</li>
|
||||
<li>Fixed: Improved importing Evernote notes that contain codeblocks (<a href="https://github.com/laurent22/joplin/issues/4965">#4965</a>)</li>
|
||||
<li>Fixed: Prevent cursor from jumping to top of page when pasting image (<a href="https://github.com/laurent22/joplin/issues/4591">#4591</a>)</li>
|
||||
</ul>
|
||||
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.1">v2.0.1</a> (Pre-release) - 2021-05-15T13:22:58Z<a name="v2-0-1-https-github-com-laurent22-joplin-releases-tag-v2-0-1-pre-release-2021-05-15t13-22-58z" href="#v2-0-1-https-github-com-laurent22-joplin-releases-tag-v2-0-1-pre-release-2021-05-15t13-22-58z" class="heading-anchor">🔗</a></h2>
|
||||
<ul>
|
||||
<li>New: Add support for sharing notebooks with Joplin Server (<a href="https://github.com/laurent22/joplin/issues/4772">#4772</a>)</li>
|
||||
<li>New: Add new date format YYMMDD (<a href="https://github.com/laurent22/joplin/issues/4954">#4954</a> by Helmut K. C. Tessarek)</li>
|
||||
<li>New: Added button to skip an application update (a31b402)</li>
|
||||
<li>Fixed: Display proper error message when JEX file is corrupted (<a href="https://github.com/laurent22/joplin/issues/4958">#4958</a>)</li>
|
||||
<li>Fixed: Show or hide completed todos in search results based on user settings (<a href="https://github.com/laurent22/joplin/issues/4951">#4951</a>) (<a href="https://github.com/laurent22/joplin/issues/4581">#4581</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
|
||||
<li>Fixed: Solve "Resource Id not provided" error (<a href="https://github.com/laurent22/joplin/issues/4943">#4943</a>) (<a href="https://github.com/laurent22/joplin/issues/4891">#4891</a> by <a href="https://github.com/Subhra264">@Subhra264</a>)</li>
|
||||
</ul>
|
||||
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.5">v1.8.5</a> - 2021-05-10T11:58:14Z<a name="v1-8-5-https-github-com-laurent22-joplin-releases-tag-v1-8-5-2021-05-10t11-58-14z" href="#v1-8-5-https-github-com-laurent22-joplin-releases-tag-v1-8-5-2021-05-10t11-58-14z" class="heading-anchor">🔗</a></h2>
|
||||
<ul>
|
||||
<li>Fixed: Fixed pasting of text and images from Word on Windows (<a href="https://github.com/laurent22/joplin/issues/4916">#4916</a>)</li>
|
||||
<li>Security: Filter out NOSCRIPT tags that could be used to cause an XSS (found by <a href="https://twitter.com/jubairfolder">Jubair Rehman Yousafzai</a>) (9c20d59)</li>
|
||||
</ul>
|
||||
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.4">v1.8.4</a> (Pre-release) - 2021-05-09T18:05:05Z<a name="v1-8-4-https-github-com-laurent22-joplin-releases-tag-v1-8-4-pre-release-2021-05-09t18-05-05z" href="#v1-8-4-https-github-com-laurent22-joplin-releases-tag-v1-8-4-pre-release-2021-05-09t18-05-05z" class="heading-anchor">🔗</a></h2>
|
||||
<ul>
|
||||
<li>Improved: Improve display of release notes for new versions (f76f99b)</li>
|
||||
<li>Fixed: Ensure that image paths that contain spaces are pasted correctly in the Rich Text editor (<a href="https://github.com/laurent22/joplin/issues/4916">#4916</a>)</li>
|
||||
<li>Fixed: Make sure sync startup operations are cleared after startup (<a href="https://github.com/laurent22/joplin/issues/4919">#4919</a>)</li>
|
||||
<li>Security: Apply npm audit security fixes (0b67446)</li>
|
||||
</ul>
|
||||
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.3">v1.8.3</a> (Pre-release) - 2021-05-04T10:38:16Z<a name="v1-8-3-https-github-com-laurent22-joplin-releases-tag-v1-8-3-pre-release-2021-05-04t10-38-16z" href="#v1-8-3-https-github-com-laurent22-joplin-releases-tag-v1-8-3-pre-release-2021-05-04t10-38-16z" class="heading-anchor">🔗</a></h2>
|
||||
<ul>
|
||||
<li>New: Add "id" and "due" search filters (<a href="https://github.com/laurent22/joplin/issues/4898">#4898</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
|
||||
|
443
docs/changelog_android/index.html
Normal file
443
docs/changelog_android/index.html
Normal file
@@ -0,0 +1,443 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<!--
|
||||
|
||||
!!! WARNING !!!
|
||||
|
||||
This file was auto-generated from readme/changelog_android.md and any manual change
|
||||
made to it will be overwritten. To make a change to this file please modify
|
||||
the source Markdown file:
|
||||
|
||||
https://github.com/laurent22/joplin/blob/dev/readme/changelog_android.md
|
||||
|
||||
-->
|
||||
|
||||
<head>
|
||||
<title>Joplin Android app changelog | Joplin</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://joplinapp.org/css/bootstrap.min.css">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://joplinapp.org/favicon.ico">
|
||||
<!-- <link rel="stylesheet" href="https://joplinapp.org/css/fontawesome-all.min.css"> -->
|
||||
<link rel="stylesheet" href="https://joplinapp.org/css/fork-awesome.min.css">
|
||||
<script src="https://joplinapp.org/js/jquery-3.2.1.slim.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #F1F1F1;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.root {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a[href^="mailto:"] {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
td, th {
|
||||
padding: .8em;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.page-markdown table pre,
|
||||
.page-markdown table blockquote {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-markdown table pre,
|
||||
.page-markdown table blockquote {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-markdown table pre {
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
border-bottom: 1px solid #eaecef;
|
||||
padding-bottom: 0.3em;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-weight: 600;
|
||||
font-size: 2em;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
code {
|
||||
color: black;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
font-size: .85em;
|
||||
/* word-break: break-all; */
|
||||
}
|
||||
pre code {
|
||||
border: none;
|
||||
}
|
||||
pre {
|
||||
font-size: .85em;
|
||||
}
|
||||
blockquote {
|
||||
font-size: 1em;
|
||||
color: #555;
|
||||
};
|
||||
#toc ul {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#toc > ul > li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#toc {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.title-icon {
|
||||
display: flex;
|
||||
height: 1em;
|
||||
}
|
||||
.title-text {
|
||||
display: flex;
|
||||
font-weight: normal;
|
||||
margin-bottom: .2em;
|
||||
margin-left: .5em;
|
||||
}
|
||||
.sub-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
padding: 0;
|
||||
box-shadow: 0 10px 20px #888888;
|
||||
}
|
||||
table.screenshots {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
table.screenshots th {
|
||||
height: 3em;
|
||||
text-align: center;
|
||||
}
|
||||
table.screenshots th,
|
||||
table.screenshots td {
|
||||
border: 1px solid #C2C2C2;
|
||||
}
|
||||
img[align="left"] {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.mobile-screenshot {
|
||||
height: 40em;
|
||||
padding: 1em;
|
||||
}
|
||||
.cli-screenshot-wrapper {
|
||||
background-color: black;
|
||||
vertical-align: top;
|
||||
padding: 1em 2em 1em 1em;
|
||||
}
|
||||
.cli-screenshot {
|
||||
font-family: "Monaco", "Inconsolata", "CONSOLAS", "Deja Vu Sans Mono", "Droid Sans Mono", "Andale Mono", monospace;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
.cli-screenshot .prompt {
|
||||
color: #48C2F0;
|
||||
}
|
||||
.top-screenshot {
|
||||
margin-top: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
.header {
|
||||
position: relative;
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
color: white;
|
||||
background-color: #2B2B3D;
|
||||
}
|
||||
.header a h1 {
|
||||
color: white;
|
||||
}
|
||||
.header a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.content {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
padding-bottom: 2em;
|
||||
padding-top: 2em;
|
||||
}
|
||||
.forkme {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top:0;
|
||||
}
|
||||
.nav-wrapper {
|
||||
position: relative;
|
||||
width: inherit;
|
||||
}
|
||||
.nav {
|
||||
background-color: black;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.nav.sticky {
|
||||
position:fixed;
|
||||
top: 0;
|
||||
width: inherit;
|
||||
box-shadow: 0 0 10px #000000;
|
||||
}
|
||||
.nav a {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
padding: .6em .9em .6em .9em;
|
||||
}
|
||||
.nav ul {
|
||||
padding-left: 2em;
|
||||
margin-bottom: 0;
|
||||
display: table-cell;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* For GSoC: */
|
||||
min-width: 470px;
|
||||
}
|
||||
.nav ul li {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
}
|
||||
.nav li.selected {
|
||||
background-color: #222;
|
||||
font-weight: bold;
|
||||
}
|
||||
.nav-right {
|
||||
display: flex;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
line-height: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.nav-right .share-btn {
|
||||
display: none;
|
||||
}
|
||||
.nav-right .small-share-btn {
|
||||
display: none;
|
||||
}
|
||||
.footer {
|
||||
padding: 2em;
|
||||
border-top: 1px solid #d4d4d4;
|
||||
margin-top: 2em;
|
||||
color: gray;
|
||||
font-size: .9em;
|
||||
}
|
||||
a.heading-anchor {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
width: 1.3em;
|
||||
font-size: 0.7em;
|
||||
margin-left: 0.4em;
|
||||
line-height: 1em;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
a.heading-anchor:hover,
|
||||
h1:hover a.heading-anchor,
|
||||
h2:hover a.heading-anchor,
|
||||
h3:hover a.heading-anchor,
|
||||
h4:hover a.heading-anchor,
|
||||
h5:hover a.heading-anchor,
|
||||
h6:hover a.heading-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.content{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#toc{
|
||||
display: block!important;
|
||||
align-self: flex-start;
|
||||
width: 300px;
|
||||
position: sticky; top: 20px; left: 0;
|
||||
}
|
||||
|
||||
.main{
|
||||
width: calc(100% - 300px);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #d4d4d4;
|
||||
margin-top: 30px;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
@media all and (min-width: 400px) {
|
||||
.nav-right .share-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
.nav-right .small-share-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container root page-changelog_android">
|
||||
|
||||
<div class="header">
|
||||
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplinapp.org/images/ForkMe.png"/></a>
|
||||
<a href="https://joplinapp.org"><h1 class="title"><img class="title-icon" src="https://joplinapp.org/images/Icon512.png"><span class="title-text">Joplin</span></h1></a>
|
||||
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities</p>
|
||||
</div>
|
||||
|
||||
<div class="nav-wrapper">
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li class=""><a href="https://joplinapp.org/" title="Home"><i class="fa fa-home"></i></a></li>
|
||||
<li><a href="https://discourse.joplinapp.org" title="Forum">Forum</a></li>
|
||||
<li><a class="gsoc" href="https://joplinapp.org/gsoc2021/index/" title="Google Summer of Code 2021">GSoC 2021</a></li>
|
||||
</ul>
|
||||
<div class="nav-right">
|
||||
<iframe class="share-btn share-btn-github" src="https://ghbtns.com/github-btn.html?user=laurent22&repo=joplin&type=star&count=true" frameborder="0" scrolling="0" width="115px" height="20px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div id="toc"><ul>
|
||||
<li>
|
||||
<p>Applications</p>
|
||||
<ul>
|
||||
<li><a href="https://joplinapp.org/desktop/">Desktop application</a></li>
|
||||
<li><a href="https://joplinapp.org/mobile/">Mobile applications</a></li>
|
||||
<li><a href="https://joplinapp.org/terminal/">Terminal application</a></li>
|
||||
<li><a href="https://joplinapp.org/clipper/">Web Clipper</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Support</p>
|
||||
<ul>
|
||||
<li><a href="https://discourse.joplinapp.org">Joplin Forum</a></li>
|
||||
<li><a href="https://joplinapp.org/markdown/">Markdown Guide</a></li>
|
||||
<li><a href="https://joplinapp.org/e2ee/">How to enable end-to-end encryption</a></li>
|
||||
<li><a href="https://joplinapp.org/conflict/">What is a conflict?</a></li>
|
||||
<li><a href="https://joplinapp.org/debugging/">How to enable debug mode</a></li>
|
||||
<li><a href="https://joplinapp.org/rich_text_editor/">About the Rich Text editor limitations</a></li>
|
||||
<li><a href="https://joplinapp.org/faq/">FAQ</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Joplin API - Get Started</p>
|
||||
<ul>
|
||||
<li><a href="https://joplinapp.org/api/overview/">Joplin API Overview</a></li>
|
||||
<li><a href="https://joplinapp.org/api/get_started/plugins/">Plugin development</a></li>
|
||||
<li><a href="https://joplinapp.org/api/tutorials/toc_plugin/">Plugin tutorial</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Joplin API - References</p>
|
||||
<ul>
|
||||
<li><a href="https://joplinapp.org/api/references/plugin_api/classes/joplin.html">Plugin API</a></li>
|
||||
<li><a href="https://joplinapp.org/api/references/rest_api/">Data API</a></li>
|
||||
<li><a href="https://joplinapp.org/api/references/plugin_manifest/">Plugin manifest</a></li>
|
||||
<li><a href="https://joplinapp.org/api/references/plugin_loading_rules/">Plugin loading rules</a></li>
|
||||
<li><a href="https://joplinapp.org/api/references/plugin_theming/">Plugin theming</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Development</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/laurent22/joplin/blob/dev/BUILD.md">How to build the apps</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/e2ee/">End-to-end encryption spec</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/history/">Note History spec</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/sync_lock/">Sync Lock spec</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/plugins/">Plugin Architecture spec</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/search_sorting/">Search Sorting spec</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/server_file_url_format/">Server: File URL Format</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/server_delta_sync/">Server: Delta Sync</a></li>
|
||||
<li><a href="https://joplinapp.org/spec/server_sharing/">Server: Sharing</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Google Summer of Code 2021</p>
|
||||
<ul>
|
||||
<li><a href="https://joplinapp.org/gsoc2021/index/">Google Summer of Code 2021</a></li>
|
||||
<li><a href="https://joplinapp.org/gsoc2021/pull_request_guidelines/">How to submit a GSoC pull request</a></li>
|
||||
<li><a href="https://joplinapp.org/gsoc2021/ideas/">Project Ideas</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>About</p>
|
||||
<ul>
|
||||
<li><a href="https://joplinapp.org/changelog/">Changelog (Desktop App)</a></li>
|
||||
<li><a href="https://joplinapp.org/changelog_cli/">Changelog (CLI App)</a></li>
|
||||
<li><a href="https://joplinapp.org/changelog_server/">Changelog (Server)</a></li>
|
||||
<li><a href="https://joplinapp.org/stats/">Stats</a></li>
|
||||
<li><a href="https://joplinapp.org/donate/">Donate</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&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>
|
||||
<h1>Joplin Android app changelog<a name="joplin-android-app-changelog" href="#joplin-android-app-changelog" class="heading-anchor">🔗</a></h1>
|
||||
|
||||
<div class="bottom-links">
|
||||
<a href="https://github.com/laurent22/joplin/blob/dev/readme/changelog_android.md">
|
||||
<i class="fa fa-github"></i> Improve this doc
|
||||
</a>
|
||||
</div>
|
||||
<script>
|
||||
function stickyHeader() {
|
||||
return; // Disabled
|
||||
|
||||
if ($(window).scrollTop() > 179) {
|
||||
$('.nav').addClass('sticky');
|
||||
} else {
|
||||
$('.nav').removeClass('sticky');
|
||||
}
|
||||
}
|
||||
|
||||
$(window).scroll(function() {
|
||||
stickyHeader();
|
||||
});
|
||||
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-103586105-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
|
||||
</div></div>
|
||||
|
||||
<div class="footer">
|
||||
Copyright (C) 2016-2021 Laurent Cozic
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -405,6 +405,50 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog_server.md
|
||||
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&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>
|
||||
<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>
|
||||
<ul>
|
||||
<li>New: Add support for sharing notes via a link (ccbc329)</li>
|
||||
<li>New: Add support for sharing a folder (#4772)</li>
|
||||
<li>New: Added log page to view latest changes to files (874f301)</li>
|
||||
<li>Fixed: Prevent new user password from being hashed twice (76c143e)</li>
|
||||
<li>Fixed: Fixed crash when rendering note with links to non-existing resources or notes (07484de)</li>
|
||||
<li>Fixed: Fixed error handling when no session is provided (63a5bfa)</li>
|
||||
<li>Fixed: Fixed uploading empty file to the API (#4402)</li>
|
||||
</ul>
|
||||
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v1.7.2">server-v1.7.2</a> - 2021-01-24T19:11:10Z<a name="server-v1-7-2-https-github-com-laurent22-joplin-releases-tag-server-v1-7-2-2021-01-24t19-11-10z" href="#server-v1-7-2-https-github-com-laurent22-joplin-releases-tag-server-v1-7-2-2021-01-24t19-11-10z" class="heading-anchor">🔗</a></h2>
|
||||
<ul>
|
||||
<li>Fixed: Fixed password hashing when changing password</li>
|
||||
|
BIN
docs/images/sponsors/Tranio.png
Normal file
BIN
docs/images/sponsors/Tranio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
1038
docs/index.html
1038
docs/index.html
File diff suppressed because it is too large
Load Diff
@@ -99,22 +99,17 @@
|
||||
"sync.9.path": {
|
||||
"type": "string",
|
||||
"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/"
|
||||
},
|
||||
"sync.9.directory": {
|
||||
"type": "string",
|
||||
"default": "Apps/Joplin",
|
||||
"description": "Joplin Server Directory"
|
||||
"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": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Joplin Server username"
|
||||
"description": "Joplin Cloud email"
|
||||
},
|
||||
"sync.9.password": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Joplin Server password",
|
||||
"description": "Joplin Cloud password",
|
||||
"$comment": "private"
|
||||
},
|
||||
"sync.5.syncTargets": {
|
||||
@@ -602,6 +597,11 @@
|
||||
"default": -1,
|
||||
"$comment": "private"
|
||||
},
|
||||
"sync.userId": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"$comment": "private"
|
||||
},
|
||||
"style.zoom": {
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
@@ -633,7 +633,7 @@
|
||||
},
|
||||
"autoUpdateEnabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"default": true,
|
||||
"description": "Automatically update the application"
|
||||
},
|
||||
"autoUpdate.includePreReleases": {
|
||||
@@ -726,12 +726,6 @@
|
||||
"description": "Enable spell checking in Markdown editor? (WARNING BETA feature). Spell checker in the Markdown editor was previously unstable (cursor location was not stable, sometimes edits would not be saved or reflected in the viewer, etc.) however it appears to be more reliable now. If you notice any issue, please report it on GitHub or the Joplin Forum (Help -> Joplin Forum)",
|
||||
"$comment": "private"
|
||||
},
|
||||
"image.noresizing": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not resize images",
|
||||
"$comment": "private"
|
||||
},
|
||||
"net.customCertificates": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@
|
||||
"releaseIOS": "node packages/tools/release-ios.js",
|
||||
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
|
||||
"releaseServer": "node packages/tools/release-server.js",
|
||||
"buildServerDocker": "node packages/tools/buildServerDocker.js",
|
||||
"setupNewRelease": "node ./packages/tools/setupNewRelease",
|
||||
"test-ci": "lerna run test-ci --stream",
|
||||
"test": "lerna run test --stream",
|
||||
|
@@ -71,20 +71,28 @@ class Command extends BaseCommand {
|
||||
};
|
||||
|
||||
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) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return;
|
||||
}
|
||||
const password2 = await this.prompt(_('Confirm password:'), { type: 'string', secure: true });
|
||||
if (!password2) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return;
|
||||
}
|
||||
if (password !== password2) {
|
||||
this.stdout(_('Passwords do not match!'));
|
||||
return;
|
||||
|
||||
// If the password was passed via command line, we don't ask for
|
||||
// confirmation. This is to allow setting up E2EE entirely from the
|
||||
// command line.
|
||||
if (!argPassword) {
|
||||
const password2 = await this.prompt(_('Confirm password:'), { type: 'string', secure: true });
|
||||
if (!password2) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return;
|
||||
}
|
||||
if (password !== password2) {
|
||||
this.stdout(_('Passwords do not match!'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
||||
return;
|
||||
}
|
||||
|
@@ -33,8 +33,7 @@ module.exports = {
|
||||
'<rootDir>/node_modules/',
|
||||
'<rootDir>/tests/support/',
|
||||
'<rootDir>/build/',
|
||||
'<rootDir>/tests/test-utils.js',
|
||||
'<rootDir>/tests/test-utils-synchronizer.js',
|
||||
'<rootDir>/tests/testUtils.js',
|
||||
'<rootDir>/tests/tmp/',
|
||||
'<rootDir>/tests/test data/',
|
||||
],
|
||||
|
@@ -1,4 +1,17 @@
|
||||
const { afterEachCleanUp } = require('./tests/test-utils.js');
|
||||
const { afterEachCleanUp } = require('@joplin/lib/testing/test-utils.js');
|
||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const sharp = require('sharp');
|
||||
|
||||
let keytar;
|
||||
try {
|
||||
keytar = shim.platformSupportsKeyChain() ? require('keytar') : null;
|
||||
} catch (error) {
|
||||
console.error('Cannot load keytar - keychain support will be disabled', error);
|
||||
keytar = null;
|
||||
}
|
||||
|
||||
shimInit(sharp, keytar);
|
||||
|
||||
global.afterEach(async () => {
|
||||
await afterEachCleanUp();
|
||||
|
105
packages/app-cli/package-lock.json
generated
105
packages/app-cli/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "1.8.1",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"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": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
@@ -2403,27 +2329,6 @@
|
||||
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
|
||||
@@ -2545,11 +2450,6 @@
|
||||
"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": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
@@ -4666,7 +4566,8 @@
|
||||
},
|
||||
"y18n": {
|
||||
"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
|
||||
},
|
||||
"yargs": {
|
||||
|
@@ -5,7 +5,7 @@
|
||||
"author": "Laurent Cozic",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "jest --config=jest.config.js --bail --forceExit",
|
||||
"test": "jest --verbose=false --config=jest.config.js --bail --forceExit",
|
||||
"test-one": "jest --verbose=false --config=jest.config.js --bail --forceExit",
|
||||
"test-ci": "jest --config=jest.config.js --forceExit",
|
||||
"build": "gulp build",
|
||||
@@ -43,7 +43,6 @@
|
||||
"@joplin/renderer": "1.8",
|
||||
"aws-sdk": "^2.588.0",
|
||||
"chalk": "^4.1.0",
|
||||
"clean-html": "^1.5.0",
|
||||
"compare-version": "^0.1.2",
|
||||
"fs-extra": "^5.0.0",
|
||||
"html-entities": "^1.2.1",
|
||||
|
@@ -1,116 +0,0 @@
|
||||
|
||||
const { setupDatabaseAndSynchronizer, switchClient } = require('./test-utils.js');
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const { enexXmlToHtml } = require('@joplin/lib/import-enex-html-gen.js');
|
||||
const cleanHtml = require('clean-html');
|
||||
|
||||
const fileWithPath = (filename) =>
|
||||
`${__dirname}/enex_to_html/${filename}`;
|
||||
|
||||
const audioResource = {
|
||||
filename: 'audio test',
|
||||
id: '9168ee833d03c5ea7c730ac6673978c1',
|
||||
mime: 'audio/x-m4a',
|
||||
size: 82011,
|
||||
title: 'audio test',
|
||||
};
|
||||
|
||||
// All the test HTML files are beautified ones, so we need to run
|
||||
// this before the comparison. Before, beautifying was done by `enexXmlToHtml`
|
||||
// but that was removed due to problems with the clean-html package.
|
||||
const beautifyHtml = (html) => {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
cleanHtml.clean(html, { wrap: 0 }, (...cleanedHtml) => resolve(cleanedHtml.join('')));
|
||||
} catch (error) {
|
||||
console.warn(`Could not clean HTML - the "unclean" version will be used: ${error.message}: ${html.trim().substr(0, 512).replace(/[\n\r]/g, ' ')}...`);
|
||||
resolve([html].join(''));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests the importer for a single note, checking that the result of
|
||||
* processing the given `.enex` input file matches the contents of the given
|
||||
* `.html` file.
|
||||
*
|
||||
* Note that this does not test the importing of an entire exported `.enex`
|
||||
* archive, but rather a single node of such a file. Thus, the test data files
|
||||
* (e.g. `./enex_to_html/code1.enex`) correspond to the contents of a single
|
||||
* `<note>...</note>` node in an `.enex` file already extracted from
|
||||
* `<content><![CDATA[...]]</content>`.
|
||||
*/
|
||||
const compareOutputToExpected = (options) => {
|
||||
const inputFile = fileWithPath(`${options.testName}.enex`);
|
||||
const outputFile = fileWithPath(`${options.testName}.html`);
|
||||
const testTitle = `should convert from Enex to Html: ${options.testName}`;
|
||||
|
||||
it(testTitle, (async () => {
|
||||
const enexInput = await shim.fsDriver().readFile(inputFile);
|
||||
const expectedOutput = await shim.fsDriver().readFile(outputFile);
|
||||
const actualOutput = await beautifyHtml(await enexXmlToHtml(enexInput, options.resources));
|
||||
|
||||
expect(actualOutput).toEqual(expectedOutput);
|
||||
}));
|
||||
};
|
||||
|
||||
describe('EnexToHtml', function() {
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
compareOutputToExpected({
|
||||
testName: 'checklist-list',
|
||||
resources: [],
|
||||
});
|
||||
|
||||
compareOutputToExpected({
|
||||
testName: 'svg',
|
||||
resources: [],
|
||||
});
|
||||
|
||||
compareOutputToExpected({
|
||||
testName: 'en-media--image',
|
||||
resources: [{
|
||||
filename: '',
|
||||
id: '89ce7da62c6b2832929a6964237e98e9', // Mock id
|
||||
mime: 'image/jpeg',
|
||||
size: 50347,
|
||||
title: '',
|
||||
}],
|
||||
});
|
||||
|
||||
compareOutputToExpected({
|
||||
testName: 'en-media--audio',
|
||||
resources: [audioResource],
|
||||
});
|
||||
|
||||
compareOutputToExpected({
|
||||
testName: 'attachment',
|
||||
resources: [{
|
||||
filename: 'attachment-1',
|
||||
id: '21ca2b948f222a38802940ec7e2e5de3',
|
||||
mime: 'application/pdf', // Any non-image/non-audio mime type will do
|
||||
size: 1000,
|
||||
}],
|
||||
});
|
||||
|
||||
// it('fails when not given a matching resource', (async () => {
|
||||
// // To test the promise-unexpectedly-resolved case, add `audioResource` to the array.
|
||||
// const resources = [];
|
||||
// const inputFile = fileWithPath('en-media--image.enex');
|
||||
// const enexInput = await shim.fsDriver().readFile(inputFile);
|
||||
// const promisedOutput = enexXmlToHtml(enexInput, resources);
|
||||
|
||||
// promisedOutput.then(() => {
|
||||
// // Promise should not be resolved
|
||||
// expect(false).toEqual(true);
|
||||
// }, (reason) => {
|
||||
// expect(reason)
|
||||
// .toBe('Hash with no associated resource: 89ce7da62c6b2832929a6964237e98e9');
|
||||
// });
|
||||
// }));
|
||||
|
||||
});
|
@@ -4,7 +4,7 @@
|
||||
const os = require('os');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { filename } = require('@joplin/lib/path-utils');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('./test-utils.js');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('@joplin/lib/testing/test-utils.js');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import MdToHtml from '@joplin/renderer/MdToHtml';
|
||||
const os = require('os');
|
||||
const { filename } = require('@joplin/lib/path-utils');
|
||||
const { setupDatabaseAndSynchronizer, switchClient } = require('./test-utils.js');
|
||||
const { setupDatabaseAndSynchronizer, switchClient } = require('@joplin/lib/testing/test-utils.js');
|
||||
import shim from '@joplin/lib/shim';
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
|
||||
@@ -137,6 +137,14 @@ describe('MdToHtml', function() {
|
||||
}
|
||||
}));
|
||||
|
||||
it('should render an empty string', (async () => {
|
||||
const mdToHtml = newTestMdToHtml();
|
||||
const result = await mdToHtml.render('', null, { splitted: true });
|
||||
// The TinyMCE component checks for this exact string to apply a hack,
|
||||
// so make sure it doesn't change from version to version.
|
||||
expect(result.html).toBe('<div id="rendered-md"></div>');
|
||||
}));
|
||||
|
||||
it('should split HTML and CSS', (async () => {
|
||||
const mdToHtml = newTestMdToHtml();
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
const mdImporterService = require('@joplin/lib/services/interop/InteropService_Importer_Md').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const { setupDatabaseAndSynchronizer, switchClient } = require('./test-utils.js');
|
||||
const { setupDatabaseAndSynchronizer, switchClient } = require('@joplin/lib/testing/test-utils.js');
|
||||
|
||||
const importer = new mdImporterService();
|
||||
|
||||
@@ -43,4 +43,9 @@ describe('InteropService_Importer_Md: importLocalImages', function() {
|
||||
const items = await Note.linkedItems(note.body);
|
||||
expect(items.length).toBe(1);
|
||||
});
|
||||
it('should import resources for files', async function() {
|
||||
const note = await importer.importFile(`${__dirname}/md_to_md/sample-files.md`, 'notebook');
|
||||
const items = await Note.linkedItems(note.body);
|
||||
expect(items.length).toBe(4);
|
||||
});
|
||||
});
|
@@ -1,290 +0,0 @@
|
||||
'use strict';
|
||||
const __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function(resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
const Setting_1 = require('@joplin/lib/models/Setting');
|
||||
const test_utils_synchronizer_1 = require('./test-utils-synchronizer');
|
||||
const { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSynchronizer, synchronizer, sleep, switchClient, syncTargetId, fileApi } = require('./test-utils.js');
|
||||
const Folder_1 = require('@joplin/lib/models/Folder');
|
||||
const Note_1 = require('@joplin/lib/models/Note');
|
||||
const BaseItem_1 = require('@joplin/lib/models/BaseItem');
|
||||
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
||||
describe('Synchronizer.basics', function() {
|
||||
beforeEach((done) => __awaiter(this, void 0, void 0, function* () {
|
||||
yield setupDatabaseAndSynchronizer(1);
|
||||
yield setupDatabaseAndSynchronizer(2);
|
||||
yield switchClient(1);
|
||||
done();
|
||||
}));
|
||||
afterAll(() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield afterAllCleanUp();
|
||||
}));
|
||||
it('should create remote items', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder = yield Folder_1.default.save({ title: 'folder1' });
|
||||
yield Note_1.default.save({ title: 'un', parent_id: folder.id });
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield synchronizerStart();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(all, expect);
|
||||
})));
|
||||
it('should update remote items', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const note = yield Note_1.default.save({ title: 'un', parent_id: folder.id });
|
||||
yield synchronizerStart();
|
||||
yield Note_1.default.save({ title: 'un UPDATE', id: note.id });
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield synchronizerStart();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(all, expect);
|
||||
})));
|
||||
it('should create local items', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder = yield Folder_1.default.save({ title: 'folder1' });
|
||||
yield Note_1.default.save({ title: 'un', parent_id: folder.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(all, expect);
|
||||
})));
|
||||
it('should update local items', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const note1 = yield Note_1.default.save({ title: 'un', parent_id: folder1.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield sleep(0.1);
|
||||
let note2 = yield Note_1.default.load(note1.id);
|
||||
note2.title = 'Updated on client 2';
|
||||
yield Note_1.default.save(note2);
|
||||
note2 = yield Note_1.default.load(note2.id);
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield synchronizerStart();
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(all, expect);
|
||||
})));
|
||||
it('should delete remote notes', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const note1 = yield Note_1.default.save({ title: 'un', parent_id: folder1.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield sleep(0.1);
|
||||
yield Note_1.default.delete(note1.id);
|
||||
yield synchronizerStart();
|
||||
const remotes = yield test_utils_synchronizer_1.remoteNotesAndFolders();
|
||||
expect(remotes.length).toBe(1);
|
||||
expect(remotes[0].id).toBe(folder1.id);
|
||||
const deletedItems = yield BaseItem_1.default.deletedItems(syncTargetId());
|
||||
expect(deletedItems.length).toBe(0);
|
||||
})));
|
||||
it('should not created deleted_items entries for items deleted via sync', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
yield Note_1.default.save({ title: 'un', parent_id: folder1.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield Folder_1.default.delete(folder1.id);
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield synchronizerStart();
|
||||
const deletedItems = yield BaseItem_1.default.deletedItems(syncTargetId());
|
||||
expect(deletedItems.length).toBe(0);
|
||||
})));
|
||||
it('should delete local notes', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const note1 = yield Note_1.default.save({ title: 'un', parent_id: folder1.id });
|
||||
const note2 = yield Note_1.default.save({ title: 'deux', parent_id: folder1.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield Note_1.default.delete(note1.id);
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield synchronizerStart();
|
||||
const items = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
expect(items.length).toBe(2);
|
||||
const deletedItems = yield BaseItem_1.default.deletedItems(syncTargetId());
|
||||
expect(deletedItems.length).toBe(0);
|
||||
yield Note_1.default.delete(note2.id);
|
||||
yield synchronizerStart();
|
||||
})));
|
||||
it('should delete remote folder', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield Folder_1.default.save({ title: 'folder1' });
|
||||
const folder2 = yield Folder_1.default.save({ title: 'folder2' });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield sleep(0.1);
|
||||
yield Folder_1.default.delete(folder2.id);
|
||||
yield synchronizerStart();
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(all, expect);
|
||||
})));
|
||||
it('should delete local folder', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield Folder_1.default.save({ title: 'folder1' });
|
||||
const folder2 = yield Folder_1.default.save({ title: 'folder2' });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield Folder_1.default.delete(folder2.id);
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield synchronizerStart();
|
||||
const items = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(items, expect);
|
||||
})));
|
||||
it('should cross delete all folders', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
// If client1 and 2 have two folders, client 1 deletes item 1 and client
|
||||
// 2 deletes item 2, they should both end up with no items after sync.
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const folder2 = yield Folder_1.default.save({ title: 'folder2' });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield sleep(0.1);
|
||||
yield Folder_1.default.delete(folder1.id);
|
||||
yield switchClient(1);
|
||||
yield Folder_1.default.delete(folder2.id);
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
const items2 = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield switchClient(1);
|
||||
yield synchronizerStart();
|
||||
const items1 = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
expect(items1.length).toBe(0);
|
||||
expect(items1.length).toBe(items2.length);
|
||||
})));
|
||||
it('items should be downloaded again when user cancels in the middle of delta operation', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
yield Note_1.default.save({ title: 'un', is_todo: 1, parent_id: folder1.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
synchronizer().testingHooks_ = ['cancelDeltaLoop2'];
|
||||
yield synchronizerStart();
|
||||
let notes = yield Note_1.default.all();
|
||||
expect(notes.length).toBe(0);
|
||||
synchronizer().testingHooks_ = [];
|
||||
yield synchronizerStart();
|
||||
notes = yield Note_1.default.all();
|
||||
expect(notes.length).toBe(1);
|
||||
})));
|
||||
it('should skip items that cannot be synced', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder1 = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const note1 = yield Note_1.default.save({ title: 'un', is_todo: 1, parent_id: folder1.id });
|
||||
const noteId = note1.id;
|
||||
yield synchronizerStart();
|
||||
let disabledItems = yield BaseItem_1.default.syncDisabledItems(syncTargetId());
|
||||
expect(disabledItems.length).toBe(0);
|
||||
yield Note_1.default.save({ id: noteId, title: 'un mod' });
|
||||
synchronizer().testingHooks_ = ['notesRejectedByTarget'];
|
||||
yield synchronizerStart();
|
||||
synchronizer().testingHooks_ = [];
|
||||
yield synchronizerStart(); // Another sync to check that this item is now excluded from sync
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
const notes = yield Note_1.default.all();
|
||||
expect(notes.length).toBe(1);
|
||||
expect(notes[0].title).toBe('un');
|
||||
yield switchClient(1);
|
||||
disabledItems = yield BaseItem_1.default.syncDisabledItems(syncTargetId());
|
||||
expect(disabledItems.length).toBe(1);
|
||||
})));
|
||||
it('should allow duplicate folder titles', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
yield Folder_1.default.save({ title: 'folder' });
|
||||
yield switchClient(2);
|
||||
let remoteF2 = yield Folder_1.default.save({ title: 'folder' });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield sleep(0.1);
|
||||
yield synchronizerStart();
|
||||
const localF2 = yield Folder_1.default.load(remoteF2.id);
|
||||
expect(localF2.title == remoteF2.title).toBe(true);
|
||||
// Then that folder that has been renamed locally should be set in such a way
|
||||
// that synchronizing it applies the title change remotely, and that new title
|
||||
// should be retrieved by client 2.
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield sleep(0.1);
|
||||
yield synchronizerStart();
|
||||
remoteF2 = yield Folder_1.default.load(remoteF2.id);
|
||||
expect(remoteF2.title == localF2.title).toBe(true);
|
||||
})));
|
||||
it('should create remote items with UTF-8 content', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder = yield Folder_1.default.save({ title: 'Fahrräder' });
|
||||
yield Note_1.default.save({ title: 'Fahrräder', body: 'Fahrräder', parent_id: folder.id });
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
yield synchronizerStart();
|
||||
yield test_utils_synchronizer_1.localNotesFoldersSameAsRemote(all, expect);
|
||||
})));
|
||||
it('should update remote items but not pull remote changes', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
const folder = yield Folder_1.default.save({ title: 'folder1' });
|
||||
const note = yield Note_1.default.save({ title: 'un', parent_id: folder.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
yield Note_1.default.save({ title: 'deux', parent_id: folder.id });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield Note_1.default.save({ title: 'un UPDATE', id: note.id });
|
||||
yield synchronizerStart(null, { syncSteps: ['update_remote'] });
|
||||
const all = yield test_utils_synchronizer_1.allNotesFolders();
|
||||
expect(all.length).toBe(2);
|
||||
yield switchClient(2);
|
||||
yield synchronizerStart();
|
||||
const note2 = yield Note_1.default.load(note.id);
|
||||
expect(note2.title).toBe('un UPDATE');
|
||||
})));
|
||||
it('should create a new Welcome notebook on each client', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
// Create the Welcome items on two separate clients
|
||||
yield WelcomeUtils.createWelcomeItems();
|
||||
yield synchronizerStart();
|
||||
yield switchClient(2);
|
||||
yield WelcomeUtils.createWelcomeItems();
|
||||
const beforeFolderCount = (yield Folder_1.default.all()).length;
|
||||
const beforeNoteCount = (yield Note_1.default.all()).length;
|
||||
expect(beforeFolderCount === 1).toBe(true);
|
||||
expect(beforeNoteCount > 1).toBe(true);
|
||||
yield synchronizerStart();
|
||||
const afterFolderCount = (yield Folder_1.default.all()).length;
|
||||
const afterNoteCount = (yield Note_1.default.all()).length;
|
||||
expect(afterFolderCount).toBe(beforeFolderCount * 2);
|
||||
expect(afterNoteCount).toBe(beforeNoteCount * 2);
|
||||
// Changes to the Welcome items should be synced to all clients
|
||||
const f1 = (yield Folder_1.default.all())[0];
|
||||
yield Folder_1.default.save({ id: f1.id, title: 'Welcome MOD' });
|
||||
yield synchronizerStart();
|
||||
yield switchClient(1);
|
||||
yield synchronizerStart();
|
||||
const f1_1 = yield Folder_1.default.load(f1.id);
|
||||
expect(f1_1.title).toBe('Welcome MOD');
|
||||
})));
|
||||
it('should not wipe out user data when syncing with an empty target', (() => __awaiter(this, void 0, void 0, function* () {
|
||||
// Only these targets support the wipeOutFailSafe flag (in other words, the targets that use basicDelta)
|
||||
if (!['nextcloud', 'memory', 'filesystem', 'amazon_s3'].includes(syncTargetName())) { return; }
|
||||
for (let i = 0; i < 10; i++) { yield Note_1.default.save({ title: 'note' }); }
|
||||
Setting_1.default.setValue('sync.wipeOutFailSafe', true);
|
||||
yield synchronizerStart();
|
||||
yield fileApi().clearRoot(); // oops
|
||||
yield synchronizerStart();
|
||||
expect((yield Note_1.default.all()).length).toBe(10); // but since the fail-safe if on, the notes have not been deleted
|
||||
Setting_1.default.setValue('sync.wipeOutFailSafe', false); // Now switch it off
|
||||
yield synchronizerStart();
|
||||
expect((yield Note_1.default.all()).length).toBe(0); // Since the fail-safe was off, the data has been cleared
|
||||
// Handle case where the sync target has been wiped out, then the user creates one note and sync.
|
||||
for (let i = 0; i < 10; i++) { yield Note_1.default.save({ title: 'note' }); }
|
||||
Setting_1.default.setValue('sync.wipeOutFailSafe', true);
|
||||
yield synchronizerStart();
|
||||
yield fileApi().clearRoot();
|
||||
yield Note_1.default.save({ title: 'ma note encore' });
|
||||
yield synchronizerStart();
|
||||
expect((yield Note_1.default.all()).length).toBe(11);
|
||||
})));
|
||||
});
|
||||
// # sourceMappingURL=Synchronizer.share.js.map
|
@@ -1,31 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { sortedIds, createNTestNotes, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('./test-utils.js');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const ArrayUtils = require('@joplin/lib/ArrayUtils.js');
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
|
||||
describe('database', function() {
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not modify cached field names', (async () => {
|
||||
const db = BaseModel.db();
|
||||
|
||||
const fieldNames = db.tableFieldNames('notes');
|
||||
const fieldCount = fieldNames.length;
|
||||
fieldNames.push('type_');
|
||||
|
||||
expect(fieldCount).toBeGreaterThan(0);
|
||||
expect(db.tableFieldNames('notes').length).toBe(fieldCount);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,14 +1,16 @@
|
||||
For example, consider a web page like this:
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="page-scripts/page-script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<body>
|
||||
<script src="page-scripts/page-script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The script "page-script.js" does this:
|
@@ -1,7 +1,9 @@
|
||||
Subshell:
|
||||
|
||||
(
|
||||
set -e
|
||||
false
|
||||
echo Unreachable
|
||||
) && echo Great success
|
||||
```
|
||||
(
|
||||
set -e
|
||||
false
|
||||
echo Unreachable
|
||||
) && echo Great success
|
||||
```
|
@@ -1 +1 @@
|
||||
jq -r '.[]|[.index, .name, .section, .award, .industry]|join("\t")' raw.json |pbcopy
|
||||
`jq -r '.[]|[.index, .name, .section, .award, .industry]|join("\t")' raw.json |pbcopy`
|
1
packages/app-cli/tests/enex_to_md/code4.html
Normal file
1
packages/app-cli/tests/enex_to_md/code4.html
Normal file
@@ -0,0 +1 @@
|
||||
<div><div>code block:</div><div><br/></div><div style="box-sizing: border-box; padding: 8px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 12px; color: rgb(51, 51, 51); border-radius: 4px; background-color: rgb(251, 250, 248); border: 1px solid rgba(0, 0, 0, 0.15);-en-codeblock:true;"><div>public static void main(String[] args) {</div><div><span> System.out.println('Hello World');</span><br/></div><div>}</div></div><div><br/></div><div>end of code block</div></div>
|
9
packages/app-cli/tests/enex_to_md/code4.md
Normal file
9
packages/app-cli/tests/enex_to_md/code4.md
Normal file
@@ -0,0 +1,9 @@
|
||||
code block:
|
||||
|
||||
```
|
||||
public static void main(String[] args) {
|
||||
System.out.println('Hello World');
|
||||
}
|
||||
```
|
||||
|
||||
end of code block
|
@@ -1,4 +1,4 @@
|
||||
const { id, ids, createNTestFolders, sortedIds, createNTestNotes, TestApp } = require('./test-utils.js');
|
||||
const { id, ids, createNTestFolders, sortedIds, createNTestNotes, TestApp } = require('@joplin/lib/testing/test-utils.js');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const uuid = require('@joplin/lib/uuid').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const { setupDatabaseAndSynchronizer, switchClient, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('./test-utils.js');
|
||||
const { setupDatabaseAndSynchronizer, switchClient, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('@joplin/lib/testing/test-utils.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const { setupDatabaseAndSynchronizer, switchClient, id, ids, sortedIds, at, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('./test-utils.js');
|
||||
const { setupDatabaseAndSynchronizer, switchClient, id, ids, sortedIds, at, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('@joplin/lib/testing/test-utils.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const { setupDatabaseAndSynchronizer, switchClient, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('./test-utils.js');
|
||||
const { setupDatabaseAndSynchronizer, switchClient, createNTestFolders, createNTestNotes, createNTestTags, TestApp } = require('@joplin/lib/testing/test-utils.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
$katexcode$
|
||||
Hello World:$katexcode$
|
File diff suppressed because one or more lines are too long
@@ -1,3 +1,5 @@
|
||||
Hello World :
|
||||
|
||||
$$
|
||||
katexcode
|
||||
\sqrt{3x}
|
||||
$$
|
9
packages/app-cli/tests/md_to_md/sample-files.md
Normal file
9
packages/app-cli/tests/md_to_md/sample-files.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Markdown file test
|
||||
|
||||

|
||||
|
||||
[welcome.pdf](../support/welcome.pdf)
|
||||
|
||||
[sample.md](sample.md)
|
||||
|
||||
[sample2.md](./sample.md)
|
@@ -1,8 +1,7 @@
|
||||
import KeychainService from '@joplin/lib/services/keychain/KeychainService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
const { db, setupDatabaseAndSynchronizer, switchClient } = require('./test-utils.js');
|
||||
import { db, setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
|
||||
function describeIfCompatible(name: string, fn: any, elseFn: any) {
|
||||
if (['win32', 'darwin'].includes(shim.platformName())) {
|
@@ -1,4 +1,4 @@
|
||||
import PluginRunner from '../app/services/plugins/PluginRunner';
|
||||
import PluginRunner from '../../../app/services/plugins/PluginRunner';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
|
||||
import MdToHtml from '@joplin/renderer/MdToHtml';
|
||||
@@ -7,10 +7,10 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
import * as fs from 'fs-extra';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { newPluginScript } from './test-utils';
|
||||
import { expectNotThrow, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir } from './test-utils.js';
|
||||
import { expectNotThrow, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir, supportDir } from '@joplin/lib/testing/test-utils';
|
||||
import { newPluginScript } from '../../testUtils';
|
||||
|
||||
const testPluginDir = `${__dirname}/../tests/support/plugins`;
|
||||
const testPluginDir = `${supportDir}/plugins`;
|
||||
|
||||
function newPluginService(appVersion: string = '1.4') {
|
||||
const runner = new PluginRunner();
|
||||
@@ -102,7 +102,7 @@ describe('services_PluginService', function() {
|
||||
"homepage_url": "https://joplinapp.org"
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.data.post(['folders'], null, { title: "my plugin folder" });
|
||||
@@ -141,14 +141,14 @@ describe('services_PluginService', function() {
|
||||
"not_a_valid_manifest_at_all": 1
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {},
|
||||
});
|
||||
`, `
|
||||
/* joplin-manifest:
|
||||
*/
|
||||
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {},
|
||||
});
|
||||
@@ -189,7 +189,7 @@ describe('services_PluginService', function() {
|
||||
"homepage_url": "https://joplinapp.org"
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.contentScripts.register('markdownItPlugin', 'justtesting', './markdownItTestPlugin.js');
|
||||
@@ -236,7 +236,7 @@ describe('services_PluginService', function() {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() { },
|
||||
});
|
||||
@@ -283,7 +283,7 @@ describe('services_PluginService', function() {
|
||||
}));
|
||||
|
||||
it('should create the data directory', (async () => {
|
||||
const pluginScript = newPluginScript(`
|
||||
const pluginScript = newPluginScript(`
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
const dataDir = await joplin.plugins.dataDir();
|
@@ -1,10 +1,10 @@
|
||||
import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { setupDatabaseAndSynchronizer, switchClient, supportDir, createTempDir } from '../../test-utils';
|
||||
import { setupDatabaseAndSynchronizer, switchClient, supportDir, createTempDir } from '@joplin/lib/testing/test-utils';
|
||||
|
||||
async function newRepoApi(): Promise<RepositoryApi> {
|
||||
const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir());
|
||||
await repo.loadManifests();
|
||||
await repo.initialize();
|
||||
return repo;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
const { waitForFolderCount, newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('../../../test-utils');
|
||||
import { waitForFolderCount, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { newPluginScript, newPluginService } from '../../../testUtils';
|
||||
|
||||
describe('JoplinSettings', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('JoplinSettings', () => {
|
||||
});
|
||||
|
||||
test('should listen to setting change event', async () => {
|
||||
const service = new newPluginService() as PluginService;
|
||||
const service = newPluginService();
|
||||
|
||||
const pluginScript = newPluginScript(`
|
||||
joplin.plugins.register({
|
||||
@@ -68,7 +68,7 @@ describe('JoplinSettings', () => {
|
||||
});
|
||||
|
||||
test('should allow registering multiple settings', async () => {
|
||||
const service = new newPluginService() as PluginService;
|
||||
const service = newPluginService();
|
||||
|
||||
const pluginScript = newPluginScript(`
|
||||
joplin.plugins.register({
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import KeymapService from '@joplin/lib/services/KeymapService';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
const { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('../../../test-utils');
|
||||
import { setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
|
||||
import { newPluginScript, newPluginService } from '../../../testUtils';
|
||||
|
||||
describe('JoplinViewMenuItem', () => {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('JoplinViewMenuItem', () => {
|
||||
});
|
||||
|
||||
test('should register commands with the keymap service', async () => {
|
||||
const service = new newPluginService() as PluginService;
|
||||
const service = newPluginService();
|
||||
|
||||
KeymapService.instance().initialize();
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '../../../test-utils';
|
||||
import { setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import ItemChange from '@joplin/lib/models/ItemChange';
|
||||
import { newPluginScript, newPluginService } from '../../../testUtils';
|
||||
|
||||
describe('JoplinWorkspace', () => {
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import sandboxProxy, { Target } from '@joplin/lib/services/plugins/sandboxProxy';
|
||||
|
||||
const { setupDatabaseAndSynchronizer, switchClient } = require('../../test-utils.js');
|
||||
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
|
||||
describe('services_plugins_sandboxProxy', function() {
|
||||
|
||||
|
@@ -1,53 +0,0 @@
|
||||
import { setupDatabaseAndSynchronizer, db, switchClient } from './test-utils.js';
|
||||
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
|
||||
import SearchEngineUtils from '@joplin/lib/services/searchengine/SearchEngineUtils';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
||||
|
||||
let searchEngine: any = null;
|
||||
|
||||
describe('services_SearchEngineUtils', function() {
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
searchEngine = new SearchEngine();
|
||||
searchEngine.setDb(db());
|
||||
done();
|
||||
});
|
||||
|
||||
describe('filter todos based on showCompletedTodos', function() {
|
||||
it('show completed', (async () => {
|
||||
const note1 = await Note.save({ title: 'abcd', body: 'body 1' });
|
||||
const todo1 = await Note.save({ title: 'abcd', body: 'todo 1', is_todo: 1 });
|
||||
const todo2 = await Note.save({ title: 'abcd', body: 'todo 2', is_todo: 1, todo_completed: 1590085027710 });
|
||||
await Note.save({ title: 'qwer', body: 'body 2' });
|
||||
await searchEngine.syncTables();
|
||||
|
||||
Setting.setValue('showCompletedTodos', true);
|
||||
|
||||
const rows = await SearchEngineUtils.notesForQuery('abcd', 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('hide completed', (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' });
|
||||
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', null, searchEngine);
|
||||
|
||||
expect(rows.length).toBe(2);
|
||||
expect(rows.map(r=>r.id)).toContain(note1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(todo1.id);
|
||||
}));
|
||||
});
|
||||
});
|
@@ -1,876 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint prefer-const: 0*/
|
||||
|
||||
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('./test-utils.js');
|
||||
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
const ItemChange = require('@joplin/lib/models/ItemChange').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Resource = require('@joplin/lib/models/Resource').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const ResourceService = require('@joplin/lib/services/ResourceService').default;
|
||||
|
||||
|
||||
let engine = null;
|
||||
|
||||
const ids = (array) => array.map(a => a.id);
|
||||
|
||||
describe('services_SearchFilter', function() {
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
|
||||
engine = new SearchEngine();
|
||||
engine.setDb(db());
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('should return note matching title', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'body 1' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'body 2' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('title: abcd');
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n1.id);
|
||||
}));
|
||||
|
||||
it('should return note matching negated title', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'body 1' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'body 2' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('-title: abcd');
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n2.id);
|
||||
}));
|
||||
|
||||
it('should return note matching body', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'body1' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'body2' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('body: body1');
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n1.id);
|
||||
}));
|
||||
|
||||
it('should return note matching negated body', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'body1' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'body2' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('-body: body1');
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n2.id);
|
||||
}));
|
||||
|
||||
it('should return note matching title containing multiple words', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd xyz', body: 'body1' });
|
||||
const n2 = await Note.save({ title: 'efgh ijk', body: 'body2' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('title: "abcd xyz"');
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n1.id);
|
||||
}));
|
||||
|
||||
it('should return note matching body containing multiple words', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'ho ho ho' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'foo bar' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('body: "foo bar"');
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n2.id);
|
||||
}));
|
||||
|
||||
it('should return note matching title AND body', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'ho ho ho' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'foo bar' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('title: efgh body: "foo bar"');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n2.id);
|
||||
|
||||
rows = await engine.search('title: abcd body: "foo bar"');
|
||||
expect(rows.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should return note matching title OR body', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abcd', body: 'ho ho ho' });
|
||||
const n2 = await Note.save({ title: 'efgh', body: 'foo bar' });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('any:1 title: abcd body: "foo bar"');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(rows.map(r=>r.id)).toContain(n1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('any:1 title: wxyz body: "blah blah"');
|
||||
expect(rows.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should return notes matching text', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'foo beef', body: 'dead bar' });
|
||||
const n2 = await Note.save({ title: 'bar efgh', body: 'foo dog' });
|
||||
const n3 = await Note.save({ title: 'foo ho', body: 'ho ho ho' });
|
||||
await engine.syncTables();
|
||||
|
||||
// Interpretation: Match with notes containing foo in title/body and bar in title/body
|
||||
// Note: This is NOT saying to match notes containing foo bar in title/body
|
||||
rows = await engine.search('foo bar');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(rows.map(r=>r.id)).toContain(n1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('foo -bar');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows.map(r=>r.id)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('foo efgh');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n2.id);
|
||||
|
||||
rows = await engine.search('zebra');
|
||||
expect(rows.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should return notes matching any negated text', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abc', body: 'def' });
|
||||
const n2 = await Note.save({ title: 'def', body: 'ghi' });
|
||||
const n3 = await Note.save({ title: 'ghi', body: 'jkl' });
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('any:1 -abc -ghi');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(rows.map(r=>r.id)).toContain(n1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n2.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should return notes matching any negated title', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abc', body: 'def' });
|
||||
const n2 = await Note.save({ title: 'def', body: 'ghi' });
|
||||
const n3 = await Note.save({ title: 'ghi', body: 'jkl' });
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('any:1 -title:abc -title:ghi');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(rows.map(r=>r.id)).toContain(n1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n2.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should return notes matching any negated body', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'abc', body: 'def' });
|
||||
const n2 = await Note.save({ title: 'def', body: 'ghi' });
|
||||
const n3 = await Note.save({ title: 'ghi', body: 'jkl' });
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('any:1 -body:xyz -body:ghi');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(rows.map(r=>r.id)).toContain(n1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n2.id);
|
||||
expect(rows.map(r=>r.id)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support phrase search', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'foo beef', body: 'bar dog' });
|
||||
const n2 = await Note.save({ title: 'bar efgh', body: 'foo dog' });
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('"bar dog"');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows[0].id).toBe(n1.id);
|
||||
}));
|
||||
|
||||
it('should support prefix search', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'foo beef', body: 'bar dog' });
|
||||
const n2 = await Note.save({ title: 'bar efgh', body: 'foo dog' });
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('"bar*"');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by tags', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'But I would', body: 'walk 500 miles' });
|
||||
const n2 = await Note.save({ title: 'And I would', body: 'walk 500 more' });
|
||||
const n3 = await Note.save({ title: 'Just to be', body: 'the man who' });
|
||||
const n4 = await Note.save({ title: 'walked a thousand', body: 'miles to fall' });
|
||||
const n5 = await Note.save({ title: 'down at your', body: 'door' });
|
||||
|
||||
await Tag.setNoteTagsByTitles(n1.id, ['Da', 'da', 'lat', 'da']);
|
||||
await Tag.setNoteTagsByTitles(n2.id, ['Da', 'da', 'lat', 'da']);
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('tag:*');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('-tag:*');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
expect(ids(rows)).toContain(n4.id);
|
||||
expect(ids(rows)).toContain(n5.id);
|
||||
}));
|
||||
|
||||
|
||||
it('should support filtering by tags', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'peace talks', body: 'battle ground' });
|
||||
const n2 = await Note.save({ title: 'mouse', body: 'mister' });
|
||||
const n3 = await Note.save({ title: 'dresden files', body: 'harry dresden' });
|
||||
|
||||
await Tag.setNoteTagsByTitles(n1.id, ['tag1', 'tag2']);
|
||||
await Tag.setNoteTagsByTitles(n2.id, ['tag2', 'tag3']);
|
||||
await Tag.setNoteTagsByTitles(n3.id, ['tag3', 'tag4', 'space travel']);
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('tag:tag2');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('tag:tag2 tag:tag3');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('any:1 tag:tag1 tag:tag2 tag:tag3');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('tag:tag2 tag:tag3 tag:tag4');
|
||||
expect(rows.length).toBe(0);
|
||||
|
||||
rows = await engine.search('-tag:tag2');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('-tag:tag2 -tag:tag3');
|
||||
expect(rows.length).toBe(0);
|
||||
|
||||
rows = await engine.search('-tag:tag2 -tag:tag3');
|
||||
expect(rows.length).toBe(0);
|
||||
|
||||
rows = await engine.search('any:1 -tag:tag2 -tag:tag3');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('tag:"space travel"');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by notebook', (async () => {
|
||||
let rows;
|
||||
const folder0 = await Folder.save({ title: 'notebook0' });
|
||||
const folder1 = await Folder.save({ title: 'notebook1' });
|
||||
const notes0 = await createNTestNotes(5, folder0);
|
||||
const notes1 = await createNTestNotes(5, folder1);
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('notebook:notebook0');
|
||||
expect(rows.length).toBe(5);
|
||||
expect(ids(rows).sort()).toEqual(ids(notes0).sort());
|
||||
|
||||
}));
|
||||
|
||||
it('should support filtering by nested notebook', (async () => {
|
||||
let rows;
|
||||
const folder0 = await Folder.save({ title: 'notebook0' });
|
||||
const folder00 = await Folder.save({ title: 'notebook00', parent_id: folder0.id });
|
||||
const folder1 = await Folder.save({ title: 'notebook1' });
|
||||
const notes0 = await createNTestNotes(5, folder0);
|
||||
const notes00 = await createNTestNotes(5, folder00);
|
||||
const notes1 = await createNTestNotes(5, folder1);
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('notebook:notebook0');
|
||||
expect(rows.length).toBe(10);
|
||||
expect(ids(rows).sort()).toEqual(ids(notes0.concat(notes00)).sort());
|
||||
}));
|
||||
|
||||
it('should support filtering by multiple notebooks', (async () => {
|
||||
let rows;
|
||||
const folder0 = await Folder.save({ title: 'notebook0' });
|
||||
const folder00 = await Folder.save({ title: 'notebook00', parent_id: folder0.id });
|
||||
const folder1 = await Folder.save({ title: 'notebook1' });
|
||||
const folder2 = await Folder.save({ title: 'notebook2' });
|
||||
const notes0 = await createNTestNotes(5, folder0);
|
||||
const notes00 = await createNTestNotes(5, folder00);
|
||||
const notes1 = await createNTestNotes(5, folder1);
|
||||
const notes2 = await createNTestNotes(5, folder2);
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('notebook:notebook0 notebook:notebook1');
|
||||
expect(rows.length).toBe(15);
|
||||
expect(ids(rows).sort()).toEqual(ids(notes0).concat(ids(notes00).concat(ids(notes1))).sort());
|
||||
}));
|
||||
|
||||
it('should support filtering by created date', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this on', body: 'May 20 2020', user_created_time: Date.parse('2020-05-20') });
|
||||
const n2 = await Note.save({ title: 'I made this on', body: 'May 19 2020', user_created_time: Date.parse('2020-05-19') });
|
||||
const n3 = await Note.save({ title: 'I made this on', body: 'May 18 2020', user_created_time: Date.parse('2020-05-18') });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('created:20200520');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('created:20200519');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('-created:20200519');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
}));
|
||||
|
||||
it('should support filtering by between two dates', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'January 01 2020', body: 'January 01 2020', user_created_time: Date.parse('2020-01-01') });
|
||||
const n2 = await Note.save({ title: 'February 15 2020', body: 'February 15 2020', user_created_time: Date.parse('2020-02-15') });
|
||||
const n3 = await Note.save({ title: 'March 25 2019', body: 'March 25 2019', user_created_time: Date.parse('2019-03-25') });
|
||||
const n4 = await Note.save({ title: 'March 01 2018', body: 'March 01 2018', user_created_time: Date.parse('2018-03-01') });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('created:20200101 -created:20200220');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('created:201901 -created:202002');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('created:2018 -created:2019');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n4.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by created with smart value: day', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this', body: 'today', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'day'), 10) });
|
||||
const n2 = await Note.save({ title: 'I made this', body: 'yesterday', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'day'), 10) });
|
||||
const n3 = await Note.save({ title: 'I made this', body: 'day before yesterday', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'day'), 10) });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('created:day-0');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('created:day-1');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('created:day-2');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by created with smart value: week', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this', body: 'this week', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'week'), 10) });
|
||||
const n2 = await Note.save({ title: 'I made this', body: 'the week before', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'week'), 10) });
|
||||
const n3 = await Note.save({ title: 'I made this', body: 'before before week', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'week'), 10) });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('created:week-0');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('created:week-1');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('created:week-2');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by created with smart value: month', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this', body: 'this month', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'month'), 10) });
|
||||
const n2 = await Note.save({ title: 'I made this', body: 'the month before', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'month'), 10) });
|
||||
const n3 = await Note.save({ title: 'I made this', body: 'before before month', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'month'), 10) });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('created:month-0');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('created:month-1');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('created:month-2');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by created with smart value: year', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this', body: 'this year', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'year'), 10) });
|
||||
const n2 = await Note.save({ title: 'I made this', body: 'the year before', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'year'), 10) });
|
||||
const n3 = await Note.save({ title: 'I made this', body: 'before before year', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'year'), 10) });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('created:year-0');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('created:year-1');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('created:year-2');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by updated date', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I updated this on', body: 'May 20 2020', updated_time: Date.parse('2020-05-20'), user_updated_time: Date.parse('2020-05-20') }, { autoTimestamp: false });
|
||||
const n2 = await Note.save({ title: 'I updated this on', body: 'May 19 2020', updated_time: Date.parse('2020-05-19'), user_updated_time: Date.parse('2020-05-19') }, { autoTimestamp: false });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('updated:20200520');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('updated:20200519');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by updated with smart value: day', (async () => {
|
||||
let rows;
|
||||
const today = parseInt(time.goBackInTime(Date.now(), 0, 'day'), 10);
|
||||
const yesterday = parseInt(time.goBackInTime(Date.now(), 1, 'day'), 10);
|
||||
const dayBeforeYesterday = parseInt(time.goBackInTime(Date.now(), 2, 'day'), 10);
|
||||
const n1 = await Note.save({ title: 'I made this', body: 'today', updated_time: today, user_updated_time: today }, { autoTimestamp: false });
|
||||
const n11 = await Note.save({ title: 'I also made this', body: 'today', updated_time: today, user_updated_time: today }, { autoTimestamp: false });
|
||||
|
||||
const n2 = await Note.save({ title: 'I made this', body: 'yesterday', updated_time: yesterday, user_updated_time: yesterday }, { autoTimestamp: false });
|
||||
const n3 = await Note.save({ title: 'I made this', body: 'day before yesterday', updated_time: dayBeforeYesterday ,user_updated_time: dayBeforeYesterday }, { autoTimestamp: false });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('updated:day-0');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n11.id);
|
||||
|
||||
rows = await engine.search('updated:day-1');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n11.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('updated:day-2');
|
||||
expect(rows.length).toBe(4);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n11.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by type todo', (async () => {
|
||||
let rows;
|
||||
const t1 = await Note.save({ title: 'This is a ', body: 'todo', is_todo: 1 });
|
||||
const t2 = await Note.save({ title: 'This is another', body: 'todo but completed', is_todo: 1, todo_completed: 1590085027710 });
|
||||
const t3 = await Note.save({ title: 'This is NOT a ', body: 'todo' });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('type:todo');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(t1.id);
|
||||
expect(ids(rows)).toContain(t2.id);
|
||||
|
||||
rows = await engine.search('any:1 type:todo');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(t1.id);
|
||||
expect(ids(rows)).toContain(t2.id);
|
||||
|
||||
rows = await engine.search('iscompleted:1');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(t2.id);
|
||||
|
||||
rows = await engine.search('iscompleted:0');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(t1.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by type note', (async () => {
|
||||
let rows;
|
||||
const t1 = await Note.save({ title: 'This is a ', body: 'todo', is_todo: 1 });
|
||||
const t2 = await Note.save({ title: 'This is another', body: 'todo but completed', is_todo: 1, todo_completed: 1590085027710 });
|
||||
const t3 = await Note.save({ title: 'This is NOT a ', body: 'todo' });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('type:note');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(t3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by due date', (async () => {
|
||||
let rows;
|
||||
const toDo1 = await Note.save({ title: 'ToDo 1', body: 'todo', is_todo: 1, todo_due: Date.parse('2021-04-27') });
|
||||
const toDo2 = await Note.save({ title: 'ToDo 2', body: 'todo', is_todo: 1, todo_due: Date.parse('2021-03-17') });
|
||||
const note1 = await Note.save({ title: 'Note 1', body: 'Note' });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('due:20210425');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(toDo1.id);
|
||||
|
||||
rows = await engine.search('-due:20210425');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(toDo2.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by due with smart value: day', (async () => {
|
||||
let rows;
|
||||
|
||||
const inThreeDays = parseInt(time.goForwardInTime(Date.now(), 3, 'day'), 10);
|
||||
const inSevenDays = parseInt(time.goForwardInTime(Date.now(), 7, 'day'), 10);
|
||||
const threeDaysAgo = parseInt(time.goBackInTime(Date.now(), 3, 'day'), 10);
|
||||
const sevenDaysAgo = parseInt(time.goBackInTime(Date.now(), 7, 'day'), 10);
|
||||
|
||||
const toDo1 = await Note.save({ title: 'ToDo + 3 day', body: 'toto', is_todo: 1, todo_due: inThreeDays });
|
||||
const toDo2 = await Note.save({ title: 'ToDo + 7 day', body: 'toto', is_todo: 1, todo_due: inSevenDays });
|
||||
const toDo3 = await Note.save({ title: 'ToDo - 3 day', body: 'toto', is_todo: 1, todo_due: threeDaysAgo });
|
||||
const toDo4 = await Note.save({ title: 'ToDo - 7 day', body: 'toto', is_todo: 1, todo_due: sevenDaysAgo });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('due:day-4');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(toDo1.id);
|
||||
expect(ids(rows)).toContain(toDo2.id);
|
||||
expect(ids(rows)).toContain(toDo3.id);
|
||||
|
||||
rows = await engine.search('-due:day-4');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(toDo4.id);
|
||||
|
||||
rows = await engine.search('-due:day+4');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(toDo1.id);
|
||||
expect(ids(rows)).toContain(toDo3.id);
|
||||
expect(ids(rows)).toContain(toDo4.id);
|
||||
|
||||
rows = await engine.search('due:day+4');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(toDo2.id);
|
||||
|
||||
rows = await engine.search('due:day-4 -due:day+4');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(toDo1.id);
|
||||
expect(ids(rows)).toContain(toDo3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by latitude, longitude, altitude', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this', body: 'this week', latitude: 12.97, longitude: 88.88, altitude: 69.96 });
|
||||
const n2 = await Note.save({ title: 'I made this', body: 'the week before', latitude: 42.11, longitude: 77.77, altitude: 42.00 });
|
||||
const n3 = await Note.save({ title: 'I made this', body: 'before before week', latitude: 82.01, longitude: 66.66, altitude: 13.13 });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search('latitude:13.5');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('-latitude:40');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('latitude:13 -latitude:80');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('altitude:13.5');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('-altitude:80.12');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('longitude:70 -longitude:80');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('latitude:20 longitude:50 altitude:40');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('any:1 latitude:20 longitude:50 altitude:40');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
}));
|
||||
|
||||
it('should support filtering by resource MIME type', (async () => {
|
||||
let rows;
|
||||
const service = new ResourceService();
|
||||
// console.log(testImagePath)
|
||||
const folder1 = await Folder.save({ title: 'folder1' });
|
||||
let n1 = await Note.save({ title: 'I have a picture', body: 'Im awesome', parent_id: folder1.id });
|
||||
const n2 = await Note.save({ title: 'Boring note 1', body: 'I just have text', parent_id: folder1.id });
|
||||
const n3 = await Note.save({ title: 'Boring note 2', body: 'me too', parent_id: folder1.id });
|
||||
let n4 = await Note.save({ title: 'A picture?', body: 'pfff, I have a pdf', parent_id: folder1.id });
|
||||
await engine.syncTables();
|
||||
|
||||
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
n1 = await shim.attachFileToNote(n1, `${__dirname}/../tests/support/photo.jpg`);
|
||||
// const resource1 = (await Resource.all())[0];
|
||||
|
||||
n4 = await shim.attachFileToNote(n4, `${__dirname}/../tests/support/welcome.pdf`);
|
||||
|
||||
await service.indexNoteResources();
|
||||
|
||||
rows = await engine.search('resource:image/jpeg');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('resource:image/*');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('resource:application/pdf');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n4.id);
|
||||
|
||||
rows = await engine.search('-resource:image/jpeg');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
expect(ids(rows)).toContain(n4.id);
|
||||
|
||||
rows = await engine.search('any:1 resource:application/pdf resource:image/jpeg');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n4.id);
|
||||
}));
|
||||
|
||||
it('should ignore dashes in a word', (async () => {
|
||||
const n0 = await Note.save({ title: 'doesnotwork' });
|
||||
const n1 = await Note.save({ title: 'does not work' });
|
||||
const n2 = await Note.save({ title: 'does-not-work' });
|
||||
const n3 = await Note.save({ title: 'does_not_work' });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
let rows = await engine.search('does-not-work');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('does not work');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('"does not work"');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('title:does-not-work');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('doesnotwork');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n0.id);
|
||||
|
||||
}));
|
||||
|
||||
it('should support filtering by sourceurl', (async () => {
|
||||
const n0 = await Note.save({ title: 'n0', source_url: 'https://discourse.joplinapp.org' });
|
||||
const n1 = await Note.save({ title: 'n1', source_url: 'https://google.com' });
|
||||
const n2 = await Note.save({ title: 'n2', source_url: 'https://reddit.com' });
|
||||
const n3 = await Note.save({ title: 'n3', source_url: 'https://joplinapp.org' });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
let rows = await engine.search('sourceurl:https://joplinapp.org');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('sourceurl:https://google.com');
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
|
||||
rows = await engine.search('any:1 sourceurl:https://google.com sourceurl:https://reddit.com');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
rows = await engine.search('-sourceurl:https://google.com');
|
||||
expect(rows.length).toBe(3);
|
||||
expect(ids(rows)).toContain(n0.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
rows = await engine.search('sourceurl:*joplinapp.org');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n0.id);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
|
||||
}));
|
||||
|
||||
it('should support negating notebooks', (async () => {
|
||||
|
||||
const folder1 = await Folder.save({ title: 'folder1' });
|
||||
let n1 = await Note.save({ title: 'task1', body: 'foo', parent_id: folder1.id });
|
||||
let n2 = await Note.save({ title: 'task2', body: 'bar', parent_id: folder1.id });
|
||||
|
||||
|
||||
const folder2 = await Folder.save({ title: 'folder2' });
|
||||
let n3 = await Note.save({ title: 'task3', body: 'baz', parent_id: folder2.id });
|
||||
let n4 = await Note.save({ title: 'task4', body: 'blah', parent_id: folder2.id });
|
||||
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
let rows = await engine.search('-notebook:folder1');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n3.id);
|
||||
expect(ids(rows)).toContain(n4.id);
|
||||
|
||||
|
||||
rows = await engine.search('-notebook:folder2');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
}));
|
||||
|
||||
it('should support both inclusion and exclusion of notebooks together', (async () => {
|
||||
|
||||
const parentFolder = await Folder.save({ title: 'parent' });
|
||||
let n1 = await Note.save({ title: 'task1', body: 'foo', parent_id: parentFolder.id });
|
||||
let n2 = await Note.save({ title: 'task2', body: 'bar', parent_id: parentFolder.id });
|
||||
|
||||
|
||||
const subFolder = await Folder.save({ title: 'child', parent_id: parentFolder.id });
|
||||
let n3 = await Note.save({ title: 'task3', body: 'baz', parent_id: subFolder.id });
|
||||
let n4 = await Note.save({ title: 'task4', body: 'blah', parent_id: subFolder.id });
|
||||
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
let rows = await engine.search('notebook:parent -notebook:child');
|
||||
expect(rows.length).toBe(2);
|
||||
expect(ids(rows)).toContain(n1.id);
|
||||
expect(ids(rows)).toContain(n2.id);
|
||||
|
||||
}));
|
||||
|
||||
it('should support filtering by note id', (async () => {
|
||||
let rows;
|
||||
const note1 = await Note.save({ title: 'Note 1', body: 'body' });
|
||||
const note2 = await Note.save({ title: 'Note 2', body: 'body' });
|
||||
const note3 = await Note.save({ title: 'Note 3', body: 'body' });
|
||||
await engine.syncTables();
|
||||
|
||||
rows = await engine.search(`id:${note1.id}`);
|
||||
expect(rows.length).toBe(1);
|
||||
expect(rows.map(r=>r.id)).toContain(note1.id);
|
||||
|
||||
rows = await engine.search(`any:1 id:${note1.id} id:${note2.id}`);
|
||||
expect(rows.length).toBe(2);
|
||||
expect(rows.map(r=>r.id)).toContain(note1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(note2.id);
|
||||
|
||||
rows = await engine.search(`any:0 id:${note1.id} id:${note2.id}`);
|
||||
expect(rows.length).toBe(0);
|
||||
|
||||
rows = await engine.search(`-id:${note2.id}`);
|
||||
expect(rows.length).toBe(2);
|
||||
expect(rows.map(r=>r.id)).toContain(note1.id);
|
||||
expect(rows.map(r=>r.id)).toContain(note3.id);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
const {main} = require('./syncTargetUtils');
|
||||
const {main} = require('@joplin/lib/testing/syncTargetUtils');
|
||||
|
||||
const syncTargetType = process.argv.length <= 2 ? 'normal' : process.argv[2];
|
||||
|
||||
|
41
packages/app-cli/tests/testUtils.ts
Normal file
41
packages/app-cli/tests/testUtils.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import PluginRunner from '../app/services/plugins/PluginRunner';
|
||||
|
||||
export interface PluginServiceOptions {
|
||||
getState?(): Record<string, any>;
|
||||
}
|
||||
|
||||
export function newPluginService(appVersion = '1.4', options: PluginServiceOptions = null): PluginService {
|
||||
options = options || {};
|
||||
|
||||
const runner = new PluginRunner();
|
||||
const service = new PluginService();
|
||||
service.initialize(
|
||||
appVersion,
|
||||
{
|
||||
joplin: {},
|
||||
},
|
||||
runner,
|
||||
{
|
||||
dispatch: () => {},
|
||||
getState: options.getState ? options.getState : () => {},
|
||||
}
|
||||
);
|
||||
return service;
|
||||
}
|
||||
|
||||
export function newPluginScript(script: string) {
|
||||
return `
|
||||
/* joplin-manifest:
|
||||
{
|
||||
"id": "org.joplinapp.plugins.PluginTest",
|
||||
"manifest_version": 1,
|
||||
"app_min_version": "1.4",
|
||||
"name": "JS Bundle test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
|
||||
${script}
|
||||
`;
|
||||
}
|
@@ -166,6 +166,7 @@ export default class InteropServiceHelper {
|
||||
exportOptions.format = module.format;
|
||||
// exportOptions.modulePath = module.path;
|
||||
if (options.plugins) exportOptions.plugins = options.plugins;
|
||||
exportOptions.customCss = options.customCss;
|
||||
exportOptions.target = module.target;
|
||||
exportOptions.includeConflicts = !!options.includeConflicts;
|
||||
if (options.sourceFolderIds) exportOptions.sourceFolderIds = options.sourceFolderIds;
|
||||
|
@@ -74,6 +74,7 @@ const commands = [
|
||||
require('./gui/MainScreen/commands/toggleNoteList'),
|
||||
require('./gui/MainScreen/commands/toggleSideBar'),
|
||||
require('./gui/MainScreen/commands/toggleVisiblePanes'),
|
||||
require('./gui/MainScreen/commands/showPrompt'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteBody'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
|
||||
require('./gui/NoteEditor/commands/showLocalSearch'),
|
||||
@@ -96,6 +97,7 @@ const globalCommands = [
|
||||
require('./commands/stopExternalEditing'),
|
||||
require('./commands/toggleExternalEditing'),
|
||||
require('./commands/toggleSafeMode'),
|
||||
require('./commands/restoreNoteRevision'),
|
||||
require('@joplin/lib/commands/historyBackward'),
|
||||
require('@joplin/lib/commands/historyForward'),
|
||||
require('@joplin/lib/commands/synchronize'),
|
||||
|
20
packages/app-desktop/commands/restoreNoteRevision.ts
Normal file
20
packages/app-desktop/commands/restoreNoteRevision.ts
Normal 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);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
@@ -13,8 +13,13 @@ import { PluginItem } from './PluginBox';
|
||||
import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import useOnInstallHandler, { OnPluginSettingChangeEvent } from './useOnInstallHandler';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import StyledMessage from '../../../style/StyledMessage';
|
||||
import StyledLink from '../../../style/StyledLink';
|
||||
const { space } = require('styled-system');
|
||||
|
||||
const logger = Logger.create('PluginState');
|
||||
|
||||
const maxWidth: number = 320;
|
||||
|
||||
const Root = styled.div`
|
||||
@@ -32,6 +37,11 @@ const ToolsButton = styled(Button)`
|
||||
margin-right: 6px;
|
||||
`;
|
||||
|
||||
const RepoApiErrorMessage = styled(StyledMessage)`
|
||||
max-width: ${props => props.maxWidth}px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
value: any;
|
||||
themeId: number;
|
||||
@@ -84,6 +94,8 @@ export default function(props: Props) {
|
||||
const [manifestsLoaded, setManifestsLoaded] = useState<boolean>(false);
|
||||
const [updatingPluginsIds, setUpdatingPluginIds] = useState<Record<string, boolean>>({});
|
||||
const [canBeUpdatedPluginIds, setCanBeUpdatedPluginIds] = useState<Record<string, boolean>>({});
|
||||
const [repoApiError, setRepoApiError] = useState<Error>(null);
|
||||
const [fetchManifestTime, setFetchManifestTime] = useState<number>(Date.now());
|
||||
|
||||
const pluginService = PluginService.instance();
|
||||
|
||||
@@ -96,9 +108,25 @@ export default function(props: Props) {
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function fetchManifests() {
|
||||
await repoApi().loadManifests();
|
||||
setManifestsLoaded(false);
|
||||
setRepoApiError(null);
|
||||
|
||||
let loadError: Error = null;
|
||||
try {
|
||||
await repoApi().initialize();
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
loadError = error;
|
||||
}
|
||||
|
||||
if (cancelled) return;
|
||||
setManifestsLoaded(true);
|
||||
|
||||
if (loadError) {
|
||||
setManifestsLoaded(false);
|
||||
setRepoApiError(loadError);
|
||||
} else {
|
||||
setManifestsLoaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
void fetchManifests();
|
||||
@@ -106,7 +134,7 @@ export default function(props: Props) {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [fetchManifestTime]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!manifestsLoaded) return () => {};
|
||||
@@ -252,7 +280,7 @@ export default function(props: Props) {
|
||||
|
||||
function renderSearchArea() {
|
||||
return (
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<div style={{ marginBottom: 0 }}>
|
||||
<SearchPlugins
|
||||
disabled={!manifestsLoaded}
|
||||
maxWidth={maxWidth}
|
||||
@@ -268,11 +296,18 @@ export default function(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderRepoApiError() {
|
||||
if (!repoApiError) return null;
|
||||
|
||||
return <RepoApiErrorMessage maxWidth={maxWidth} type="error">{_('Could not connect to plugin repository')} - <StyledLink href="#" onClick={() => { setFetchManifestTime(Date.now()); }}>{_('Try again')}</StyledLink></RepoApiErrorMessage>;
|
||||
}
|
||||
|
||||
function renderBottomArea() {
|
||||
if (searchQuery) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderRepoApiError()}
|
||||
<div style={{ display: 'flex', flexDirection: 'row', maxWidth }}>
|
||||
<ToolsButton tooltip={_('Plugin tools')} iconName="fas fa-cog" level={ButtonLevel.Secondary} onClick={onToolsClick}/>
|
||||
<div style={{ display: 'flex', flex: 1 }}>
|
||||
|
@@ -101,7 +101,7 @@ export default function(props: Props) {
|
||||
onChange={onChange}
|
||||
onSearchButtonClick={onSearchButtonClick}
|
||||
searchStarted={searchStarted}
|
||||
placeholder={_('Search for plugins...')}
|
||||
placeholder={props.disabled ? _('Please wait...') : _('Search for plugins...')}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -256,7 +256,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
<div>
|
||||
<div style={containerStyle}>
|
||||
{
|
||||
<div style={{ backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
|
||||
<div className="alert alert-warning" style={{ backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
|
||||
<p style={theme.textStyle}>
|
||||
<span>{_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')}</span>{' '}
|
||||
<a
|
||||
|
@@ -34,6 +34,7 @@ import ShareFolderDialog from '../ShareFolderDialog/ShareFolderDialog';
|
||||
import { ShareInvitation } from '@joplin/lib/services/share/reducer';
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { PromptDialog } = require('../PromptDialog.min.js');
|
||||
@@ -134,6 +135,7 @@ const commands = [
|
||||
require('./commands/openNote'),
|
||||
require('./commands/openFolder'),
|
||||
require('./commands/openTag'),
|
||||
require('./commands/showPrompt'),
|
||||
];
|
||||
|
||||
class MainScreenComponent extends React.Component<Props, State> {
|
||||
@@ -234,6 +236,14 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
try {
|
||||
output = loadLayout(Object.keys(userLayout).length ? userLayout : null, defaultLayout, rootLayoutSize);
|
||||
|
||||
// For unclear reasons, layout items sometimes end up witout a key.
|
||||
// In that case, we can't do anything with them, so remove them
|
||||
// here. It could be due to the deprecated plugin API, which allowed
|
||||
// creating panel without a key, although in this case it should
|
||||
// have been set automatically.
|
||||
// https://github.com/laurent22/joplin/issues/4926
|
||||
output = removeKeylessItems(output);
|
||||
|
||||
if (!findItemByKey(output, 'sideBar') || !findItemByKey(output, 'noteList') || !findItemByKey(output, 'editor')) {
|
||||
throw new Error('"sideBar", "noteList" and "editor" must be present in the layout');
|
||||
}
|
||||
|
41
packages/app-desktop/gui/MainScreen/commands/showPrompt.ts
Normal file
41
packages/app-desktop/gui/MainScreen/commands/showPrompt.ts
Normal 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,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@@ -18,6 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
|
||||
},
|
||||
});
|
||||
},
|
||||
enabledCondition: 'folderIsShareRootAndOwnedByUser || !folderIsShared',
|
||||
enabledCondition: 'joplinServerConnected && (folderIsShareRootAndOwnedByUser || !folderIsShared)',
|
||||
};
|
||||
};
|
||||
|
@@ -18,5 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
|
||||
},
|
||||
});
|
||||
},
|
||||
enabledCondition: 'joplinServerConnected && someNotesSelected',
|
||||
};
|
||||
};
|
||||
|
@@ -90,6 +90,7 @@ interface Props {
|
||||
['spellChecker.enabled']: boolean;
|
||||
['spellChecker.language']: string;
|
||||
plugins: PluginStates;
|
||||
customCss: string;
|
||||
}
|
||||
|
||||
const commandNames: string[] = menuCommandNames();
|
||||
@@ -313,7 +314,10 @@ function useMenu(props: Props) {
|
||||
await InteropServiceHelper.export(
|
||||
(action: any) => props.dispatch(action),
|
||||
module,
|
||||
{ plugins: props.plugins }
|
||||
{
|
||||
plugins: props.plugins,
|
||||
customCss: props.customCss,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -522,6 +526,14 @@ function useMenu(props: Props) {
|
||||
click: () => { bridge().electronApp().hide(); },
|
||||
} : noItem,
|
||||
|
||||
shim.isMac() ? {
|
||||
role: 'hideothers',
|
||||
} : noItem,
|
||||
|
||||
shim.isMac() ? {
|
||||
role: 'unhide',
|
||||
} : noItem,
|
||||
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
@@ -695,11 +707,18 @@ function useMenu(props: Props) {
|
||||
},
|
||||
],
|
||||
},
|
||||
folder: {
|
||||
label: _('Note&book'),
|
||||
submenu: [
|
||||
menuItemDic.showShareFolderDialog,
|
||||
],
|
||||
},
|
||||
note: {
|
||||
label: _('&Note'),
|
||||
submenu: [
|
||||
menuItemDic.toggleExternalEditing,
|
||||
menuItemDic.setTags,
|
||||
menuItemDic.showShareNoteDialog,
|
||||
separator(),
|
||||
menuItemDic.showNoteContentProperties,
|
||||
],
|
||||
@@ -818,6 +837,7 @@ function useMenu(props: Props) {
|
||||
rootMenus.edit,
|
||||
rootMenus.view,
|
||||
rootMenus.go,
|
||||
rootMenus.folder,
|
||||
rootMenus.note,
|
||||
rootMenus.tools,
|
||||
rootMenus.help,
|
||||
@@ -852,7 +872,7 @@ function useMenu(props: Props) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
};
|
||||
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled'], props.plugins]);
|
||||
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled'], props.plugins, props.customCss]);
|
||||
|
||||
useMenuStates(menu, props);
|
||||
|
||||
@@ -909,6 +929,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
['spellChecker.language']: state.settings['spellChecker.language'],
|
||||
['spellChecker.enabled']: state.settings['spellChecker.enabled'],
|
||||
plugins: state.pluginService.plugins,
|
||||
customCss: state.customCss,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -13,6 +13,7 @@ interface MultiNoteActionsProps {
|
||||
watchedNoteFiles: string[];
|
||||
plugins: PluginStates;
|
||||
inConflictFolder: boolean;
|
||||
customCss: string;
|
||||
}
|
||||
|
||||
function styles_(props: MultiNoteActionsProps) {
|
||||
@@ -53,6 +54,7 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) {
|
||||
watchedNoteFiles: props.watchedNoteFiles,
|
||||
plugins: props.plugins,
|
||||
inConflictFolder: props.inConflictFolder,
|
||||
customCss: props.customCss,
|
||||
});
|
||||
|
||||
const itemComps = [];
|
||||
|
@@ -24,7 +24,7 @@ interface KeyToLabelMap {
|
||||
let markupToHtml_: any = null;
|
||||
function markupToHtml() {
|
||||
if (markupToHtml_) return markupToHtml_;
|
||||
markupToHtml_ = markupLanguageUtils.newMarkupToHtml({});
|
||||
markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
|
||||
return markupToHtml_;
|
||||
}
|
||||
|
||||
|
@@ -224,10 +224,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
textHeading: () => addListItem('## ', ''),
|
||||
textHorizontalRule: () => addListItem('* * *'),
|
||||
'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);
|
||||
} else if (editorRef.current.commandExists(value.name)) {
|
||||
editorRef.current.execCommand(value.name);
|
||||
} else {
|
||||
reg.logger().warn('CodeMirror execCommand: unsupported command: ', value.name);
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import useEditorSearch from './utils/useEditorSearch';
|
||||
import useJoplinMode from './utils/useJoplinMode';
|
||||
import useKeymap from './utils/useKeymap';
|
||||
import useExternalPlugins from './utils/useExternalPlugins';
|
||||
import useJoplinCommands from './utils/useJoplinCommands';
|
||||
|
||||
import 'codemirror/keymap/emacs';
|
||||
import 'codemirror/keymap/vim';
|
||||
@@ -107,6 +108,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
useJoplinMode(CodeMirror);
|
||||
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
|
||||
useKeymap(CodeMirror);
|
||||
useJoplinCommands(CodeMirror);
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return editor;
|
||||
|
@@ -2,19 +2,76 @@ import { modifyListLines } from './useCursorUtils';
|
||||
|
||||
describe('useCursorUtils', () => {
|
||||
|
||||
const listWithDashes = `- item1
|
||||
- item2
|
||||
- item3`;
|
||||
const listWithDashes = [
|
||||
'- item1',
|
||||
'- item2',
|
||||
'- item3',
|
||||
];
|
||||
|
||||
const listNoDashes = `item1
|
||||
item2
|
||||
item3`;
|
||||
const listWithNoPrefixes = [
|
||||
'item1',
|
||||
'item2',
|
||||
'item3',
|
||||
];
|
||||
|
||||
test('should remove "- " from beggining of each line of input string', () => {
|
||||
expect(JSON.stringify(modifyListLines(listWithDashes.split('\n'), 0, '- '))).toBe(JSON.stringify(listNoDashes.split('\n')));
|
||||
const listWithNumbers = [
|
||||
'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', () => {
|
||||
expect(JSON.stringify(modifyListLines(listNoDashes.split('\n'), 0, '- '))).toBe(JSON.stringify(listWithDashes.split('\n')));
|
||||
test('should add "- " at the beginning of each line of the input string', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@@ -1,20 +1,27 @@
|
||||
import markdownUtils from '@joplin/lib/markdownUtils';
|
||||
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++) {
|
||||
const line = lines[j];
|
||||
if (!line && j === lines.length - 1) continue;
|
||||
// Only add the list token if it's not already there
|
||||
// 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}`;
|
||||
num++;
|
||||
} else {
|
||||
lines[j] = listSymbol + line;
|
||||
const listToken = markdownUtils.extractListToken(line);
|
||||
lines[j] = line.substr(listToken.length, line.length - listToken.length);
|
||||
}
|
||||
} 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;
|
||||
|
@@ -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];
|
||||
});
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps } from '../../utils/types';
|
||||
import { resourcesStatus, commandAttachFileToBody, handlePasteEvent, processPastedHtml } from '../../utils/resourceHandling';
|
||||
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps, ResourceInfos } from '../../utils/types';
|
||||
import { resourcesStatus, commandAttachFileToBody, handlePasteEvent, processPastedHtml, attachedResources } from '../../utils/resourceHandling';
|
||||
import useScroll from './utils/useScroll';
|
||||
import styles_ from './styles';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
@@ -20,6 +20,7 @@ const taboverride = require('taboverride');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import setupToolbarButtons from './utils/setupToolbarButtons';
|
||||
import { plainTextToHtml } from '@joplin/lib/htmlUtils';
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
@@ -66,6 +67,26 @@ function newBlockSource(language: string = '', content: string = ''): any {
|
||||
};
|
||||
}
|
||||
|
||||
// In TinyMCE 5.2, when setting the body to '<div id="rendered-md"></div>',
|
||||
// it would end up as '<div id="rendered-md"><br/></div>' once rendered
|
||||
// (an additional <br/> was inserted).
|
||||
//
|
||||
// This behaviour was "fixed" later on, possibly in 5.6, which has this change:
|
||||
//
|
||||
// - Fixed getContent with text format returning a new line when the editor is empty #TINY-6281
|
||||
//
|
||||
// The problem is that the list plugin was, unknown to me, relying on this <br/>
|
||||
// being present. Without it, trying to add a bullet point or checkbox on an
|
||||
// empty document, does nothing. The exact reason for this is unclear
|
||||
// so as a workaround we manually add this <br> for empty documents,
|
||||
// which fixes the issue.
|
||||
//
|
||||
// Perhaps upgrading the list plugin (which is a fork of TinyMCE own list plugin)
|
||||
// would help?
|
||||
function awfulBrHack(html: string): string {
|
||||
return html === '<div id="rendered-md"></div>' ? '<div id="rendered-md"><br/></div>' : html;
|
||||
}
|
||||
|
||||
function findEditableContainer(node: any): any {
|
||||
while (node) {
|
||||
if (node.classList && node.classList.contains('joplin-editable')) return node;
|
||||
@@ -127,6 +148,12 @@ const joplinCommandToTinyMceCommands: JoplinCommandToTinyMceCommands = {
|
||||
'search': { name: 'SearchReplace' },
|
||||
};
|
||||
|
||||
interface LastOnChangeEventInfo {
|
||||
content: string;
|
||||
resourceInfos: ResourceInfos;
|
||||
contentKey: string;
|
||||
}
|
||||
|
||||
let loadedCssFiles_: string[] = [];
|
||||
let loadedJsFiles_: string[] = [];
|
||||
let dispatchDidUpdateIID_: any = null;
|
||||
@@ -147,7 +174,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const markupToHtml = useRef(null);
|
||||
markupToHtml.current = props.markupToHtml;
|
||||
|
||||
const lastOnChangeEventInfo = useRef<any>({
|
||||
const lastOnChangeEventInfo = useRef<LastOnChangeEventInfo>({
|
||||
content: null,
|
||||
resourceInfos: null,
|
||||
contentKey: null,
|
||||
@@ -338,7 +365,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
element.id = script.id;
|
||||
|
||||
element.onload = () => {
|
||||
resolve();
|
||||
resolve(null);
|
||||
};
|
||||
|
||||
document.getElementsByTagName('head')[0].appendChild(element);
|
||||
@@ -807,6 +834,25 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
function resourceInfosEqual(ri1: ResourceInfos, ri2: ResourceInfos): boolean {
|
||||
if (ri1 && !ri2 || !ri1 && ri2) return false;
|
||||
if (!ri1 && !ri2) return true;
|
||||
|
||||
const keys1 = Object.keys(ri1);
|
||||
const keys2 = Object.keys(ri2);
|
||||
|
||||
if (keys1.length !== keys2.length) return false;
|
||||
|
||||
// The attachedResources() call that generates the ResourceInfos object
|
||||
// uses cache for the resource objects, so we can use strict equality
|
||||
// for comparison.
|
||||
for (const k of keys1) {
|
||||
if (ri1[k] !== ri2[k]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
|
||||
@@ -818,11 +864,13 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
let cancelled = false;
|
||||
|
||||
const loadContent = async () => {
|
||||
if (lastOnChangeEventInfo.current.content !== props.content || lastOnChangeEventInfo.current.resourceInfos !== props.resourceInfos) {
|
||||
const resourcesEqual = resourceInfosEqual(lastOnChangeEventInfo.current.resourceInfos, props.resourceInfos);
|
||||
|
||||
if (lastOnChangeEventInfo.current.content !== props.content || !resourcesEqual) {
|
||||
const result = await props.markupToHtml(props.contentMarkupLanguage, props.content, markupRenderOptions({ resourceInfos: props.resourceInfos }));
|
||||
if (cancelled) return;
|
||||
|
||||
editor.setContent(result.html);
|
||||
editor.setContent(awfulBrHack(result.html));
|
||||
|
||||
if (lastOnChangeEventInfo.current.contentKey !== props.contentKey) {
|
||||
// Need to clear UndoManager to avoid this problem:
|
||||
@@ -922,6 +970,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const contentMd = await prop_htmlToMarkdownRef.current(info.contentMarkupLanguage, info.editor.getContent(), info.contentOriginalCss);
|
||||
|
||||
lastOnChangeEventInfo.current.content = contentMd;
|
||||
lastOnChangeEventInfo.current.resourceInfos = await attachedResources(contentMd);
|
||||
|
||||
props_onChangeRef.current({
|
||||
changeId: info.changeId,
|
||||
@@ -1017,31 +1066,37 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
}
|
||||
|
||||
async function onPaste(event: any) {
|
||||
// We do not use the default pasting behaviour because the input has
|
||||
// to be processed in various ways.
|
||||
event.preventDefault();
|
||||
|
||||
const resourceMds = await handlePasteEvent(event);
|
||||
if (resourceMds.length) {
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resourceMds.join('\n'), markupRenderOptions({ bodyOnly: true }));
|
||||
editor.insertContent(result.html);
|
||||
} else {
|
||||
const pastedText = event.clipboardData.getData('text');
|
||||
const pastedText = event.clipboardData.getData('text/plain');
|
||||
|
||||
if (BaseItem.isMarkdownTag(pastedText)) { // Paste a link to a note
|
||||
event.preventDefault();
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, pastedText, markupRenderOptions({ bodyOnly: true }));
|
||||
editor.insertContent(result.html);
|
||||
} else { // Paste regular text
|
||||
// HACK: TinyMCE doesn't add an undo step when pasting, for unclear reasons
|
||||
// so we manually add it here. We also can't do it immediately it seems, or
|
||||
// else nothing is added to the stack, so do it on the next frame.
|
||||
|
||||
const pastedHtml = clipboard.readHTML();
|
||||
if (pastedHtml) {
|
||||
event.preventDefault();
|
||||
const pastedHtml = event.clipboardData.getData('text/html');
|
||||
if (pastedHtml) { // Handles HTML
|
||||
const modifiedHtml = await processPastedHtml(pastedHtml);
|
||||
editor.insertContent(modifiedHtml);
|
||||
} else { // Handles plain text
|
||||
pasteAsPlainText(pastedText);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => editor.undoManager.add());
|
||||
onChangeHandler();
|
||||
// This code before was necessary to get undo working after
|
||||
// pasting but it seems it's no longer necessary, so
|
||||
// removing it for now. We also couldn't do it immediately
|
||||
// it seems, or else nothing is added to the stack, so do it
|
||||
// on the next frame.
|
||||
//
|
||||
// window.requestAnimationFrame(() =>
|
||||
// editor.undoManager.add()); onChangeHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1060,6 +1115,13 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
onChangeHandler();
|
||||
}
|
||||
|
||||
function pasteAsPlainText(text: string = null) {
|
||||
const pastedText = text === null ? clipboard.readText() : text;
|
||||
if (pastedText) {
|
||||
editor.insertContent(plainTextToHtml(pastedText));
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyDown(event: any) {
|
||||
// It seems "paste as text" is handled automatically by
|
||||
// on Windows so the code below so we need to run the below
|
||||
@@ -1072,8 +1134,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
// it here and we don't need to do anything special in onPaste
|
||||
if (!shim.isWindows()) {
|
||||
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
|
||||
const pastedText = clipboard.readText();
|
||||
if (pastedText) editor.insertContent(pastedText);
|
||||
pasteAsPlainText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -156,10 +156,11 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
|
||||
const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, {
|
||||
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
|
||||
customCss: props.customCss,
|
||||
});
|
||||
|
||||
return markupToHtml.allAssets(markupLanguage, theme);
|
||||
}, [props.themeId]);
|
||||
}, [props.themeId, props.customCss]);
|
||||
|
||||
const handleProvisionalFlag = useCallback(() => {
|
||||
if (props.isProvisional) {
|
||||
@@ -458,6 +459,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
watchedNoteFiles={props.watchedNoteFiles}
|
||||
plugins={props.plugins}
|
||||
inConflictFolder={props.selectedFolderId === Folder.conflictFolderId()}
|
||||
customCss={props.customCss}
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -549,7 +551,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
{renderSearchBar()}
|
||||
</div>
|
||||
<div style={{ paddingLeft: theme.editorPaddingLeft, display: 'flex', flexDirection: 'row', alignItems: 'center', height: 40 }}>
|
||||
<div className="tag-bar" style={{ paddingLeft: theme.editorPaddingLeft, display: 'flex', flexDirection: 'row', alignItems: 'center', height: 40 }}>
|
||||
{renderTagButton()}
|
||||
{renderTagBar()}
|
||||
</div>
|
||||
|
@@ -91,7 +91,7 @@ export default function NoteTitleBar(props: Props) {
|
||||
}, []);
|
||||
|
||||
function renderTitleBarDate() {
|
||||
return <span style={styles.titleDate}>{time.formatMsToLocal(props.noteUserUpdatedTime)}</span>;
|
||||
return <span className="updated-time-label" style={styles.titleDate}>{time.formatMsToLocal(props.noteUserUpdatedTime)}</span>;
|
||||
}
|
||||
|
||||
function renderNoteToolbar() {
|
||||
@@ -104,6 +104,7 @@ export default function NoteTitleBar(props: Props) {
|
||||
return (
|
||||
<StyledRoot>
|
||||
<input
|
||||
className="title-input"
|
||||
type="text"
|
||||
ref={props.titleInputRef}
|
||||
placeholder={props.isProvisional ? _('Creating new %s...', props.noteIsTodo ? _('to-do') : _('note')) : ''}
|
||||
|
@@ -135,6 +135,13 @@ export async function processPastedHtml(html: string) {
|
||||
const allImageUrls: string[] = [];
|
||||
const mappedResources: Record<string, string> = {};
|
||||
|
||||
// When copying text from eg. GitHub, the HTML might contain non-breaking
|
||||
// spaces instead of regular spaces. If these non-breaking spaces are
|
||||
// inserted into the TinyMCE editor (using insertContent), they will be
|
||||
// dropped. So here we convert them to regular spaces.
|
||||
// https://stackoverflow.com/a/31790544/561309
|
||||
html = html.replace(/[\u202F\u00A0]/g, ' ');
|
||||
|
||||
htmlUtils.replaceImageUrls(html, (src: string) => {
|
||||
allImageUrls.push(src);
|
||||
});
|
||||
|
@@ -2,6 +2,8 @@
|
||||
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
|
||||
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
import { MarkupLanguage } from '@joplin/renderer';
|
||||
import { RenderResult, RenderResultPluginAsset } from '@joplin/renderer/MarkupToHtml';
|
||||
|
||||
export interface ToolbarButtonInfos {
|
||||
[key: string]: ToolbarButtonInfo;
|
||||
@@ -49,9 +51,9 @@ export interface NoteBodyEditorProps {
|
||||
onWillChange(event: any): void;
|
||||
onMessage(event: any): void;
|
||||
onScroll(event: any): void;
|
||||
markupToHtml: Function;
|
||||
markupToHtml: (markupLanguage: MarkupLanguage, markup: string, options: any)=> Promise<RenderResult>;
|
||||
htmlToMarkdown: Function;
|
||||
allAssets: Function;
|
||||
allAssets: (markupLanguage: MarkupLanguage)=> Promise<RenderResultPluginAsset[]>;
|
||||
disabled: boolean;
|
||||
dispatch: Function;
|
||||
noteToolbar: any;
|
||||
|
@@ -24,6 +24,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
||||
const markupToHtml = useMemo(() => {
|
||||
return markupLanguageUtils.newMarkupToHtml(deps.plugins, {
|
||||
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
|
||||
customCss: customCss || '',
|
||||
});
|
||||
}, [plugins]);
|
||||
|
||||
@@ -49,7 +50,6 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
||||
|
||||
const result = await markupToHtml.render(markupLanguage, md, theme, Object.assign({}, {
|
||||
codeTheme: theme.codeThemeCss,
|
||||
userCss: customCss || '',
|
||||
resources: resources,
|
||||
postMessageSyntax: 'ipcProxySendToHost',
|
||||
splitted: true,
|
||||
|
@@ -9,6 +9,7 @@ const { urlDecode } = require('@joplin/lib/string-utils');
|
||||
const urlUtils = require('@joplin/lib/urlUtils');
|
||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||
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) {
|
||||
return useCallback(async (event: any) => {
|
||||
@@ -51,8 +52,14 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
|
||||
|
||||
} else if (urlUtils.urlProtocol(msg)) {
|
||||
if (msg.indexOf('file://') === 0) {
|
||||
// When using the file:// protocol, openPath doesn't work (does nothing) with URL-encoded paths
|
||||
require('electron').shell.openPath(urlDecode(msg));
|
||||
// When using the file:// protocol, openPath doesn't work (does
|
||||
// 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 {
|
||||
require('electron').shell.openExternal(msg);
|
||||
}
|
||||
|
@@ -125,6 +125,7 @@ class NoteListComponent extends React.Component {
|
||||
watchedNoteFiles: this.props.watchedNoteFiles,
|
||||
plugins: this.props.plugins,
|
||||
inConflictFolder: this.props.selectedFolderId === Folder.conflictFolderId(),
|
||||
customCss: this.props.customCss,
|
||||
});
|
||||
|
||||
menu.popup(bridge().window());
|
||||
@@ -513,6 +514,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
noteSortOrder: state.settings['notes.sortOrder.field'],
|
||||
highlightedWords: state.highlightedWords,
|
||||
plugins: state.pluginService.plugins,
|
||||
customCss: state.customCss,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -54,12 +54,14 @@ export default function NoteListControls(props: Props) {
|
||||
return (
|
||||
<ButtonContainer>
|
||||
<StyledButton
|
||||
className="new-todo-button"
|
||||
tooltip={CommandService.instance().label('newTodo')}
|
||||
iconName="far fa-check-square"
|
||||
level={ButtonLevel.Primary}
|
||||
onClick={onNewTodoButtonClick}
|
||||
/>
|
||||
<StyledButton
|
||||
className="new-note-button"
|
||||
tooltip={CommandService.instance().label('newNote')}
|
||||
iconName="icon-note"
|
||||
level={ButtonLevel.Primary}
|
||||
|
@@ -110,6 +110,7 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
|
||||
let listItemTitleStyle = Object.assign({}, props.style.listItemTitle);
|
||||
listItemTitleStyle.paddingLeft = !item.is_todo ? hPadding : 4;
|
||||
if (item.is_shared) listItemTitleStyle.color = theme.colorWarn;
|
||||
if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, props.style.listItemTitleCompleted);
|
||||
|
||||
const displayTitle = Note.displayTitle(item);
|
||||
@@ -149,12 +150,21 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
color: theme.color,
|
||||
};
|
||||
const watchedIcon = props.isWatched ? null : <i style={watchedIconStyle} className={'fa fa-share-square'}></i>;
|
||||
const classNames = [
|
||||
'list-item-container',
|
||||
props.isSelected && 'selected',
|
||||
item.todo_completed && 'todo-completed',
|
||||
item.is_todo ? 'todo-list-item' : 'note-list-item',
|
||||
(props.index + 1) % 2 === 0 ? 'even' : 'odd',
|
||||
]
|
||||
.filter(e => !!e)
|
||||
.join(' ');
|
||||
|
||||
// Need to include "todo_completed" in key so that checkbox is updated when
|
||||
// item is changed via sync.
|
||||
return (
|
||||
<StyledRoot
|
||||
className="list-item-container"
|
||||
className={classNames}
|
||||
onDragOver={props.onNoteDragOver}
|
||||
onDrop={props.onNoteDrop}
|
||||
width={props.width}
|
||||
|
@@ -13,7 +13,7 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
|
||||
const { MarkupToHtml } = require('@joplin/renderer');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
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 markupLanguageUtils = require('../utils/markupLanguageUtils').default;
|
||||
|
||||
@@ -75,7 +75,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
this.setState({ restoring: true });
|
||||
await RevisionService.instance().importRevisionNote(this.state.note);
|
||||
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() {
|
||||
@@ -118,11 +118,11 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
|
||||
const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, {
|
||||
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
|
||||
customCss: this.props.customCss ? this.props.customCss : '',
|
||||
});
|
||||
|
||||
const result = await markupToHtml.render(markupLanguage, noteBody, theme, {
|
||||
codeTheme: theme.codeThemeCss,
|
||||
userCss: this.props.customCss ? this.props.customCss : '',
|
||||
resources: await shared.attachedResources(noteBody),
|
||||
postMessageSyntax: 'ipcProxySendToHost',
|
||||
});
|
||||
|
@@ -158,7 +158,7 @@ class NoteSearchBarComponent extends React.Component {
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={this.props.style}>
|
||||
<div className="note-search-bar" style={this.props.style}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
{closeButton}
|
||||
<input
|
||||
|
@@ -221,11 +221,11 @@ class PromptDialog extends React.Component {
|
||||
let inputComp = null;
|
||||
|
||||
if (this.props.inputType === 'datetime') {
|
||||
inputComp = <Datetime value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={momentObject => onDateTimeChange(momentObject)} />;
|
||||
inputComp = <Datetime className="datetime-picker" value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={momentObject => onDateTimeChange(momentObject)} />;
|
||||
} else if (this.props.inputType === 'tags') {
|
||||
inputComp = <CreatableSelect styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={event => onKeyDown(event)} />;
|
||||
inputComp = <CreatableSelect className="tag-selector" styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={event => onKeyDown(event)} />;
|
||||
} else if (this.props.inputType === 'dropdown') {
|
||||
inputComp = <Select styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={event => onKeyDown(event)} />;
|
||||
inputComp = <Select className="item-selector" styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={event => onKeyDown(event)} />;
|
||||
} else {
|
||||
inputComp = <input style={styles.input} ref={this.answerInput_} value={this.state.answer} type="text" onChange={event => onChange(event)} onKeyDown={event => onKeyDown(event)} />;
|
||||
}
|
||||
@@ -254,8 +254,8 @@ class PromptDialog extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.modalLayer}>
|
||||
<div style={styles.promptDialog}>
|
||||
<div className="modal-layer" style={styles.modalLayer}>
|
||||
<div className="modal-dialog" style={styles.promptDialog}>
|
||||
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
||||
<div style={{ display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor }}>
|
||||
{inputComp}
|
||||
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import useWindowResizeEvent from './utils/useWindowResizeEvent';
|
||||
import setLayoutItemProps from './utils/setLayoutItemProps';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './utils/useLayoutItemSizes';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize, calculateMaxSizeAvailableForItem, itemMinWidth, itemMinHeight } from './utils/useLayoutItemSizes';
|
||||
import validateLayout from './utils/validateLayout';
|
||||
import { Size, LayoutItem } from './utils/types';
|
||||
import { canMove, MoveDirection } from './utils/movements';
|
||||
@@ -11,9 +11,6 @@ import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRoot
|
||||
import { Resizable } from 're-resizable';
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const itemMinWidth = 20;
|
||||
const itemMinHeight = 20;
|
||||
|
||||
interface onResizeEvent {
|
||||
layout: LayoutItem;
|
||||
}
|
||||
@@ -35,7 +32,7 @@ function itemVisible(item: LayoutItem, moveMode: boolean) {
|
||||
return item.visible !== false;
|
||||
}
|
||||
|
||||
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
|
||||
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, resizedItemMaxSize: Size | null, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
|
||||
const style: any = {
|
||||
display: itemVisible(item, moveMode) ? 'flex' : 'none',
|
||||
flexDirection: item.direction,
|
||||
@@ -68,6 +65,8 @@ function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
|
||||
enable={enable}
|
||||
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
|
||||
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
|
||||
maxWidth={resizedItemMaxSize?.width}
|
||||
maxHeight={resizedItemMaxSize?.height}
|
||||
>
|
||||
{children}
|
||||
</Resizable>
|
||||
@@ -114,6 +113,7 @@ function ResizableLayout(props: Props) {
|
||||
key: item.key,
|
||||
initialWidth: sizes[item.key].width,
|
||||
initialHeight: sizes[item.key].height,
|
||||
maxSize: calculateMaxSizeAvailableForItem(item, parent, sizes),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ function ResizableLayout(props: Props) {
|
||||
setResizedItem(null);
|
||||
}
|
||||
|
||||
const resizedItemMaxSize = item.key === resizedItem?.key ? resizedItem.maxSize : null;
|
||||
if (!item.children) {
|
||||
const size = itemSize(item, parent, sizes, false);
|
||||
|
||||
@@ -155,7 +156,7 @@ function ResizableLayout(props: Props) {
|
||||
|
||||
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
|
||||
|
||||
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
|
||||
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
|
||||
} else {
|
||||
const childrenComponents = [];
|
||||
for (let i = 0; i < item.children.length; i++) {
|
||||
@@ -163,7 +164,7 @@ function ResizableLayout(props: Props) {
|
||||
childrenComponents.push(renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1));
|
||||
}
|
||||
|
||||
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
|
||||
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,30 @@
|
||||
import produce from 'immer';
|
||||
import iterateItems from './iterateItems';
|
||||
import { LayoutItem } from './types';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
interface ItemToRemove {
|
||||
parent: LayoutItem;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export default function(layout: LayoutItem): LayoutItem {
|
||||
const itemsToRemove: ItemToRemove[] = [];
|
||||
|
||||
const output = produce(layout, (layoutDraft: LayoutItem) => {
|
||||
iterateItems(layoutDraft, (itemIndex: number, item: LayoutItem, parent: LayoutItem) => {
|
||||
if (!item.key) itemsToRemove.push({ parent, index: itemIndex });
|
||||
return true;
|
||||
});
|
||||
|
||||
itemsToRemove.sort((a: ItemToRemove, b: ItemToRemove) => {
|
||||
return a.index > b.index ? -1 : +1;
|
||||
});
|
||||
|
||||
for (const item of itemsToRemove) {
|
||||
item.parent.children.splice(item.index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
return output !== layout ? validateLayout(output) : layout;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import useLayoutItemSizes, { itemSize } from './useLayoutItemSizes';
|
||||
import useLayoutItemSizes, { itemSize, calculateMaxSizeAvailableForItem } from './useLayoutItemSizes';
|
||||
import { LayoutItem, LayoutItemDirection } from './types';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import validateLayout from './validateLayout';
|
||||
@@ -138,4 +138,219 @@ describe('useLayoutItemSizes', () => {
|
||||
expect(itemSize(parent.children[1], parent, sizes, false)).toEqual({ width: 95, height: 50 });
|
||||
});
|
||||
|
||||
test('should decrease size of the largest item if the total size would be larger than the container', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
minWidth: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(50);
|
||||
expect(sizes.col2.width).toBe(100);
|
||||
expect(sizes.col3.width).toBe(50);
|
||||
});
|
||||
|
||||
test('should not allow a minWidth of 0, should still make space for the item', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 210,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
minWidth: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(160);
|
||||
expect(sizes.col2.width).toBe(40); // default minWidth is 40
|
||||
});
|
||||
|
||||
test('should ignore invisible items when counting remaining size', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 110,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
minWidth: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(0);
|
||||
expect(sizes.col2.width).toBe(100);
|
||||
expect(sizes.col3.width).toBe(100);
|
||||
});
|
||||
|
||||
test('should ignore invisible items when selecting largest child', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 110,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
key: 'col4',
|
||||
minWidth: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(0);
|
||||
expect(sizes.col2.width).toBe(100);
|
||||
expect(sizes.col3.width).toBe(50);
|
||||
expect(sizes.col4.width).toBe(50);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('calculateMaxSizeAvailableForItem', () => {
|
||||
|
||||
test('should give maximum available space this item can take up during resizing', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
|
||||
const maxSize2 = calculateMaxSizeAvailableForItem(layout.children[1], layout, sizes);
|
||||
|
||||
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
expect(maxSize1.width).toBe(90); // 90 = layout.width - (col2.width + col3.minWidth(=40) )
|
||||
expect(maxSize2.width).toBe(110); // 110 = layout.width - (col1.width + col3.minWidth(=40) )
|
||||
});
|
||||
|
||||
test('should respect minimum sizes', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
minWidth: 60,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
|
||||
const maxSize2 = calculateMaxSizeAvailableForItem(layout.children[1], layout, sizes);
|
||||
|
||||
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
expect(maxSize1.width).toBe(70); // 70 = layout.width - (col2.width + col3.minWidth)
|
||||
expect(maxSize2.width).toBe(90); // 90 = layout.width - (col1.width + col3.minWidth)
|
||||
});
|
||||
|
||||
test('should not allow a minWidth of 0, should still leave space for the item', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
minWidth: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
|
||||
|
||||
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
expect(maxSize1.width).toBe(160); // 160 = layout.width - col2.minWidth(=40)
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -3,6 +3,9 @@ import { LayoutItem, Size } from './types';
|
||||
|
||||
const dragBarThickness = 5;
|
||||
|
||||
export const itemMinWidth = 40;
|
||||
export const itemMinHeight = 40;
|
||||
|
||||
export interface LayoutItemSizes {
|
||||
[key: string]: Size;
|
||||
}
|
||||
@@ -17,8 +20,8 @@ export function itemSize(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
|
||||
const bottomGap = !isContainer && (item.resizableBottom || parentResizableBottom) ? dragBarThickness : 0;
|
||||
|
||||
return {
|
||||
width: ('width' in item ? item.width : sizes[item.key].width) - rightGap,
|
||||
height: ('height' in item ? item.height : sizes[item.key].height) - bottomGap,
|
||||
width: sizes[item.key].width - rightGap,
|
||||
height: sizes[item.key].height - bottomGap,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +41,10 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
||||
const noWidthChildren: any[] = [];
|
||||
const noHeightChildren: any[] = [];
|
||||
|
||||
// The minimum space required for items with no defined size
|
||||
let noWidthChildrenMinWidth = 0;
|
||||
let noHeightChildrenMinHeight = 0;
|
||||
|
||||
for (const child of item.children) {
|
||||
let w = 'width' in child ? child.width : null;
|
||||
let h = 'height' in child ? child.height : null;
|
||||
@@ -47,10 +54,43 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
||||
}
|
||||
|
||||
sizes[child.key] = { width: w, height: h };
|
||||
|
||||
if (w !== null) remainingSize.width -= w;
|
||||
if (h !== null) remainingSize.height -= h;
|
||||
if (w === null) noWidthChildren.push({ item: child, parent: item });
|
||||
if (h === null) noHeightChildren.push({ item: child, parent: item });
|
||||
if (w === null) {
|
||||
noWidthChildren.push({ item: child, parent: item });
|
||||
noWidthChildrenMinWidth += child.minWidth || itemMinWidth;
|
||||
}
|
||||
if (h === null) {
|
||||
noHeightChildren.push({ item: child, parent: item });
|
||||
noHeightChildrenMinHeight += child.minHeight || itemMinHeight;
|
||||
}
|
||||
}
|
||||
|
||||
while (remainingSize.width < noWidthChildrenMinWidth) {
|
||||
// There is not enough space, the widest item will be made smaller
|
||||
let widestChild = item.children[0].key;
|
||||
for (const child of item.children) {
|
||||
if (!child.visible) continue;
|
||||
if (sizes[child.key].width > sizes[widestChild].width) widestChild = child.key;
|
||||
}
|
||||
|
||||
const dw = Math.abs(remainingSize.width - noWidthChildrenMinWidth);
|
||||
sizes[widestChild].width -= dw;
|
||||
remainingSize.width += dw;
|
||||
}
|
||||
|
||||
while (remainingSize.height < noHeightChildrenMinHeight) {
|
||||
// There is not enough space, the tallest item will be made smaller
|
||||
let tallestChild = item.children[0].key;
|
||||
for (const child of item.children) {
|
||||
if (!child.visible) continue;
|
||||
if (sizes[child.key].height > sizes[tallestChild].height) tallestChild = child.key;
|
||||
}
|
||||
|
||||
const dh = Math.abs(remainingSize.height - noHeightChildrenMinHeight);
|
||||
sizes[tallestChild].height -= dh;
|
||||
remainingSize.height += dh;
|
||||
}
|
||||
|
||||
if (noWidthChildren.length) {
|
||||
@@ -77,6 +117,24 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
||||
return sizes;
|
||||
}
|
||||
|
||||
// Gives the maximum available space for this item that it can take up during resizing
|
||||
// availableSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
export function calculateMaxSizeAvailableForItem(item: LayoutItem, parent: LayoutItem, sizes: LayoutItemSizes): Size {
|
||||
const availableSize: Size = { ...sizes[parent.key] };
|
||||
|
||||
for (const sibling of parent.children) {
|
||||
if (!sibling.visible) continue;
|
||||
|
||||
availableSize.width -= 'width' in sibling ? sizes[sibling.key].width : (sibling.minWidth || itemMinWidth);
|
||||
availableSize.height -= 'height' in sibling ? sizes[sibling.key].height : (sibling.minHeight || itemMinHeight);
|
||||
}
|
||||
|
||||
availableSize.width += sizes[item.key].width;
|
||||
availableSize.height += sizes[item.key].height;
|
||||
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
export default function useLayoutItemSizes(layout: LayoutItem, makeAllVisible: boolean = false) {
|
||||
return useMemo(() => {
|
||||
let sizes: LayoutItemSizes = {};
|
||||
|
@@ -126,7 +126,7 @@ function SearchBar(props: Props) {
|
||||
}, [props.notesParentType, onExitSearch]);
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<Root className="search-bar">
|
||||
<SearchInput
|
||||
inputRef={props.inputRef}
|
||||
value={query}
|
||||
|
@@ -10,19 +10,21 @@ import { reg } from '@joplin/lib/registry';
|
||||
import Dialog from './Dialog';
|
||||
import DialogTitle from './DialogTitle';
|
||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||
import { StateShare } from '@joplin/lib/services/share/reducer';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import Button from './Button/Button';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../app';
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
interface ShareNoteDialogProps {
|
||||
interface Props {
|
||||
themeId: number;
|
||||
noteIds: Array<string>;
|
||||
onClose: Function;
|
||||
shares: StateShare[];
|
||||
}
|
||||
|
||||
interface SharesMap {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function styles_(props: ShareNoteDialogProps) {
|
||||
function styles_(props: Props) {
|
||||
return buildStyle('ShareNoteDialog', props.themeId, (theme: any) => {
|
||||
return {
|
||||
noteList: {
|
||||
@@ -60,17 +62,21 @@ function styles_(props: ShareNoteDialogProps) {
|
||||
});
|
||||
}
|
||||
|
||||
export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
export function ShareNoteDialog(props: Props) {
|
||||
console.info('Render ShareNoteDialog');
|
||||
|
||||
const [notes, setNotes] = useState<any[]>([]);
|
||||
const [notes, setNotes] = useState<NoteEntity[]>([]);
|
||||
const [sharesState, setSharesState] = useState<string>('unknown');
|
||||
const [shares, setShares] = useState<SharesMap>({});
|
||||
// const [shares, setShares] = useState<SharesMap>({});
|
||||
|
||||
const noteCount = notes.length;
|
||||
const theme = themeStyle(props.themeId);
|
||||
const styles = styles_(props);
|
||||
|
||||
useEffect(() => {
|
||||
void ShareService.instance().refreshShares();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchNotes() {
|
||||
const result = [];
|
||||
@@ -87,9 +93,9 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
const copyLinksToClipboard = (shares: SharesMap) => {
|
||||
const copyLinksToClipboard = (shares: StateShare[]) => {
|
||||
const links = [];
|
||||
for (const n in shares) links.push(ShareService.instance().shareUrl(shares[n]));
|
||||
for (const share of shares) links.push(ShareService.instance().shareUrl(ShareService.instance().userId, share));
|
||||
clipboard.writeText(links.join('\n'));
|
||||
};
|
||||
|
||||
@@ -109,15 +115,13 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
|
||||
setSharesState('creating');
|
||||
|
||||
const newShares = Object.assign({}, shares);
|
||||
const newShares: StateShare[] = [];
|
||||
|
||||
for (const note of notes) {
|
||||
const share = await service.shareNote(note.id);
|
||||
newShares[note.id] = share;
|
||||
newShares.push(share);
|
||||
}
|
||||
|
||||
setShares(newShares);
|
||||
|
||||
setSharesState('synchronizing');
|
||||
await reg.waitForSyncFinishedThenSync();
|
||||
setSharesState('creating');
|
||||
@@ -125,6 +129,8 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
copyLinksToClipboard(newShares);
|
||||
|
||||
setSharesState('created');
|
||||
|
||||
await ShareService.instance().refreshShares();
|
||||
} catch (error) {
|
||||
if (error.code === 404 && !hasSynced) {
|
||||
reg.logger().info('ShareNoteDialog: Note does not exist on server - trying to sync it.', error);
|
||||
@@ -142,34 +148,53 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const removeNoteButton_click = (event: any) => {
|
||||
const newNotes = [];
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
const n = notes[i];
|
||||
if (n.id === event.noteId) continue;
|
||||
newNotes.push(n);
|
||||
}
|
||||
setNotes(newNotes);
|
||||
// const removeNoteButton_click = (event: any) => {
|
||||
// const newNotes = [];
|
||||
// for (let i = 0; i < notes.length; i++) {
|
||||
// const n = notes[i];
|
||||
// if (n.id === event.noteId) continue;
|
||||
// newNotes.push(n);
|
||||
// }
|
||||
// setNotes(newNotes);
|
||||
// };
|
||||
|
||||
const unshareNoteButton_click = async (event: any) => {
|
||||
await ShareService.instance().unshareNote(event.noteId);
|
||||
await ShareService.instance().refreshShares();
|
||||
};
|
||||
|
||||
const renderNote = (note: any) => {
|
||||
const removeButton = notes.length <= 1 ? null : (
|
||||
<button onClick={() => removeNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
|
||||
<i style={styles.noteRemoveButtonIcon} className={'fa fa-times'}></i>
|
||||
</button>
|
||||
const renderNote = (note: NoteEntity) => {
|
||||
const unshareButton = !props.shares.find(s => s.note_id === note.id) ? null : (
|
||||
<Button tooltip={_('Unshare note')} iconName="fas fa-share-alt" onClick={() => unshareNoteButton_click({ noteId: note.id })}/>
|
||||
);
|
||||
|
||||
// const removeButton = notes.length <= 1 ? null : (
|
||||
// <Button iconName="fa fa-times" onClick={() => removeNoteButton_click({ noteId: note.id })}/>
|
||||
// );
|
||||
|
||||
// const unshareButton = !shares[note.id] ? null : (
|
||||
// <button onClick={() => unshareNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
|
||||
// <i style={styles.noteRemoveButtonIcon} className={'fas fa-share-alt'}></i>
|
||||
// </button>
|
||||
// );
|
||||
|
||||
// const removeButton = notes.length <= 1 ? null : (
|
||||
// <button onClick={() => removeNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
|
||||
// <i style={styles.noteRemoveButtonIcon} className={'fa fa-times'}></i>
|
||||
// </button>
|
||||
// );
|
||||
|
||||
return (
|
||||
<div key={note.id} style={styles.note}>
|
||||
<span style={styles.noteTitle}>{note.title}</span>{removeButton}
|
||||
<span style={styles.noteTitle}>{note.title}</span>{unshareButton}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderNoteList = (notes: any) => {
|
||||
const noteComps = [];
|
||||
for (const noteId of Object.keys(notes)) {
|
||||
noteComps.push(renderNote(notes[noteId]));
|
||||
for (const note of notes) {
|
||||
noteComps.push(renderNote(note));
|
||||
}
|
||||
return <div style={styles.noteList}>{noteComps}</div>;
|
||||
};
|
||||
@@ -194,7 +219,12 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
<button disabled={['creating', 'synchronizing'].indexOf(sharesState) >= 0} style={styles.copyShareLinkButton} onClick={shareLinkButton_click}>{_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)}</button>
|
||||
<div style={theme.textStyle}>{statusMessage(sharesState)}</div>
|
||||
{renderEncryptionWarningMessage()}
|
||||
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
<DialogButtonRow
|
||||
themeId={props.themeId}
|
||||
onClick={buttonRow_click}
|
||||
okButtonShow={false}
|
||||
cancelButtonLabel={_('Close')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -203,3 +233,11 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
|
||||
<Dialog renderContent={renderContent}/>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
shares: state.shareService.shares.filter(s => !!s.note_id),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(ShareNoteDialog as any);
|
||||
|
@@ -77,12 +77,12 @@ function ExpandLink(props: any) {
|
||||
function FolderItem(props: any) {
|
||||
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
|
||||
|
||||
const noteCountComp = noteCount ? <StyledNoteCount>{noteCount}</StyledNoteCount> : null;
|
||||
const noteCountComp = noteCount ? <StyledNoteCount className="note-count-label">{noteCount}</StyledNoteCount> : null;
|
||||
|
||||
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
|
||||
|
||||
return (
|
||||
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
|
||||
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth} ${selected ? 'selected' : ''}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
|
||||
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
|
||||
<StyledListItemAnchor
|
||||
ref={anchorRef}
|
||||
@@ -100,7 +100,8 @@ function FolderItem(props: any) {
|
||||
}}
|
||||
onDoubleClick={onFolderToggleClick_}
|
||||
>
|
||||
{folderTitle} {shareIcon} {noteCountComp}
|
||||
<span className="title">{folderTitle}</span>
|
||||
{shareIcon} {noteCountComp}
|
||||
</StyledListItemAnchor>
|
||||
</StyledListItem>
|
||||
);
|
||||
@@ -382,7 +383,7 @@ class SidebarComponent extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
renderNoteCount(count: number) {
|
||||
return count ? <StyledNoteCount>{count}</StyledNoteCount> : null;
|
||||
return count ? <StyledNoteCount className="note-count-label">{count}</StyledNoteCount> : null;
|
||||
}
|
||||
|
||||
renderExpandIcon(isExpanded: boolean, isVisible: boolean = true) {
|
||||
@@ -394,7 +395,7 @@ class SidebarComponent extends React.Component<Props, State> {
|
||||
|
||||
renderAllNotesItem(selected: boolean) {
|
||||
return (
|
||||
<StyledListItem key="allNotesHeader" selected={selected} className={'list-item-container list-item-depth-0'} isSpecialItem={true}>
|
||||
<StyledListItem key="allNotesHeader" selected={selected} className={'list-item-container list-item-depth-0 all-notes'} isSpecialItem={true}>
|
||||
<StyledExpandLink>{this.renderExpandIcon(false, false)}</StyledExpandLink>
|
||||
<StyledAllNotesIcon className="icon-notes"/>
|
||||
<StyledListItemAnchor
|
||||
@@ -448,10 +449,19 @@ class SidebarComponent extends React.Component<Props, State> {
|
||||
|
||||
renderTag(tag: any, selected: boolean) {
|
||||
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 (
|
||||
<StyledListItem selected={selected} className={'list-item-container'} key={tag.id} onDrop={this.onTagDrop_} data-tag-id={tag.id}>
|
||||
<StyledListItem selected={selected}
|
||||
className={`list-item-container ${selected ? 'selected' : ''}`}
|
||||
key={tag.id}
|
||||
onDrop={this.onTagDrop_}
|
||||
data-tag-id={tag.id}
|
||||
>
|
||||
<StyledExpandLink>{this.renderExpandIcon(false, false)}</StyledExpandLink>
|
||||
<StyledListItemAnchor
|
||||
ref={anchorRef}
|
||||
@@ -465,7 +475,8 @@ class SidebarComponent extends React.Component<Props, State> {
|
||||
this.tagItem_click(tag);
|
||||
}}
|
||||
>
|
||||
{Tag.displayTitle(tag)} {noteCount}
|
||||
<span className="tag-label">{Tag.displayTitle(tag)}</span>
|
||||
{noteCount}
|
||||
</StyledListItemAnchor>
|
||||
</StyledListItem>
|
||||
);
|
||||
@@ -657,7 +668,11 @@ class SidebarComponent extends React.Component<Props, State> {
|
||||
const folderItems = [this.renderAllNotesItem(allNotesSelected)].concat(result.items);
|
||||
this.folderItemsOrder_ = result.order;
|
||||
items.push(
|
||||
<div className="folders" key="folder_items" style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none', paddingBottom: 10 }}>
|
||||
<div
|
||||
className={`folders ${this.state.folderHeaderIsExpanded ? 'expanded' : ''}`}
|
||||
key="folder_items"
|
||||
style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none', paddingBottom: 10 }}
|
||||
>
|
||||
{folderItems}
|
||||
</div>
|
||||
);
|
||||
|
@@ -69,7 +69,6 @@ function listItemTextColor(props: any) {
|
||||
|
||||
export const StyledListItemAnchor = styled.a`
|
||||
font-size: ${(props: any) => Math.round(props.theme.fontSize * 1.0833333)}px;
|
||||
// font-weight: 500;
|
||||
text-decoration: none;
|
||||
color: ${(props: any) => listItemTextColor(props)};
|
||||
cursor: default;
|
||||
|
@@ -87,12 +87,15 @@ function StatusScreen(props: Props) {
|
||||
|
||||
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
|
||||
|
||||
let currentListKey = '';
|
||||
let listItems: any[] = [];
|
||||
for (const n in section.body) {
|
||||
if (!section.body.hasOwnProperty(n)) continue;
|
||||
const item = section.body[n];
|
||||
let text = '';
|
||||
|
||||
let retryLink = null;
|
||||
let itemType = null;
|
||||
if (typeof item === 'object') {
|
||||
if (item.canRetry) {
|
||||
const onClick = async () => {
|
||||
@@ -107,22 +110,47 @@ function StatusScreen(props: Props) {
|
||||
);
|
||||
}
|
||||
text = item.text;
|
||||
itemType = item.type;
|
||||
} else {
|
||||
text = item;
|
||||
}
|
||||
|
||||
if (itemType === 'openList') {
|
||||
currentListKey = item.key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemType === 'closeList') {
|
||||
itemsHtml.push(<ul key={currentListKey}>{listItems}</ul>);
|
||||
currentListKey = '';
|
||||
listItems = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!text) text = '\xa0';
|
||||
|
||||
itemsHtml.push(
|
||||
<div style={theme.textStyle} key={`item_${n}`}>
|
||||
<span>{text}</span>
|
||||
{retryLink}
|
||||
</div>
|
||||
);
|
||||
if (currentListKey) {
|
||||
listItems.push(
|
||||
<li style={theme.textStyle} key={`item_${n}`}>
|
||||
<span>{text}</span>
|
||||
{retryLink}
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
itemsHtml.push(
|
||||
<div style={theme.textStyle} key={`item_${n}`}>
|
||||
<span>{text}</span>
|
||||
{retryLink}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
|
@@ -17,7 +17,16 @@ export default function ToggleEditorsButton(props: Props) {
|
||||
const style = styles_(props);
|
||||
|
||||
return (
|
||||
<button style={style.button} disabled={!props.toolbarButtonInfo.enabled} aria-label={props.toolbarButtonInfo.tooltip} title={props.toolbarButtonInfo.tooltip} type="button" className="tox-tbtn" aria-pressed="false" onClick={props.toolbarButtonInfo.onClick}>
|
||||
<button
|
||||
style={style.button}
|
||||
disabled={!props.toolbarButtonInfo.enabled}
|
||||
aria-label={props.toolbarButtonInfo.tooltip}
|
||||
title={props.toolbarButtonInfo.tooltip}
|
||||
type="button"
|
||||
className={`tox-tbtn ${props.value}-active`}
|
||||
aria-pressed="false"
|
||||
onClick={props.toolbarButtonInfo.onClick}
|
||||
>
|
||||
<div style={style.leftInnerButton}>
|
||||
<i style={style.leftIcon} className="fab fa-markdown"></i>
|
||||
</div>
|
||||
|
@@ -45,5 +45,7 @@ export default function() {
|
||||
'editor.swapLineUp',
|
||||
'editor.swapLineDown',
|
||||
'toggleSafeMode',
|
||||
'showShareNoteDialog',
|
||||
'showShareFolderDialog',
|
||||
];
|
||||
}
|
||||
|
9
packages/app-desktop/gui/style/StyledLink.tsx
Normal file
9
packages/app-desktop/gui/style/StyledLink.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledLink = styled.a`
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
color: ${props => props.theme.urlColor};
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
`;
|
||||
|
||||
export default StyledLink;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user