1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-20 00:46:28 +02:00

Compare commits

...

66 Commits

Author SHA1 Message Date
Laurent Cozic
84a793270d move back to test 2021-05-21 15:15:48 +02:00
Laurent Cozic
5f8b73350d other tdsts 2021-05-21 15:09:08 +02:00
Laurent Cozic
a733cb0394 services 2021-05-21 13:44:57 +02:00
Laurent Cozic
7da4cb0d80 sync tests 2021-05-21 13:22:50 +02:00
Laurent Cozic
3233082b4c move more 2021-05-21 13:14:45 +02:00
Laurent Cozic
c782f5e981 move 2021-05-21 13:01:43 +02:00
Laurent Cozic
db51e2dd4b cli setup 2021-05-21 12:42:19 +02:00
Laurent Cozic
f00f3d7f04 test move 2021-05-21 12:33:33 +02:00
Laurent Cozic
6ff560f22f Desktop: Fixes #4591: Prevent cursor from jumping to top of page when pasting image 2021-05-20 19:13:35 +02:00
Laurent Cozic
2226b79c46 Desktop: Fixed pasting HTML in Rich Text editor, and improved pasting plain text 2021-05-20 18:08:59 +02:00
Laurent Cozic
9e9bf63d70 Desktop: Regression: Pasting plain text in Rich Text editor was broken 2021-05-20 17:42:04 +02:00
Breno Baptista
5d9419be5d Doc: Improved Linux notifications instruction (#4985) 2021-05-19 22:27:43 +01:00
Roman Musin
9f37aa96c6 Android: Fixes #4912, Fixes #4086, Fixes #4908: Fixed and improved alarm notifications (#4984) 2021-05-19 22:26:42 +01:00
JackGruber
eceb14ff9e Desktop: Resolves #4433: Import linked local files when importing Markdown files (#4966) 2021-05-19 22:22:03 +01:00
Laurent Cozic
85211e8d5c Mobile: Fixes #4494: Fixed opening URLs that contain non-alphabetical characters 2021-05-19 16:48:55 +02:00
Laurent Cozic
bd08041f53 Desktop: Pass custom CSS property to all export handlers and renderers 2021-05-19 15:00:16 +02:00
Laurent Cozic
77b284f01f Server: Added API end points to manage users 2021-05-18 17:11:50 +02:00
Laurent Cozic
daaaa133ab Server: Allow enabling or disabling the sharing feature per user 2021-05-18 15:53:56 +02:00
Laurent Cozic
e6c4eb7cdf Server: Renamed users.item_max_size => users.max_item_size 2021-05-18 15:11:03 +02:00
tatsu
dc2cdb7d3a Doc: Fixed json of s3 bucket policy (#4981) 2021-05-17 15:15:23 -04:00
Andre Jilderda
f3e03d48bb Desktop: Add classnames to DOM elements for theming purposes (#4933) 2021-05-17 19:33:44 +01:00
Laurent Cozic
6577f4f35d Desktop: Regression: It was no longer possible to add list items in an empty note 2021-05-17 20:30:48 +02:00
Laurent Cozic
d29624c816 Desktop: Handle too large items for Joplin Server 2021-05-17 18:55:39 +02:00
Laurent Cozic
6afde54bda Server: Add support for item size limit 2021-05-17 18:35:01 +02:00
Laurent Cozic
ec7f0f479a Server: Improved log table too and made it sortable 2021-05-17 17:29:21 +02:00
Laurent Cozic
7f05420fda Server: Improved Items table and added item size to it 2021-05-17 17:02:15 +02:00
Laurent Cozic
a3f8cd4850 Merge branch 'release-2.0' into dev 2021-05-16 19:40:08 +02:00
Laurent Cozic
01ccf5170a Tools: Changelog for Android version 2021-05-16 19:38:36 +02:00
Laurent Cozic
6ddb69e1ea Server: Fixed bug when unsharing a notebook that has no recipients 2021-05-16 18:55:07 +02:00
Laurent Cozic
b01f82bb33 Android release v2.0.1 2021-05-16 17:54:13 +02:00
Laurent Cozic
b6c9edba21 Tools: Make sure branch has been pushed before releasing Android version 2021-05-16 17:50:50 +02:00
Laurent Cozic
f7d164be6e Desktop: Allow unsharing a note 2021-05-16 17:28:49 +02:00
Laurent Cozic
6f2f24171d Desktop: Add Share Notebook menu item 2021-05-16 15:21:55 +02:00
Laurent Cozic
12cc64008b typo 2021-05-16 12:49:05 +02:00
Laurent Cozic
b9955f58d3 Server: Refactor ShareType 2021-05-16 12:46:58 +02:00
Laurent Cozic
489995daef Server: Fixed deleting a note that has been shared 2021-05-16 12:42:58 +02:00
Laurent Cozic
e156ee1b58 Server: Generate only one share link per note 2021-05-16 12:33:36 +02:00
Laurent Cozic
a24b0091ad Server: Go back to home page when there is an error and user is logged in 2021-05-16 12:19:18 +02:00
Laurent Cozic
2655b6deee Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-05-16 11:48:15 +02:00
Laurent Cozic
45c40f7395 Server: Fixed log page 2021-05-16 11:46:16 +02:00
Woosuk Park
ecb0eee355 All: Translation: Update ko.po (#4976)
* Some Translated. and some modified

* ko.po Update
2021-05-16 02:39:43 -04:00
suixinio
4916f4cc92 All: Translation: Update zh_CN.po (#4969)
This PR updates zh_CN.po, with just a minor translation fix.
2021-05-15 15:58:48 -04:00
Laurent Cozic
15fe119256 Desktop: Made sync more reliable by making it skip items that time out, and improved sync status screen 2021-05-15 20:56:49 +02:00
Laurent Cozic
0b46880a00 Desktop: Fixes #4926: Fixed issue with empty panels being created by plugins 2021-05-15 17:30:56 +02:00
Laurent Cozic
deaa731983 Update translations 2021-05-15 16:15:55 +02:00
Laurent Cozic
d061bb1a4f typo 2021-05-15 16:06:24 +02:00
Laurent Cozic
03db0c5486 Desktop: Resolves #4462: Improved usability when plugin repository cannot be connected to 2021-05-15 16:04:10 +02:00
Laurent Cozic
bb275e671d Tools: Allow running the test units with Postgres 2021-05-15 15:13:08 +02:00
Laurent Cozic
2d0580ff71 Server: Fixed /items page when using Postgres 2021-05-15 15:10:40 +02:00
Laurent Cozic
2331d3487b Desktop, Cli: Resolves #4968: Import SVG as images when importing ENEX files 2021-05-15 13:42:57 +02:00
Laurent Cozic
f1380fd51d Server: Fixes #4540: Make sure temp files are deleted after upload is done 2021-05-15 12:13:46 +02:00
Laurent Cozic
d462dab8eb Update French translation 2021-05-15 11:36:52 +02:00
Laurent Cozic
cf37b74d9a Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-05-15 11:14:40 +02:00
Laurent Cozic
aec3ea9c0c Desktop, Cli: Fixes #4965: Improved importing Evernote notes that contain codeblocks 2021-05-15 11:12:11 +02:00
Helmut K. C. Tessarek
1f5aa70acd Update translations (for new server/client code) 2021-05-14 13:27:17 -04:00
Harris Arvanitis
416637ce83 All: Translation: Update el_GR.po (#4961) 2021-05-14 13:23:13 -04:00
Gen Neko
99c4b0bc01 All: Translation: Update ja_JP.po (#4960) 2021-05-14 13:22:35 -04:00
Laurent Cozic
74d8fec98a Desktop release v2.0.1 2021-05-14 17:17:40 +02:00
Laurent Cozic
321a58c356 Prepare for v2 2021-05-14 17:17:02 +02:00
Laurent Cozic
81884cf2ea add comment 2021-05-14 14:52:29 +02:00
Laurent Cozic
d7ff634f5e Merge branch 'release-1.8' into dev 2021-05-14 14:50:04 +02:00
Laurent Cozic
a965da97b6 ios-v10.8.1 2021-05-14 14:43:49 +02:00
Laurent Cozic
b99cb0248d Cli: Fixes #4845: Fixed possible crash when trying to delete corrupted revision in revision service 2021-05-14 12:09:20 +02:00
Laurent Cozic
a31b402b9e Desktop: Added button to skip an application update
And made auto-updates enabled by default
2021-05-14 11:29:06 +02:00
Laurent Cozic
6959f14a3f Desktop: Fixes #4958: Display proper error message when JEX file is corrupted 2021-05-13 22:13:53 +02:00
Laurent
0765cf5955 All: Add support for sharing notebooks with Joplin Server (#4772)
- Joplin Server: Adds support for sharing a notebook
- Desktop: Adds support for sharing a notebook with Joplin Server
- Mobile: Adds support for reading and writing to a shared notebook (not possible to share a notebook)
- Cli: Adds support for reading and writing to a shared notebook (not possible to share a notebook)
2021-05-13 17:57:37 +01:00
425 changed files with 30288 additions and 17466 deletions

View File

@@ -18,6 +18,7 @@ packages/turndown-plugin-gfm/
node_modules/
packages/lib/lib/lib.js
packages/lib/locales/index.js
packages/lib/services/database/types.ts
packages/app-cli/build
packages/app-cli/build/
packages/app-cli/locales
@@ -73,63 +74,18 @@ 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/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_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/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
@@ -145,42 +101,6 @@ 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-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map
@@ -193,6 +113,9 @@ packages/app-desktop/app.js.map
packages/app-desktop/bridge.d.ts
packages/app-desktop/bridge.js
packages/app-desktop/bridge.js.map
packages/app-desktop/checkForUpdates.d.ts
packages/app-desktop/checkForUpdates.js
packages/app-desktop/checkForUpdates.js.map
packages/app-desktop/commands/copyDevCommand.d.ts
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyDevCommand.js.map
@@ -250,6 +173,15 @@ packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.js.ma
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.test.d.ts
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.test.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.test.js.map
packages/app-desktop/gui/Dialog.d.ts
packages/app-desktop/gui/Dialog.js
packages/app-desktop/gui/Dialog.js.map
packages/app-desktop/gui/DialogButtonRow.d.ts
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow.js.map
packages/app-desktop/gui/DialogTitle.d.ts
packages/app-desktop/gui/DialogTitle.js
packages/app-desktop/gui/DialogTitle.js.map
packages/app-desktop/gui/DropboxLoginScreen.d.ts
packages/app-desktop/gui/DropboxLoginScreen.js
packages/app-desktop/gui/DropboxLoginScreen.js.map
@@ -337,6 +269,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/showShareFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js.map
@@ -556,6 +491,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
@@ -589,6 +527,9 @@ packages/app-desktop/gui/Root_UpgradeSyncTarget.js.map
packages/app-desktop/gui/SearchBar/SearchBar.d.ts
packages/app-desktop/gui/SearchBar/SearchBar.js
packages/app-desktop/gui/SearchBar/SearchBar.js.map
packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.d.ts
packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js
packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js.map
packages/app-desktop/gui/ShareNoteDialog.d.ts
packages/app-desktop/gui/ShareNoteDialog.js
packages/app-desktop/gui/ShareNoteDialog.js.map
@@ -646,9 +587,18 @@ packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js.map
packages/app-desktop/gui/menuCommandNames.d.ts
packages/app-desktop/gui/menuCommandNames.js
packages/app-desktop/gui/menuCommandNames.js.map
packages/app-desktop/gui/style/StyledFormLabel.d.ts
packages/app-desktop/gui/style/StyledFormLabel.js
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
packages/app-desktop/gui/style/StyledTextInput.d.ts
packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/style/StyledTextInput.js.map
@@ -751,9 +701,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
@@ -769,6 +719,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
@@ -844,9 +797,15 @@ 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
packages/lib/JoplinError.d.ts
packages/lib/JoplinError.js
packages/lib/JoplinError.js.map
packages/lib/JoplinServerApi.d.ts
packages/lib/JoplinServerApi.js
packages/lib/JoplinServerApi.js.map
@@ -877,6 +836,9 @@ packages/lib/commands/synchronize.js.map
packages/lib/database.d.ts
packages/lib/database.js
packages/lib/database.js.map
packages/lib/debug/DebugService.d.ts
packages/lib/debug/DebugService.js
packages/lib/debug/DebugService.js.map
packages/lib/dummy.test.d.ts
packages/lib/dummy.test.js
packages/lib/dummy.test.js.map
@@ -889,6 +851,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
@@ -898,12 +863,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
@@ -913,6 +890,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
@@ -925,6 +905,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
@@ -937,6 +923,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
@@ -958,12 +947,18 @@ 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
@@ -991,6 +986,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
@@ -1003,6 +1001,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
@@ -1051,6 +1052,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
@@ -1063,6 +1067,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
@@ -1096,6 +1103,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
@@ -1105,6 +1115,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
@@ -1153,6 +1166,9 @@ packages/lib/services/keychain/KeychainServiceDriver.node.js.map
packages/lib/services/keychain/KeychainServiceDriverBase.d.ts
packages/lib/services/keychain/KeychainServiceDriverBase.js
packages/lib/services/keychain/KeychainServiceDriverBase.js.map
packages/lib/services/keychain/keychainService.test.d.ts
packages/lib/services/keychain/keychainService.test.js
packages/lib/services/keychain/keychainService.test.js.map
packages/lib/services/plugins/BasePluginRunner.d.ts
packages/lib/services/plugins/BasePluginRunner.js
packages/lib/services/plugins/BasePluginRunner.js.map
@@ -1267,6 +1283,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
@@ -1327,12 +1346,21 @@ 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
packages/lib/services/searchengine/queryBuilder.d.ts
packages/lib/services/searchengine/queryBuilder.js
packages/lib/services/searchengine/queryBuilder.js.map
packages/lib/services/share/ShareService.d.ts
packages/lib/services/share/ShareService.js
packages/lib/services/share/ShareService.js.map
packages/lib/services/share/reducer.d.ts
packages/lib/services/share/reducer.js
packages/lib/services/share/reducer.js.map
packages/lib/services/spellChecker/SpellCheckerService.d.ts
packages/lib/services/spellChecker/SpellCheckerService.js
packages/lib/services/spellChecker/SpellCheckerService.js.map
@@ -1345,6 +1373,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
@@ -1354,6 +1406,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
@@ -1366,6 +1424,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
@@ -1399,6 +1463,9 @@ 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
@@ -1525,6 +1592,9 @@ 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

View File

@@ -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',

249
.gitignore vendored
View File

@@ -60,63 +60,18 @@ 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/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_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/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
@@ -132,42 +87,6 @@ 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-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map
@@ -180,6 +99,9 @@ packages/app-desktop/app.js.map
packages/app-desktop/bridge.d.ts
packages/app-desktop/bridge.js
packages/app-desktop/bridge.js.map
packages/app-desktop/checkForUpdates.d.ts
packages/app-desktop/checkForUpdates.js
packages/app-desktop/checkForUpdates.js.map
packages/app-desktop/commands/copyDevCommand.d.ts
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyDevCommand.js.map
@@ -237,6 +159,15 @@ packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.js.ma
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.test.d.ts
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.test.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/useOnInstallHandler.test.js.map
packages/app-desktop/gui/Dialog.d.ts
packages/app-desktop/gui/Dialog.js
packages/app-desktop/gui/Dialog.js.map
packages/app-desktop/gui/DialogButtonRow.d.ts
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow.js.map
packages/app-desktop/gui/DialogTitle.d.ts
packages/app-desktop/gui/DialogTitle.js
packages/app-desktop/gui/DialogTitle.js.map
packages/app-desktop/gui/DropboxLoginScreen.d.ts
packages/app-desktop/gui/DropboxLoginScreen.js
packages/app-desktop/gui/DropboxLoginScreen.js.map
@@ -324,6 +255,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/showShareFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js.map
@@ -543,6 +477,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
@@ -576,6 +513,9 @@ packages/app-desktop/gui/Root_UpgradeSyncTarget.js.map
packages/app-desktop/gui/SearchBar/SearchBar.d.ts
packages/app-desktop/gui/SearchBar/SearchBar.js
packages/app-desktop/gui/SearchBar/SearchBar.js.map
packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.d.ts
packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js
packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js.map
packages/app-desktop/gui/ShareNoteDialog.d.ts
packages/app-desktop/gui/ShareNoteDialog.js
packages/app-desktop/gui/ShareNoteDialog.js.map
@@ -633,9 +573,18 @@ packages/app-desktop/gui/lib/ToggleButton/ToggleButton.js.map
packages/app-desktop/gui/menuCommandNames.d.ts
packages/app-desktop/gui/menuCommandNames.js
packages/app-desktop/gui/menuCommandNames.js.map
packages/app-desktop/gui/style/StyledFormLabel.d.ts
packages/app-desktop/gui/style/StyledFormLabel.js
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
packages/app-desktop/gui/style/StyledTextInput.d.ts
packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/style/StyledTextInput.js.map
@@ -738,9 +687,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
@@ -756,6 +705,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
@@ -831,9 +783,15 @@ 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
packages/lib/JoplinError.d.ts
packages/lib/JoplinError.js
packages/lib/JoplinError.js.map
packages/lib/JoplinServerApi.d.ts
packages/lib/JoplinServerApi.js
packages/lib/JoplinServerApi.js.map
@@ -864,6 +822,9 @@ packages/lib/commands/synchronize.js.map
packages/lib/database.d.ts
packages/lib/database.js
packages/lib/database.js.map
packages/lib/debug/DebugService.d.ts
packages/lib/debug/DebugService.js
packages/lib/debug/DebugService.js.map
packages/lib/dummy.test.d.ts
packages/lib/dummy.test.js
packages/lib/dummy.test.js.map
@@ -876,6 +837,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
@@ -885,12 +849,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
@@ -900,6 +876,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
@@ -912,6 +891,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
@@ -924,6 +909,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
@@ -945,12 +933,18 @@ 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
@@ -978,6 +972,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
@@ -990,6 +987,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
@@ -1038,6 +1038,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
@@ -1050,6 +1053,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
@@ -1083,6 +1089,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
@@ -1092,6 +1101,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
@@ -1140,6 +1152,9 @@ packages/lib/services/keychain/KeychainServiceDriver.node.js.map
packages/lib/services/keychain/KeychainServiceDriverBase.d.ts
packages/lib/services/keychain/KeychainServiceDriverBase.js
packages/lib/services/keychain/KeychainServiceDriverBase.js.map
packages/lib/services/keychain/keychainService.test.d.ts
packages/lib/services/keychain/keychainService.test.js
packages/lib/services/keychain/keychainService.test.js.map
packages/lib/services/plugins/BasePluginRunner.d.ts
packages/lib/services/plugins/BasePluginRunner.js
packages/lib/services/plugins/BasePluginRunner.js.map
@@ -1254,6 +1269,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
@@ -1314,12 +1332,21 @@ 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
packages/lib/services/searchengine/queryBuilder.d.ts
packages/lib/services/searchengine/queryBuilder.js
packages/lib/services/searchengine/queryBuilder.js.map
packages/lib/services/share/ShareService.d.ts
packages/lib/services/share/ShareService.js
packages/lib/services/share/ShareService.js.map
packages/lib/services/share/reducer.d.ts
packages/lib/services/share/reducer.js
packages/lib/services/share/reducer.js.map
packages/lib/services/spellChecker/SpellCheckerService.d.ts
packages/lib/services/spellChecker/SpellCheckerService.js
packages/lib/services/spellChecker/SpellCheckerService.js.map
@@ -1332,6 +1359,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
@@ -1341,6 +1392,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
@@ -1353,6 +1410,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
@@ -1386,6 +1449,9 @@ 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
@@ -1512,6 +1578,9 @@ 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

12
LICENSE
View File

@@ -1,3 +1,15 @@
All code in this repository is licensed under the MIT License **unless a
directory contains a LICENSE file**, in which case that LICENSE file applies to
the code in that sub-directory.
For example, packages/fork-sax contains a ISC LICENSE file, thus all files
under the packages/fork-sax directory are licensed under ISC.
For example, packages/app-cli does NOT contain a LICENSE file, thus all files
under that directory are licensed under the default license, which is MIT.
* * *
MIT License
Copyright (c) 2016-2020 Laurent Cozic

View File

@@ -292,7 +292,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 +342,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)
@@ -511,47 +511,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 98%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 31%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 76%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 59%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 84%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 99%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 88%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 98%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 58%
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 96%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 30%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 75%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 58%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 83%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 96%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 86%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 96%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 57%
![](https://joplinapp.org/images/flags/country-4x3/gb.png) | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/us.png) | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 94%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 33%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 97%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 94%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 39%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/hu.png) | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 90%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 94%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 78%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 73%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 97%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 68%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 63%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 46%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 75%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ua.png) | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 84%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 73%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 95%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 98%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 99%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 94%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 38%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 93%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/hu.png) | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 88%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 92%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 76%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 71%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 94%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 66%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 61%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 45%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 73%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/ua.png) | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 92%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -8,9 +8,8 @@
"license": "MIT",
"scripts": {
"audit": "lerna-audit",
"bootstrap": "lerna bootstrap --no-ci",
"bootstrapIgnoreScripts": "lerna bootstrap --ignore-scripts --no-ci",
"bootstrapServerOnly": "lerna bootstrap --no-ci --include-dependents --include-dependencies --scope @joplin/server",
"bootstrap": "lerna bootstrap --force-local --no-ci",
"bootstrapServerOnly": "lerna bootstrap --force-local --no-ci --include-dependents --include-dependencies --scope @joplin/server",
"build": "lerna run build && npm run tsc",
"buildApiDoc": "npm start --prefix=packages/app-cli -- apidoc ../../readme/api/references/rest_api.md",
"buildDoc": "./packages/tools/build-all.sh",

View File

@@ -35,7 +35,6 @@ module.exports = {
'<rootDir>/build/',
'<rootDir>/tests/test-utils.js',
'<rootDir>/tests/test-utils-synchronizer.js',
'<rootDir>/tests/file_api_driver.js',
'<rootDir>/tests/tmp/',
'<rootDir>/tests/test data/',
],

View File

@@ -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();

View File

@@ -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",
@@ -31,7 +31,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.8.1",
"version": "2.0.0",
"bin": {
"joplin": "./main.js"
},
@@ -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",

View File

@@ -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');
// });
// }));
});

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);
});
});

View File

@@ -1,39 +0,0 @@
import { afterAllCleanUp, synchronizerStart, setupDatabaseAndSynchronizer, switchClient } from './test-utils';
import Note from '@joplin/lib/models/Note';
import BaseItem from '@joplin/lib/models/BaseItem';
import shim from '@joplin/lib/shim';
import Resource from '@joplin/lib/models/Resource';
describe('Synchronizer.sharing', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await setupDatabaseAndSynchronizer(2);
await switchClient(1);
done();
});
afterAll(async () => {
await afterAllCleanUp();
});
it('should mark link resources as shared before syncing', (async () => {
let note1 = await Note.save({ title: 'note1' });
note1 = await shim.attachFileToNote(note1, `${__dirname}/../tests/support/photo.jpg`);
const resourceId1 = (await Note.linkedResourceIds(note1.body))[0];
const note2 = await Note.save({ title: 'note2' });
await shim.attachFileToNote(note2, `${__dirname}/../tests/support/photo.jpg`);
expect((await Resource.sharedResourceIds()).length).toBe(0);
await BaseItem.updateShareStatus(note1, true);
await synchronizerStart();
const sharedResourceIds = await Resource.sharedResourceIds();
expect(sharedResourceIds.length).toBe(1);
expect(sharedResourceIds[0]).toBe(resourceId1);
}));
});

View File

@@ -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);
}));
});

View File

@@ -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:

View File

@@ -1,7 +1,9 @@
Subshell:
(
set -e
false
echo Unreachable
) && echo Great success
```
(
set -e
false
echo Unreachable
) && echo Great success
```

View File

@@ -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`

View 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>&nbsp; &nbsp; System.out.println('Hello World');</span><br/></div><div>}</div></div><div><br/></div><div>end of code block</div></div>

View File

@@ -0,0 +1,9 @@
code block:
```
public static void main(String[] args) {
    System.out.println('Hello World');
}
```
end of code block

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,135 +0,0 @@
/* eslint-disable no-unused-vars */
const uuid = require('@joplin/lib/uuid').default;
const time = require('@joplin/lib/time').default;
const { sleep, fileApi, fileContentEqual, checkThrowAsync } = require('./test-utils.js');
const shim = require('@joplin/lib/shim').default;
const fs = require('fs-extra');
const Setting = require('@joplin/lib/models/Setting').default;
const api = null;
// Adding empty test for Jest
it('will pass', () => {
expect(true).toBe(true);
});
// NOTE: These tests work with S3 and memory driver, but not
// with other targets like file system or Nextcloud.
// All this is tested in an indirect way in tests/synchronizer
// anyway.
// We keep the file here as it could be useful as a spec for
// what calls a sync target should support, but it would
// need to be fixed first.
// To test out an FileApi implementation:
// * add a SyncTarget for your driver in `test-utils.js`
// * set `syncTargetId_` to your New SyncTarget:
// `const syncTargetId_ = SyncTargetRegistry.nameToId('memory');`
// describe('fileApi', function() {
// beforeEach(async (done) => {
// api = new fileApi();
// api.clearRoot();
// done();
// });
// describe('list', function() {
// it('should return items with relative path', (async () => {
// await api.mkdir('.subfolder');
// await api.put('1', 'something on root 1');
// await api.put('.subfolder/1', 'something subfolder 1');
// await api.put('.subfolder/2', 'something subfolder 2');
// await api.put('.subfolder/3', 'something subfolder 3');
// sleep(0.8);
// const response = await api.list('.subfolder');
// const items = response.items;
// expect(items.length).toBe(3);
// expect(items[0].path).toBe('1');
// expect(items[0].updated_time).toMatch(/^\d+$/); // make sure it's using epoch timestamp
// }));
// it('should default to only files on root directory', (async () => {
// await api.mkdir('.subfolder');
// await api.put('.subfolder/1', 'something subfolder 1');
// await api.put('file1', 'something 1');
// await api.put('file2', 'something 2');
// sleep(0.6);
// const response = await api.list();
// expect(response.items.length).toBe(2);
// }));
// }); // list
// describe('delete', function() {
// it('should not error if file does not exist', (async () => {
// const hasThrown = await checkThrowAsync(async () => await api.delete('nonexistant_file'));
// expect(hasThrown).toBe(false);
// }));
// it('should delete specific file given full path', (async () => {
// await api.mkdir('deleteDir');
// await api.put('deleteDir/1', 'something 1');
// await api.put('deleteDir/2', 'something 2');
// sleep(0.4);
// await api.delete('deleteDir/1');
// let response = await api.list('deleteDir');
// expect(response.items.length).toBe(1);
// response = await api.list('deleteDir/1');
// expect(response.items.length).toBe(0);
// }));
// }); // delete
// describe('get', function() {
// it('should return null if object does not exist', (async () => {
// const response = await api.get('nonexistant_file');
// expect(response).toBe(null);
// }));
// it('should return UTF-8 encoded string by default', (async () => {
// await api.put('testnote.md', 'something 2');
// const response = await api.get('testnote.md');
// expect(response).toBe('something 2');
// }));
// it('should return a Response object and writes file to options.path, if options.target is "file"', (async () => {
// const localFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
// await api.put('testnote.md', 'something 2');
// sleep(0.2);
// const response = await api.get('testnote.md', { target: 'file', path: localFilePath });
// expect(typeof response).toBe('object');
// // expect(response.path).toBe(localFilePath);
// expect(fs.existsSync(localFilePath)).toBe(true);
// expect(fs.readFileSync(localFilePath, 'utf8')).toBe('something 2');
// }));
// }); // get
// describe('put', function() {
// it('should create file to remote path and content', (async () => {
// await api.put('putTest.md', 'I am your content');
// sleep(0.2);
// const response = await api.get('putTest.md');
// expect(response).toBe('I am your content');
// }));
// it('should upload file in options.path to remote path, if options.source is "file"', (async () => {
// const localFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
// fs.writeFileSync(localFilePath, 'I am the local file.');
// await api.put('testfile', 'ignore me', { source: 'file', path: localFilePath });
// sleep(0.2);
// const response = await api.get('testfile');
// expect(response).toBe('I am the local file.');
// }));
// }); // put
// });

View File

@@ -0,0 +1,9 @@
# Markdown file test
![../support/photo.jpg](../support/photo.jpg)
[welcome.pdf](../support/welcome.pdf)
[sample.md](sample.md)
[sample2.md](./sample.md)

View File

@@ -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 { newPluginScript } from '@joplin/lib/testing/test-utils';
import { expectNotThrow, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir, supportDir } from '@joplin/lib/testing/test-utils';
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();

View File

@@ -1,6 +1,6 @@
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());

View File

@@ -1,6 +1,6 @@
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');
const { waitForFolderCount, newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('@joplin/lib/testing/test-utils');
import Folder from '@joplin/lib/models/Folder';
describe('JoplinSettings', () => {

View File

@@ -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');
const { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('@joplin/lib/testing/test-utils');
describe('JoplinViewMenuItem', () => {

View File

@@ -1,5 +1,5 @@
import Setting from '@joplin/lib/models/Setting';
import { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '../../../test-utils';
import { newPluginService, newPluginScript, 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';

View File

@@ -1,6 +1,6 @@
import sandboxProxy, { Target } from '@joplin/lib/services/plugins/sandboxProxy';
const { setupDatabaseAndSynchronizer, switchClient } = require('../../test-utils.js');
const { setupDatabaseAndSynchronizer, switchClient } = require('@joplin/lib/testing/test-utils.js');
describe('services_plugins_sandboxProxy', function() {

View File

@@ -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];

View File

@@ -1,17 +0,0 @@
import { ExportModule, ImportModule } from './types';
/**
* Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)
*
* To implement an import or export module, you would simply define an object with various event handlers that are called
* by the application during the import/export process.
*
* See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.
*
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
*/
export default class JoplinInterop {
registerExportModule(module: ExportModule): Promise<void>;
registerImportModule(module: ImportModule): Promise<void>;
}

View File

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

View File

@@ -7,4 +7,5 @@ gui/note-viewer/pluginAssets/
pluginAssets/
gui/note-viewer/fonts/
gui/note-viewer/lib.js
gui/NoteEditor/NoteBody/TinyMCE/supportedLocales.js
gui/NoteEditor/NoteBody/TinyMCE/supportedLocales.js
runForSharingCommands-*

View File

@@ -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;

View File

@@ -13,6 +13,7 @@ import Logger, { TargetType } from '@joplin/lib/Logger';
import Setting from '@joplin/lib/models/Setting';
import actionApi from '@joplin/lib/services/rest/actionApi.desktop';
import BaseApplication from '@joplin/lib/BaseApplication';
import DebugService from '@joplin/lib/debug/DebugService';
import { _, setLocale } from '@joplin/lib/locale';
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
@@ -58,6 +59,7 @@ const commands = [
require('./gui/MainScreen/commands/openTag'),
require('./gui/MainScreen/commands/print'),
require('./gui/MainScreen/commands/renameFolder'),
require('./gui/MainScreen/commands/showShareFolderDialog'),
require('./gui/MainScreen/commands/renameTag'),
require('./gui/MainScreen/commands/search'),
require('./gui/MainScreen/commands/selectTemplate'),
@@ -100,6 +102,8 @@ const globalCommands = [
];
import editorCommandDeclarations from './gui/NoteEditor/commands/editorCommandDeclarations';
import ShareService from '@joplin/lib/services/share/ShareService';
import checkForUpdates from './checkForUpdates';
const pluginClasses = [
require('./plugins/GotoAnything').default,
@@ -164,10 +168,6 @@ class Application extends BaseApplication {
return true;
}
checkForUpdateLoggerPath() {
return `${Setting.value('profileDir')}/log-autoupdater.txt`;
}
reducer(state: AppState = appDefaultState, action: any) {
let newState = state;
@@ -708,7 +708,7 @@ class Application extends BaseApplication {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
bridge().checkForUpdates(true, bridge().window(), this.checkForUpdateLoggerPath(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
void checkForUpdates(true, bridge().window(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
@@ -730,6 +730,8 @@ class Application extends BaseApplication {
bridge().window().show();
}
void ShareService.instance().maintenance();
ResourceService.runInBackground();
if (Setting.value('env') === 'dev') {
@@ -764,15 +766,16 @@ class Application extends BaseApplication {
RevisionService.instance().runInBackground();
// Make it available to the console window - useful to call revisionService.collectRevisions()
(window as any).joplin = () => {
return {
if (Setting.value('env') === 'dev') {
(window as any).joplin = {
revisionService: RevisionService.instance(),
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
};
};
}
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);

View File

@@ -1,6 +1,5 @@
import ElectronAppWrapper from './ElectronAppWrapper';
import shim from '@joplin/lib/shim';
import { _, setLocale } from '@joplin/lib/locale';
const { dirname, toSystemSlashes } = require('@joplin/lib/path-utils');
const { BrowserWindow, nativeTheme } = require('electron');
@@ -174,11 +173,6 @@ export class Bridge {
return require('electron').shell.openPath(fullPath);
}
checkForUpdates(inBackground: boolean, window: any, logFilePath: string, options: any) {
const { checkForUpdates } = require('./checkForUpdates.js');
checkForUpdates(inBackground, window, logFilePath, options);
}
buildDir() {
return this.electronApp().buildDir();
}

View File

@@ -1,44 +1,42 @@
const { dialog } = require('electron');
const shim = require('@joplin/lib/shim').default;
const Logger = require('@joplin/lib/Logger').default;
const { _ } = require('@joplin/lib/locale');
const fetch = require('node-fetch');
import shim from '@joplin/lib/shim';
import Logger from '@joplin/lib/Logger';
import { _ } from '@joplin/lib/locale';
import bridge from './services/bridge';
import KvStore from '@joplin/lib/services/KvStore';
const { fileExtension } = require('@joplin/lib/path-utils');
const ArrayUtils = require('@joplin/lib/ArrayUtils');
const packageInfo = require('./packageInfo.js');
const compareVersions = require('compare-versions');
let autoUpdateLogger_ = new Logger();
const logger = Logger.create('checkForUpdates');
let checkInBackground_ = false;
let isCheckingForUpdate_ = false;
let parentWindow_ = null;
function showErrorMessageBox(message) {
return dialog.showMessageBox(parentWindow_, {
type: 'error',
message: message,
});
interface CheckForUpdateOptions {
includePreReleases?: boolean;
}
function onCheckStarted() {
autoUpdateLogger_.info('checkForUpdates: Starting...');
logger.info('Starting...');
isCheckingForUpdate_ = true;
}
function onCheckEnded() {
autoUpdateLogger_.info('checkForUpdates: Done.');
logger.info('Done.');
isCheckingForUpdate_ = false;
}
function getMajorMinorTagName(tagName) {
function getMajorMinorTagName(tagName: string) {
const s = tagName.split('.');
s.pop();
return s.join('.');
}
async function fetchLatestRelease(options) {
async function fetchLatestRelease(options: CheckForUpdateOptions) {
options = Object.assign({}, { includePreReleases: false }, options);
const response = await fetch('https://api.github.com/repos/laurent22/joplin/releases');
const response = await shim.fetch('https://api.github.com/repos/laurent22/joplin/releases');
if (!response.ok) {
const responseText = await response.text();
@@ -104,7 +102,7 @@ async function fetchLatestRelease(options) {
}
}
function cleanUpReleaseNotes(releaseNotes) {
function cleanUpReleaseNotes(releaseNotes: string[]) {
const lines = releaseNotes.join('\n\n* * *\n\n').split('\n');
const output = [];
for (const line of lines) {
@@ -129,7 +127,7 @@ async function fetchLatestRelease(options) {
};
}
function truncateText(text, length) {
function truncateText(text: string, length: number) {
let truncated = text.substring(0, length);
const lastNewLine = truncated.lastIndexOf('\n');
// Cut off at a line break unless we'd be cutting off half the text
@@ -141,66 +139,80 @@ function truncateText(text, length) {
return truncated;
}
function checkForUpdates(inBackground, window, logFilePath, options) {
async function getSkippedVersions(): Promise<string[]> {
const r = await KvStore.instance().value<string>('updateCheck::skippedVersions');
return r ? JSON.parse(r) : [];
}
async function isSkippedVersion(v: string): Promise<boolean> {
const versions = await getSkippedVersions();
return versions.includes(v);
}
async function addSkippedVersion(s: string) {
let versions = await getSkippedVersions();
versions.push(s);
versions = ArrayUtils.unique(versions);
await KvStore.instance().setValue('updateCheck::skippedVersions', JSON.stringify(versions));
}
export default async function checkForUpdates(inBackground: boolean, parentWindow: any, options: CheckForUpdateOptions) {
if (isCheckingForUpdate_) {
autoUpdateLogger_.info('checkForUpdates: Skipping check because it is already running');
logger.info('Skipping check because it is already running');
return;
}
parentWindow_ = window;
onCheckStarted();
if (logFilePath && !autoUpdateLogger_.targets().length) {
autoUpdateLogger_ = new Logger();
autoUpdateLogger_.addTarget('file', { path: logFilePath });
autoUpdateLogger_.setLevel(Logger.LEVEL_DEBUG);
autoUpdateLogger_.info('checkForUpdates: Initializing...');
}
checkInBackground_ = inBackground;
autoUpdateLogger_.info(`checkForUpdates: Checking with options ${JSON.stringify(options)}`);
logger.info(`Checking with options ${JSON.stringify(options)}`);
fetchLatestRelease(options).then(async (release) => {
autoUpdateLogger_.info(`Current version: ${packageInfo.version}`);
autoUpdateLogger_.info(`Latest version: ${release.version}`);
autoUpdateLogger_.info('Is Pre-release:', release.prerelease);
try {
const release = await fetchLatestRelease(options);
logger.info(`Current version: ${packageInfo.version}`);
logger.info(`Latest version: ${release.version}`);
logger.info('Is Pre-release:', release.prerelease);
if (compareVersions(release.version, packageInfo.version) <= 0) {
if (!checkInBackground_) {
await dialog.showMessageBox({
type: 'info',
message: _('Current version is up-to-date.'),
buttons: [_('OK')],
});
await bridge().showMessageBox(_('Current version is up-to-date.'));
}
} else {
const fullReleaseNotes = release.notes.trim() ? `\n\n${release.notes.trim()}` : '';
const MAX_RELEASE_NOTES_LENGTH = 1000;
const truncateReleaseNotes = fullReleaseNotes.length > MAX_RELEASE_NOTES_LENGTH;
const releaseNotes = truncateReleaseNotes ? truncateText(fullReleaseNotes, MAX_RELEASE_NOTES_LENGTH) : fullReleaseNotes;
const shouldSkip = checkInBackground_ && await isSkippedVersion(release.version);
const newVersionString = release.prerelease ? _('%s (pre-release)', release.version) : release.version;
if (shouldSkip) {
logger.info('Not displaying notification because version has been skipped');
} else {
const fullReleaseNotes = release.notes.trim() ? `\n\n${release.notes.trim()}` : '';
const MAX_RELEASE_NOTES_LENGTH = 1000;
const truncateReleaseNotes = fullReleaseNotes.length > MAX_RELEASE_NOTES_LENGTH;
const releaseNotes = truncateReleaseNotes ? truncateText(fullReleaseNotes, MAX_RELEASE_NOTES_LENGTH) : fullReleaseNotes;
const result = await dialog.showMessageBox(parentWindow_, {
type: 'info',
message: `${_('An update is available, do you want to download it now?')}`,
detail: `${_('Your version: %s', packageInfo.version)}\n${_('New version: %s', newVersionString)}${releaseNotes}`,
buttons: [_('Download'), _('Cancel'), _('Full changelog')],
cancelId: 1,
});
const newVersionString = release.prerelease ? _('%s (pre-release)', release.version) : release.version;
const buttonIndex = result.response;
if (buttonIndex === 0) require('electron').shell.openExternal(release.downloadUrl ? release.downloadUrl : release.pageUrl);
if (buttonIndex === 2) require('electron').shell.openExternal('https://joplinapp.org/changelog/');
const buttonIndex = await bridge().showMessageBox(parentWindow, {
type: 'info',
message: `${_('An update is available, do you want to download it now?')}`,
detail: `${_('Your version: %s', packageInfo.version)}\n${_('New version: %s', newVersionString)}${releaseNotes}`,
buttons: [_('Download'), _('Skip this version'), _('Full changelog'), _('Cancel')],
cancelId: 3,
});
if (buttonIndex === 0) {
bridge().openExternal(release.downloadUrl ? release.downloadUrl : release.pageUrl);
} else if (buttonIndex === 1) {
await addSkippedVersion(release.version);
} else if (buttonIndex === 2) {
bridge().openExternal('https://joplinapp.org/changelog/');
}
}
}
}).catch(error => {
autoUpdateLogger_.error(error);
if (!checkInBackground_) showErrorMessageBox(error.message);
}).then(() => {
} catch (error) {
logger.error(error);
if (!checkInBackground_) await bridge().showErrorMessageBox(error.message);
} finally {
onCheckEnded();
});
}
}
module.exports.checkForUpdates = checkForUpdates;

View File

@@ -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().loadManifests();
} 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 }}>

View File

@@ -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>

View File

@@ -0,0 +1,40 @@
import styled from 'styled-components';
const DialogModalLayer = styled.div`
z-index: 9999;
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.6);
align-items: flex-start;
justify-content: center;
overflow: hidden;
`;
const DialogRoot = styled.div`
background-color: ${props => props.theme.backgroundColor};
padding: 16px;
box-shadow: 6px 6px 20px rgba(0,0,0,0.5);
margin-top: 20px;
min-height: fit-content;
display: flex;
flex-direction: column;
width: 50%;
`;
interface Props {
renderContent: Function;
}
export default function Dialog(props: Props) {
return (
<DialogModalLayer>
<DialogRoot>
{props.renderContent()}
</DialogRoot>
</DialogModalLayer>
);
}

View File

@@ -2,7 +2,26 @@ const React = require('react');
const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme');
function DialogButtonRow(props) {
export interface ButtonSpec {
name: string;
label: string;
}
export interface ClickEvent {
buttonName: string;
}
interface Props {
themeId: number;
onClick?: (event: ClickEvent)=> void;
okButtonShow?: boolean;
cancelButtonShow?: boolean;
cancelButtonLabel?: string;
okButtonRef?: any;
customButtons?: ButtonSpec[];
}
export default function DialogButtonRow(props: Props) {
const theme = themeStyle(props.themeId);
const okButton_click = () => {
@@ -13,7 +32,11 @@ function DialogButtonRow(props) {
if (props.onClick) props.onClick({ buttonName: 'cancel' });
};
const onKeyDown = (event) => {
const customButton_click = (event: ClickEvent) => {
if (props.onClick) props.onClick(event);
};
const onKeyDown = (event: any) => {
if (event.keyCode === 13) {
okButton_click();
} else if (event.keyCode === 27) {
@@ -23,6 +46,16 @@ function DialogButtonRow(props) {
const buttonComps = [];
if (props.customButtons) {
for (const b of props.customButtons) {
buttonComps.push(
<button key={b.name} style={theme.buttonStyle} onClick={() => customButton_click({ buttonName: b.name })} onKeyDown={onKeyDown}>
{b.label}
</button>
);
}
}
if (props.okButtonShow !== false) {
buttonComps.push(
<button key="ok" style={theme.buttonStyle} onClick={okButton_click} ref={props.okButtonRef} onKeyDown={onKeyDown}>
@@ -41,5 +74,3 @@ function DialogButtonRow(props) {
return <div style={{ textAlign: 'right', marginTop: 10 }}>{buttonComps}</div>;
}
module.exports = DialogButtonRow;

View File

@@ -0,0 +1,21 @@
import styled from 'styled-components';
const Root = styled.div`
font-family: ${props => props.theme.fontFamily};
font-size: ${props => props.theme.fontSize * 1.5};
line-height: 1.6em;
color: ${props => props.theme.color};
font-weight: bold;
margin-bottom: 1.2em;
`;
interface Props {
title: string;
}
export default function DialogTitle(props: Props) {
return (
<Root>{props.title}</Root>
);
}

View File

@@ -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

View File

@@ -29,12 +29,17 @@ import { themeStyle } from '@joplin/lib/theme';
import validateLayout from '../ResizableLayout/utils/validateLayout';
import iterateItems from '../ResizableLayout/utils/iterateItems';
import removeItem from '../ResizableLayout/utils/removeItem';
import EncryptionService from '@joplin/lib/services/EncryptionService';
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');
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
const PluginManager = require('@joplin/lib/services/PluginManager');
import EncryptionService from '@joplin/lib/services/EncryptionService';
const ipcRenderer = require('electron').ipcRenderer;
interface LayerModalState {
@@ -63,15 +68,22 @@ interface Props {
settingEditorCodeView: boolean;
pluginsLegacy: any;
startupPluginsLoaded: boolean;
shareInvitations: ShareInvitation[];
isSafeMode: boolean;
}
interface ShareFolderDialogOptions {
folderId: string;
visible: boolean;
}
interface State {
promptOptions: any;
modalLayer: LayerModalState;
notePropertiesDialogOptions: any;
noteContentPropertiesDialogOptions: any;
shareNoteDialogOptions: any;
shareFolderDialogOptions: ShareFolderDialogOptions;
}
const StyledUserWebviewDialogContainer = styled.div`
@@ -105,6 +117,7 @@ const commands = [
require('./commands/newTodo'),
require('./commands/print'),
require('./commands/renameFolder'),
require('./commands/showShareFolderDialog'),
require('./commands/renameTag'),
require('./commands/search'),
require('./commands/selectTemplate'),
@@ -144,6 +157,10 @@ class MainScreenComponent extends React.Component<Props, State> {
notePropertiesDialogOptions: {},
noteContentPropertiesDialogOptions: {},
shareNoteDialogOptions: {},
shareFolderDialogOptions: {
visible: false,
folderId: '',
},
};
this.updateMainLayout(this.buildLayout(props.plugins));
@@ -155,6 +172,7 @@ class MainScreenComponent extends React.Component<Props, State> {
this.notePropertiesDialog_close = this.notePropertiesDialog_close.bind(this);
this.noteContentPropertiesDialog_close = this.noteContentPropertiesDialog_close.bind(this);
this.shareNoteDialog_close = this.shareNoteDialog_close.bind(this);
this.shareFolderDialog_close = this.shareFolderDialog_close.bind(this);
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
this.resizableLayout_renderItem = this.resizableLayout_renderItem.bind(this);
this.resizableLayout_moveButtonClick = this.resizableLayout_moveButtonClick.bind(this);
@@ -204,6 +222,10 @@ class MainScreenComponent extends React.Component<Props, State> {
return newLayout !== layout ? validateLayout(newLayout) : layout;
}
private showShareInvitationNotification(props: Props): boolean {
return !!props.shareInvitations.find(i => i.status === 0);
}
private buildLayout(plugins: PluginStates): LayoutItem {
const rootLayoutSize = this.rootLayoutSize();
@@ -213,6 +235,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');
}
@@ -268,10 +298,14 @@ class MainScreenComponent extends React.Component<Props, State> {
this.setState({ noteContentPropertiesDialogOptions: {} });
}
shareNoteDialog_close() {
private shareNoteDialog_close() {
this.setState({ shareNoteDialogOptions: {} });
}
private shareFolderDialog_close() {
this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } });
}
updateMainLayout(layout: LayoutItem) {
this.props.dispatch({
type: 'MAIN_LAYOUT_SET',
@@ -321,6 +355,13 @@ class MainScreenComponent extends React.Component<Props, State> {
});
}
if (this.state.shareFolderDialogOptions !== prevState.shareFolderDialogOptions) {
this.props.dispatch({
type: this.state.shareFolderDialogOptions && this.state.shareFolderDialogOptions.visible ? 'VISIBLE_DIALOGS_ADD' : 'VISIBLE_DIALOGS_REMOVE',
name: 'shareFolder',
});
}
if (this.props.mainLayout !== prevProps.mainLayout) {
const toSave = saveLayout(this.props.mainLayout);
Setting.setValue('ui.layout', toSave);
@@ -496,6 +537,12 @@ class MainScreenComponent extends React.Component<Props, State> {
bridge().restart();
};
const onInvitationRespond = async (shareUserId: string, accept: boolean) => {
await ShareService.instance().respondInvitation(shareUserId, accept);
await ShareService.instance().refreshShareInvitations();
void reg.scheduleSync(1000);
};
let msg = null;
if (this.props.isSafeMode) {
@@ -516,15 +563,6 @@ class MainScreenComponent extends React.Component<Props, State> {
</a>
</span>
);
} else if (this.props.hasDisabledSyncItems) {
msg = (
<span>
{_('Some items cannot be synchronised.')}{' '}
<a href="#" onClick={() => onViewStatusScreen()}>
{_('View them now')}
</a>
</span>
);
} else if (this.props.hasDisabledEncryptionItems) {
msg = (
<span>
@@ -534,15 +572,6 @@ class MainScreenComponent extends React.Component<Props, State> {
</a>
</span>
);
} else if (this.props.showMissingMasterKeyMessage) {
msg = (
<span>
{_('One or more master keys need a password.')}{' '}
<a href="#" onClick={() => onViewEncryptionConfigScreen()}>
{_('Set the password')}
</a>
</span>
);
} else if (this.props.showNeedUpgradingMasterKeyMessage) {
msg = (
<span>
@@ -561,6 +590,40 @@ class MainScreenComponent extends React.Component<Props, State> {
</a>
</span>
);
} else if (this.showShareInvitationNotification(this.props)) {
const invitation = this.props.shareInvitations[0];
const sharer = invitation.share.user;
msg = (
<span>
{_('%s (%s) would like to share a notebook with you.', sharer.full_name, sharer.email)}{' '}
<a href="#" onClick={() => onInvitationRespond(invitation.id, true)}>
{_('Accept')}
</a>
{' / '}
<a href="#" onClick={() => onInvitationRespond(invitation.id,true)}>
{_('Reject')}
</a>
</span>
);
} else if (this.props.hasDisabledSyncItems) {
msg = (
<span>
{_('Some items cannot be synchronised.')}{' '}
<a href="#" onClick={() => onViewStatusScreen()}>
{_('View them now')}
</a>
</span>
);
} else if (this.props.showMissingMasterKeyMessage) {
msg = (
<span>
{_('One or more master keys need a password.')}{' '}
<a href="#" onClick={() => onViewEncryptionConfigScreen()}>
{_('Set the password')}
</a>
</span>
);
}
return (
@@ -572,7 +635,7 @@ class MainScreenComponent extends React.Component<Props, State> {
messageBoxVisible(props: Props = null) {
if (!props) props = this.props;
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode;
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props);
}
registerCommands() {
@@ -739,6 +802,7 @@ class MainScreenComponent extends React.Component<Props, State> {
const notePropertiesDialogOptions = this.state.notePropertiesDialogOptions;
const noteContentPropertiesDialogOptions = this.state.noteContentPropertiesDialogOptions;
const shareNoteDialogOptions = this.state.shareNoteDialogOptions;
const shareFolderDialogOptions = this.state.shareFolderDialogOptions;
const layoutComp = this.props.mainLayout ? (
<ResizableLayout
@@ -759,6 +823,7 @@ class MainScreenComponent extends React.Component<Props, State> {
{noteContentPropertiesDialogOptions.visible && <NoteContentPropertiesDialog markupLanguage={noteContentPropertiesDialogOptions.markupLanguage} themeId={this.props.themeId} onClose={this.noteContentPropertiesDialog_close} text={noteContentPropertiesDialogOptions.text}/>}
{notePropertiesDialogOptions.visible && <NotePropertiesDialog themeId={this.props.themeId} noteId={notePropertiesDialogOptions.noteId} onClose={this.notePropertiesDialog_close} onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick} />}
{shareNoteDialogOptions.visible && <ShareNoteDialog themeId={this.props.themeId} noteIds={shareNoteDialogOptions.noteIds} onClose={this.shareNoteDialog_close} />}
{shareFolderDialogOptions.visible && <ShareFolderDialog themeId={this.props.themeId} folderId={shareFolderDialogOptions.folderId} onClose={this.shareFolderDialog_close} />}
<PromptDialog autocomplete={promptOptions && 'autocomplete' in promptOptions ? promptOptions.autocomplete : null} defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''} themeId={this.props.themeId} style={styles.prompt} onClose={this.promptOnClose_} label={promptOptions ? promptOptions.label : ''} description={promptOptions ? promptOptions.description : null} visible={!!this.state.promptOptions} buttons={promptOptions && 'buttons' in promptOptions ? promptOptions.buttons : null} inputType={promptOptions && 'inputType' in promptOptions ? promptOptions.inputType : null} />
@@ -794,6 +859,7 @@ const mapStateToProps = (state: AppState) => {
layoutMoveMode: state.layoutMoveMode,
mainLayout: state.mainLayout,
startupPluginsLoaded: state.startupPluginsLoaded,
shareInvitations: state.shareService.shareInvitations,
isSafeMode: state.settings.isSafeMode,
};
};

View File

@@ -0,0 +1,23 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
export const declaration: CommandDeclaration = {
name: 'showShareFolderDialog',
label: () => _('Share notebook...'),
};
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async (context: CommandContext, folderId: string = null) => {
folderId = folderId || context.state.selectedFolderId;
comp.setState({
shareFolderDialogOptions: {
folderId,
visible: true,
},
});
},
enabledCondition: 'joplinServerConnected && (folderIsShareRootAndOwnedByUser || !folderIsShared)',
};
};

View File

@@ -18,5 +18,6 @@ export const runtime = (comp: any): CommandRuntime => {
},
});
},
enabledCondition: 'joplinServerConnected && someNotesSelected',
};
};

View File

@@ -17,6 +17,7 @@ import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerS
import menuCommandNames from './menuCommandNames';
import stateToWhenClauseContext from '../services/commands/stateToWhenClauseContext';
import bridge from '../services/bridge';
import checkForUpdates from '../checkForUpdates';
const { connect } = require('react-redux');
import { reg } from '@joplin/lib/registry';
@@ -89,6 +90,7 @@ interface Props {
['spellChecker.enabled']: boolean;
['spellChecker.language']: string;
plugins: PluginStates;
customCss: string;
}
const commandNames: string[] = menuCommandNames();
@@ -312,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,
}
);
},
});
@@ -430,7 +435,7 @@ function useMenu(props: Props) {
toolsItems.push(SpellCheckerService.instance().spellCheckerConfigMenuItem(props['spellChecker.language'], props['spellChecker.enabled']));
function _checkForUpdates() {
bridge().checkForUpdates(false, bridge().window(), `${Setting.value('profileDir')}/log-autoupdater.txt`, { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
void checkForUpdates(false, bridge().window(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
function _showAbout() {
@@ -694,11 +699,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,
],
@@ -817,6 +829,7 @@ function useMenu(props: Props) {
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.folder,
rootMenus.note,
rootMenus.tools,
rootMenus.help,
@@ -851,7 +864,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);
@@ -908,6 +921,7 @@ const mapStateToProps = (state: AppState) => {
['spellChecker.language']: state.settings['spellChecker.language'],
['spellChecker.enabled']: state.settings['spellChecker.enabled'],
plugins: state.pluginService.plugins,
customCss: state.customCss,
};
};

View File

@@ -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 = [];

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
import { useState, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import DialogButtonRow from './DialogButtonRow';
const { themeStyle } = require('@joplin/lib/theme');
const DialogButtonRow = require('./DialogButtonRow.min');
const Countable = require('countable');
import markupLanguageUtils from '../utils/markupLanguageUtils';
@@ -24,7 +24,7 @@ interface KeyToLabelMap {
let markupToHtml_: any = null;
function markupToHtml() {
if (markupToHtml_) return markupToHtml_;
markupToHtml_ = markupLanguageUtils.newMarkupToHtml({});
markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
return markupToHtml_;
}

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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')) : ''}

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,
};
};

View File

@@ -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}

View File

@@ -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}

View File

@@ -2,7 +2,7 @@ const React = require('react');
const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme');
const time = require('@joplin/lib/time').default;
const DialogButtonRow = require('./DialogButtonRow.min');
const DialogButtonRow = require('./DialogButtonRow').default;
const Datetime = require('react-datetime');
const Note = require('@joplin/lib/models/Note').default;
const formatcoords = require('formatcoords');

View File

@@ -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',
});

View File

@@ -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

View File

@@ -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}

View File

@@ -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;
}

View File

@@ -126,7 +126,7 @@ function SearchBar(props: Props) {
}, [props.notesParentType, onExitSearch]);
return (
<Root>
<Root className="search-bar">
<SearchInput
inputRef={props.inputRef}
value={query}

View File

@@ -0,0 +1,336 @@
import Dialog from '../Dialog';
import DialogButtonRow, { ClickEvent, ButtonSpec } from '../DialogButtonRow';
import DialogTitle from '../DialogTitle';
import { _ } from '@joplin/lib/locale';
import { useEffect, useState } from 'react';
import { FolderEntity } from '@joplin/lib/services/database/types';
import Folder from '@joplin/lib/models/Folder';
import ShareService from '@joplin/lib/services/share/ShareService';
import styled from 'styled-components';
import StyledFormLabel from '../style/StyledFormLabel';
import StyledInput from '../style/StyledInput';
import Button from '../Button/Button';
import Logger from '@joplin/lib/Logger';
import StyledMessage from '../style/StyledMessage';
import { ShareUserStatus, StateShare, StateShareUser } from '@joplin/lib/services/share/reducer';
import { State } from '@joplin/lib/reducer';
import { connect } from 'react-redux';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('ShareFolderDialog');
const StyledFolder = styled.div`
border: 1px solid ${(props) => props.theme.dividerColor};
padding: 0.5em;
margin-bottom: 1em;
display: flex;
align-items: center;
`;
const StyledRecipientControls = styled.div`
display: flex;
flex-direction: row;
`;
const StyledRecipientInput = styled(StyledInput)`
width: 100%;
margin-right: 10px;
`;
const StyledAddRecipient = styled.div`
margin-bottom: 1em;
`;
const StyledRecipient = styled(StyledMessage)`
display: flex;
flex-direction: row;
padding: .6em 1em;
background-color: ${props => props.index % 2 === 0 ? props.theme.backgroundColor : props.theme.oddBackgroundColor};
align-items: center;
`;
const StyledRecipientName = styled.div`
display: flex;
flex: 1;
`;
const StyledRecipientStatusIcon = styled.i`
margin-right: .6em;
`;
const StyledRecipients = styled.div`
`;
const StyledRecipientList = styled.div`
border: 1px solid ${(props: any) => props.theme.dividerColor};
border-radius: 3px;
height: 300px;
overflow-x: hidden;
overflow-y: scroll;
`;
const StyledError = styled(StyledMessage)`
word-break: break-all;
margin-bottom: 1em;
`;
const StyledShareState = styled(StyledMessage)`
word-break: break-all;
margin-bottom: 1em;
`;
const StyledIcon = styled.i`
margin-right: 8px;
`;
interface Props {
themeId: number;
folderId: string;
onClose(): void;
shares: StateShare[];
shareUsers: Record<string, StateShareUser[]>;
}
interface RecipientDeleteEvent {
shareUserId: string;
}
interface AsyncEffectEvent {
cancelled: boolean;
}
function useAsyncEffect(effect: Function, dependencies: any[]) {
useEffect(() => {
const event = { cancelled: false };
effect(event);
return () => {
event.cancelled = true;
};
}, dependencies);
}
enum ShareState {
Idle = 0,
Synchronizing = 1,
Creating = 2,
}
function ShareFolderDialog(props: Props) {
const [folder, setFolder] = useState<FolderEntity>(null);
const [recipientEmail, setRecipientEmail] = useState<string>('');
const [latestError, setLatestError] = useState<Error>(null);
const [share, setShare] = useState<StateShare>(null);
const [shareUsers, setShareUsers] = useState<StateShareUser[]>([]);
const [shareState, setShareState] = useState<ShareState>(ShareState.Idle);
const [customButtons, setCustomButtons] = useState<ButtonSpec[]>([]);
async function synchronize(event: AsyncEffectEvent = null) {
setShareState(ShareState.Synchronizing);
await reg.waitForSyncFinishedThenSync();
if (event && event.cancelled) return;
setShareState(ShareState.Idle);
}
useAsyncEffect(async (event: AsyncEffectEvent) => {
const f = await Folder.load(props.folderId);
if (event.cancelled) return;
setFolder(f);
}, [props.folderId]);
useEffect(() => {
void ShareService.instance().refreshShares();
}, []);
useAsyncEffect(async (event: AsyncEffectEvent) => {
await synchronize(event);
}, []);
useEffect(() => {
const s = props.shares.find(s => s.folder_id === props.folderId);
setShare(s);
}, [props.shares]);
useEffect(() => {
if (!share) return;
void ShareService.instance().refreshShareUsers(share.id);
}, [share]);
useEffect(() => {
setCustomButtons(share ? [{
name: 'unshare',
label: _('Unshare'),
}] : []);
}, [share]);
useEffect(() => {
if (!share) return;
const sus = props.shareUsers[share.id];
if (!sus) return;
setShareUsers(sus);
}, [share, props.shareUsers]);
useEffect(() => {
void ShareService.instance().refreshShares();
}, [props.folderId]);
async function shareRecipient_click() {
setShareState(ShareState.Creating);
try {
setLatestError(null);
const share = await ShareService.instance().shareFolder(props.folderId);
await ShareService.instance().addShareRecipient(share.id, recipientEmail);
await Promise.all([
ShareService.instance().refreshShares(),
ShareService.instance().refreshShareUsers(share.id),
]);
setRecipientEmail('');
await synchronize();
} catch (error) {
logger.error(error);
setLatestError(error);
} finally {
setShareState(ShareState.Idle);
}
}
function recipientEmail_change(event: any) {
setRecipientEmail(event.target.value);
}
async function recipient_delete(event: RecipientDeleteEvent) {
if (!confirm(_('Delete this invitation? The recipient will no longer have access to this shared notebook.'))) return;
await ShareService.instance().deleteShareRecipient(event.shareUserId);
await ShareService.instance().refreshShareUsers(share.id);
}
function renderFolder() {
return (
<StyledFolder>
<StyledIcon className="icon-notebooks"/>{folder ? folder.title : '...'}
</StyledFolder>
);
}
function renderAddRecipient() {
const disabled = shareState !== ShareState.Idle;
return (
<StyledAddRecipient>
<StyledFormLabel>{_('Add recipient:')}</StyledFormLabel>
<StyledRecipientControls>
<StyledRecipientInput disabled={disabled} type="email" placeholder="example@domain.com" value={recipientEmail} onChange={recipientEmail_change} />
<Button disabled={disabled} title={_('Share')} onClick={shareRecipient_click}></Button>
</StyledRecipientControls>
</StyledAddRecipient>
);
}
function renderRecipient(index: number, shareUser: StateShareUser) {
const statusToIcon = {
[ShareUserStatus.Waiting]: 'fas fa-question',
[ShareUserStatus.Rejected]: 'fas fa-times',
[ShareUserStatus.Accepted]: 'fas fa-check',
};
const statusToMessage = {
[ShareUserStatus.Waiting]: _('Recipient has not yet accepted the invitation'),
[ShareUserStatus.Rejected]: _('Recipient has rejected the invitation'),
[ShareUserStatus.Accepted]: _('Recipient has accepted the invitation'),
};
return (
<StyledRecipient key={shareUser.user.email} index={index}>
<StyledRecipientName>{shareUser.user.email}</StyledRecipientName>
<StyledRecipientStatusIcon title={statusToMessage[shareUser.status]} className={statusToIcon[shareUser.status]}></StyledRecipientStatusIcon>
<Button iconName="far fa-times-circle" onClick={() => recipient_delete({ shareUserId: shareUser.id })}/>
</StyledRecipient>
);
}
function renderRecipients() {
const listItems = shareUsers.map((su: StateShareUser, index: number) => renderRecipient(index, su));
return (
<StyledRecipients>
<StyledFormLabel>{_('Recipients:')}</StyledFormLabel>
<StyledRecipientList>
{listItems}
</StyledRecipientList>
</StyledRecipients>
);
}
function renderError() {
if (!latestError) return null;
return (
<StyledError type="error">
{latestError.message}
</StyledError>
);
}
function renderShareState() {
if (shareState === ShareState.Idle) return null;
const messages = {
[ShareState.Synchronizing]: _('Synchronizing...'),
[ShareState.Creating]: _('Sharing notebook...'),
};
const message = messages[shareState];
if (!message) throw new Error(`Unsupported state: ${shareState}`);
return (
<StyledShareState>
{message}
</StyledShareState>
);
}
async function buttonRow_click(event: ClickEvent) {
if (event.buttonName === 'unshare') {
if (!confirm(_('Unshare this notebook? The recipients will no longer have access to its content.'))) return;
await ShareService.instance().unshareFolder(props.folderId);
void synchronize();
}
props.onClose();
}
function renderContent() {
return (
<div>
<DialogTitle title={_('Share Notebook')}/>
{renderFolder()}
{renderAddRecipient()}
{renderShareState()}
{renderError()}
{renderRecipients()}
<DialogButtonRow
themeId={props.themeId}
onClick={buttonRow_click}
okButtonShow={false}
cancelButtonLabel={_('Close')}
customButtons={customButtons}
/>
</div>
);
}
return (
<Dialog renderContent={renderContent}/>
);
}
const mapStateToProps = (state: State) => {
return {
shares: state.shareService.shares,
shareUsers: state.shareService.shareUsers,
};
};
export default connect(mapStateToProps)(ShareFolderDialog as any);

View File

@@ -4,25 +4,27 @@ import JoplinServerApi from '@joplin/lib/JoplinServerApi';
import { _, _n } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
import Setting from '@joplin/lib/models/Setting';
import BaseItem from '@joplin/lib/models/BaseItem';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
const { themeStyle, buildStyle } = require('@joplin/lib/theme');
const DialogButtonRow = require('./DialogButtonRow.min');
import DialogButtonRow from './DialogButtonRow';
import { themeStyle, buildStyle } from '@joplin/lib/theme';
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 = [];
@@ -83,26 +89,19 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
void fetchNotes();
}, [props.noteIds]);
const fileApi = async () => {
const syncTarget = reg.syncTarget() as SyncTargetJoplinServer;
return syncTarget.fileApi();
};
const joplinServerApi = async (): Promise<JoplinServerApi> => {
return (await fileApi()).driver().api();
};
const buttonRow_click = () => {
props.onClose();
};
const copyLinksToClipboard = (api: JoplinServerApi, shares: SharesMap) => {
const copyLinksToClipboard = (shares: StateShare[]) => {
const links = [];
for (const n in shares) links.push(api.shareUrl(shares[n]));
for (const share of shares) links.push(ShareService.instance().shareUrl(share));
clipboard.writeText(links.join('\n'));
};
const shareLinkButton_click = async () => {
const service = ShareService.instance();
let hasSynced = false;
let tryToSync = false;
while (true) {
@@ -116,31 +115,22 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
setSharesState('creating');
const api = await joplinServerApi();
const newShares = Object.assign({}, shares);
let sharedStatusChanged = false;
const newShares: StateShare[] = [];
for (const note of notes) {
const fullPath = (await fileApi()).fullPath(BaseItem.systemPath(note.id));
const share = await api.shareFile(fullPath);
newShares[note.id] = share;
const changed = await BaseItem.updateShareStatus(note, true);
if (changed) sharedStatusChanged = true;
const share = await service.shareNote(note.id);
newShares.push(share);
}
setShares(newShares);
setSharesState('synchronizing');
await reg.waitForSyncFinishedThenSync();
setSharesState('creating');
if (sharedStatusChanged) {
setSharesState('synchronizing');
await reg.waitForSyncFinishedThenSync();
setSharesState('creating');
}
copyLinksToClipboard(api, newShares);
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);
@@ -158,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>;
};
@@ -202,24 +211,33 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
return <div style={theme.textStyle}>{_('Note: When a note is shared, it will no longer be encrypted on the server.')}<hr/></div>;
}
function renderBetaWarningMessage() {
return <div style={theme.textStyle}>{'Sharing notes via Joplin Server is a Beta feature and the API might change later on. What it means is that if you share a note, the link might become invalid after an upgrade, and you will have to share it again.'}</div>;
}
const rootStyle = Object.assign({}, theme.dialogBox);
rootStyle.width = '50%';
return (
<div style={theme.dialogModalLayer}>
<div style={rootStyle}>
<div style={theme.dialogTitle}>{_('Share Notes')}</div>
function renderContent() {
return (
<div>
<DialogTitle title={_('Share Notes')}/>
{renderNoteList(notes)}
<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()}
{renderBetaWarningMessage()}
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
<DialogButtonRow
themeId={props.themeId}
onClick={buttonRow_click}
okButtonShow={false}
cancelButtonLabel={_('Close')}
/>
</div>
</div>
);
}
return (
<Dialog renderContent={renderContent}/>
);
}
const mapStateToProps = (state: AppState) => {
return {
shares: state.shareService.shares.filter(s => !!s.note_id),
};
};
export default connect(mapStateToProps)(ShareNoteDialog as any);

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyledRoot, StyledAddButton, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { StyledRoot, StyledAddButton, StyledShareIcon, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { ButtonLevel } from '../Button/Button';
import CommandService from '@joplin/lib/services/CommandService';
import InteropService from '@joplin/lib/services/interop/InteropService';
@@ -12,13 +12,16 @@ import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins
import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
import { AppState } from '../../app';
import { ModelType } from '@joplin/lib/BaseModel';
const { connect } = require('react-redux');
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
import BaseModel from '@joplin/lib/BaseModel';
import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note';
import Tag from '@joplin/lib/models/Tag';
import Logger from '@joplin/lib/Logger';
import { FolderEntity } from '@joplin/lib/services/database/types';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import { store } from '@joplin/lib/reducer';
const { connect } = require('react-redux');
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
const { themeStyle } = require('@joplin/lib/theme');
const bridge = require('electron').remote.require('./bridge').default;
const Menu = bridge().Menu;
@@ -26,6 +29,8 @@ const MenuItem = bridge().MenuItem;
const { substrWithEllipsis } = require('@joplin/lib/string-utils');
const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids');
const logger = Logger.create('Sidebar');
interface Props {
themeId: number;
dispatch: Function;
@@ -70,12 +75,14 @@ function ExpandLink(props: any) {
}
function FolderItem(props: any) {
const { hasChildren, isExpanded, depth, selected, folderId, folderTitle, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_ } = props;
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}
@@ -83,6 +90,7 @@ function FolderItem(props: any) {
isConflictFolder={folderId === Folder.conflictFolderId()}
href="#"
selected={selected}
shareId={shareId}
data-id={folderId}
data-type={BaseModel.TYPE_FOLDER}
onContextMenu={itemContextMenu}
@@ -92,7 +100,8 @@ function FolderItem(props: any) {
}}
onDoubleClick={onFolderToggleClick_}
>
{folderTitle} {noteCountComp}
<span className="title">{folderTitle}</span>
{shareIcon} {noteCountComp}
</StyledListItemAnchor>
</StyledListItem>
);
@@ -158,22 +167,27 @@ class SidebarComponent extends React.Component<Props, State> {
// to put the dropped folder at the root. But for notes, folderId needs to always be defined
// since there's no such thing as a root note.
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
try {
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
if (!folderId) return;
if (!folderId) return;
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
for (let i = 0; i < noteIds.length; i++) {
await Note.moveToFolder(noteIds[i], folderId);
}
} else if (dt.types.indexOf('text/x-jop-folder-ids') >= 0) {
event.preventDefault();
const folderIds = JSON.parse(dt.getData('text/x-jop-folder-ids'));
for (let i = 0; i < folderIds.length; i++) {
await Folder.moveToFolder(folderIds[i], folderId);
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
for (let i = 0; i < noteIds.length; i++) {
await Note.moveToFolder(noteIds[i], folderId);
}
} else if (dt.types.indexOf('text/x-jop-folder-ids') >= 0) {
event.preventDefault();
const folderIds = JSON.parse(dt.getData('text/x-jop-folder-ids'));
for (let i = 0; i < folderIds.length; i++) {
await Folder.moveToFolder(folderIds[i], folderId);
}
}
} catch (error) {
logger.error(error);
alert(error.message);
}
}
@@ -222,12 +236,14 @@ class SidebarComponent extends React.Component<Props, State> {
const itemType = Number(event.currentTarget.getAttribute('data-type'));
if (!itemId || !itemType) throw new Error('No data on element');
const state: AppState = store().getState();
let deleteMessage = '';
let buttonLabel = _('Remove');
let deleteButtonLabel = _('Remove');
if (itemType === BaseModel.TYPE_FOLDER) {
const folder = await Folder.load(itemId);
deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
buttonLabel = _('Delete');
deleteButtonLabel = _('Delete');
} else if (itemType === BaseModel.TYPE_TAG) {
const tag = await Tag.load(itemId);
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
@@ -250,10 +266,10 @@ class SidebarComponent extends React.Component<Props, State> {
menu.append(
new MenuItem({
label: buttonLabel,
label: deleteButtonLabel,
click: async () => {
const ok = bridge().showConfirmMessageBox(deleteMessage, {
buttons: [buttonLabel, _('Cancel')],
buttons: [deleteButtonLabel, _('Cancel')],
defaultId: 1,
});
if (!ok) return;
@@ -294,6 +310,14 @@ class SidebarComponent extends React.Component<Props, State> {
);
}
// We don't display the "Share notebook" menu item for sub-notebooks
// that are within a shared notebook. If user wants to do this,
// they'd have to move the notebook out of the shared notebook
// first.
if (CommandService.instance().isEnabled('showShareFolderDialog', stateToWhenClauseContext(state, { commandFolderId: itemId }))) {
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('showShareFolderDialog', itemId)));
}
menu.append(
new MenuItem({
label: _('Export'),
@@ -359,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) {
@@ -371,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
@@ -387,10 +411,10 @@ class SidebarComponent extends React.Component<Props, State> {
);
}
renderFolderItem(folder: any, selected: boolean, hasChildren: boolean, depth: number) {
renderFolderItem(folder: FolderEntity, selected: boolean, hasChildren: boolean, depth: number) {
const anchorRef = this.anchorItemRef('folder', folder.id);
const isExpanded = this.props.collapsedFolderIds.indexOf(folder.id) < 0;
let noteCount = folder.note_count;
let noteCount = (folder as any).note_count;
// Thunderbird count: Subtract children note_count from parent folder if it expanded.
if (isExpanded) {
@@ -418,6 +442,8 @@ class SidebarComponent extends React.Component<Props, State> {
itemContextMenu={this.itemContextMenu}
folderItem_click={this.folderItem_click}
onFolderToggleClick_={this.onFolderToggleClick_}
shareId={folder.share_id}
parentId={folder.parent_id}
/>;
}
@@ -426,7 +452,12 @@ class SidebarComponent extends React.Component<Props, State> {
const noteCount = Setting.value('showNoteCounts') ? this.renderNoteCount(tag.note_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}
@@ -440,7 +471,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>
);
@@ -632,7 +664,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>
);

View File

@@ -63,16 +63,16 @@ export const StyledListItem = styled.div`
function listItemTextColor(props: any) {
if (props.isConflictFolder) return props.theme.colorError2;
if (props.isSpecialItem) return props.theme.colorFaded2;
if (props.shareId) return props.theme.colorWarn2;
return props.theme.color2;
}
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;
opacity: ${(props: any) => props.selected ? 1 : 0.8};
opacity: ${(props: any) => props.selected || props.shareId ? 1 : 0.8};
white-space: nowrap;
display: flex;
flex: 1;
@@ -81,6 +81,10 @@ export const StyledListItemAnchor = styled.a`
height: 100%;
`;
export const StyledShareIcon = styled.i`
margin-left: 8px;
`;
export const StyledExpandLink = styled.a`
color: ${(props: any) => props.theme.color2};
cursor: default;

View File

@@ -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,18 +110,40 @@ 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) {

View File

@@ -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>

View File

@@ -45,5 +45,7 @@ export default function() {
'editor.swapLineUp',
'editor.swapLineDown',
'toggleSafeMode',
'showShareNoteDialog',
'showShareFolderDialog',
];
}

View File

@@ -0,0 +1,11 @@
import styled from 'styled-components';
const StyledFormLabel = styled.div`
font-size: ${props => props.theme.fontSize * 1.083333}px;
color: ${props => props.theme.color};
font-family: ${props => props.theme.fontFamily};
font-weight: 500;
margin-bottom: ${props => props.theme.mainPadding / 2}px;
`;
export default StyledFormLabel;

View 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;

View File

@@ -0,0 +1,13 @@
import styled from 'styled-components';
const StyledMessage = styled.div`
border-radius: 3px;
background-color: ${props => props.type === 'error' ? props.theme.warningBackgroundColor : 'transparent'};
font-size: ${props => props.theme.fontSize}px;
color: ${props => props.theme.color};
font-family: ${props => props.theme.fontFamily};
padding: ${props => props.type === 'error' ? props.theme.mainPadding : '0'}px;
word-break: break-all;
`;
export default StyledMessage;

View File

@@ -22,6 +22,7 @@ interface ContextMenuProps {
watchedNoteFiles: string[];
plugins: PluginStates;
inConflictFolder: boolean;
customCss: string;
}
export default class NoteListUtils {
@@ -158,6 +159,7 @@ export default class NoteListUtils {
sourceNoteIds: noteIds,
includeConflicts: props.inConflictFolder,
plugins: props.plugins,
customCss: props.customCss,
});
},
})

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.8.5",
"version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3199,7 +3199,7 @@
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"resolved": false,
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true,
"optional": true
@@ -7659,7 +7659,7 @@
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"resolved": false,
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true,
"optional": true

View File

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

View File

@@ -246,7 +246,7 @@ class Dialog extends React.PureComponent<Props, State> {
markupToHtml() {
if (this.markupToHtml_) return this.markupToHtml_;
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml({});
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
return this.markupToHtml_;
}
@@ -432,7 +432,8 @@ class Dialog extends React.PureComponent<Props, State> {
renderItem(item: SearchResult) {
const theme = themeStyle(this.props.themeId);
const style = this.style();
const rowStyle = item.id === this.state.selectedItemId ? style.rowSelected : style.row;
const isSelected = item.id === this.state.selectedItemId;
const rowStyle = isSelected ? style.rowSelected : style.row;
const titleHtml = item.fragments
? `<span style="font-weight: bold; color: ${theme.colorBright};">${item.title}</span>`
: surroundKeywords(this.state.keywords, item.title, `<span style="font-weight: bold; color: ${theme.colorBright};">`, '</span>', { escapeHtml: true });
@@ -444,7 +445,7 @@ class Dialog extends React.PureComponent<Props, State> {
const fragmentComp = !fragmentsHtml ? null : <div style={style.rowFragments} dangerouslySetInnerHTML={{ __html: (fragmentsHtml) }}></div>;
return (
<div key={item.id} style={rowStyle} onClick={this.listItem_onClick} data-id={item.id} data-parent-id={item.parent_id} data-type={item.type}>
<div key={item.id} className={isSelected ? 'selected' : null} style={rowStyle} onClick={this.listItem_onClick} data-id={item.id} data-parent-id={item.parent_id} data-type={item.type}>
<div style={style.rowTitle} dangerouslySetInnerHTML={{ __html: titleHtml }}></div>
{fragmentComp}
{pathComp}
@@ -521,11 +522,11 @@ class Dialog extends React.PureComponent<Props, State> {
render() {
const theme = themeStyle(this.props.themeId);
const style = this.style();
const helpComp = !this.state.showHelp ? null : <div style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;
const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;
return (
<div onClick={this.modalLayer_onClick} style={theme.dialogModalLayer}>
<div style={style.dialogBox}>
<div className="modal-layer" onClick={this.modalLayer_onClick} style={theme.dialogModalLayer}>
<div className="modal-dialog" style={style.dialogBox}>
{helpComp}
<div style={style.inputHelpWrapper}>
<input autoFocus type="text" style={style.input} ref={this.inputRef} value={this.state.query} onChange={this.input_onChange} onKeyDown={this.input_onKeyDown} />

View File

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

View File

@@ -4,12 +4,12 @@
// one.
import { AppState } from '../../app';
import libStateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
import libStateToWhenClauseContext, { WhenClauseContextOptions } from '@joplin/lib/services/commands/stateToWhenClauseContext';
import layoutItemProp from '../../gui/ResizableLayout/utils/layoutItemProp';
export default function stateToWhenClauseContext(state: AppState) {
export default function stateToWhenClauseContext(state: AppState, options: WhenClauseContextOptions = null) {
return {
...libStateToWhenClauseContext(state),
...libStateToWhenClauseContext(state, options),
// UI elements
markdownEditorVisible: !!state.settings['editor.codeView'],

View File

@@ -1,11 +1,14 @@
import { MarkupLanguageUtils as BaseMarkupLanguageUtils } from '@joplin/lib/markupLanguageUtils';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { contentScriptsToRendererRules } from '@joplin/lib/services/plugins/utils/loadContentScripts';
import { Options } from '@joplin/renderer/MarkupToHtml';
class MarkupLanguageUtils extends BaseMarkupLanguageUtils {
public newMarkupToHtml(plugins: PluginStates, options: any = null) {
return super.newMarkupToHtml({
public newMarkupToHtml(plugins: PluginStates = null, options: Options = null) {
plugins = plugins || {};
return super.newMarkupToHtml(null, {
extraRendererRules: contentScriptsToRendererRules(plugins),
...options,
});

View File

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

View File

@@ -8,12 +8,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- RN-NOTIFICATION -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- /RN-NOTIFICATION -->
<!-- Make these features optional to enable Chromebooks -->
<!-- https://github.com/laurent22/joplin/issues/37 -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
@@ -41,35 +35,6 @@
android:resizeableActivity="true"
android:theme="@style/AppTheme">
<!-- RN-NOTIFICATION -->
<receiver
android:name="com.emekalites.react.alarm.notification.AlarmReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="ACTION_DISMISS" />
<action android:name="ACTION_SNOOZE" />
</intent-filter>
</receiver>
<receiver
android:name="com.emekalites.react.alarm.notification.AlarmDismissReceiver"
android:enabled="true"
android:exported="true" />
<receiver
android:name="com.emekalites.react.alarm.notification.AlarmBootReceiver"
android:directBootAware="true"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- /RN-NOTIFICATION -->
<!--
2018-12-16: Changed android:launchMode from "singleInstance" to "singleTop" for Firebase notification
Previously singleInstance was necessary to prevent multiple instance of the RN app from running at the same time, but maybe no longer needed.

View File

@@ -3,8 +3,13 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared');
export default function useOnMessage(onCheckboxChange: Function, noteBody: string, onMarkForDownload: Function, onJoplinLinkClick: Function, onResourceLongPress: Function) {
return useCallback((event: any) => {
// Since RN 58 (or 59) messages are now escaped twice???
const msg = unescape(unescape(event.nativeEvent.data));
// 2021-05-19: Historically this was unescaped twice as it was
// apparently needed after an upgrade to RN 58 (or 59). However this is
// no longer needed and in fact would break certain URLs so it can be
// removed. Keeping the comment here anyway in case we find some URLs
// that end up being broken after removing the double unescaping.
// https://github.com/laurent22/joplin/issues/4494
const msg = event.nativeEvent.data;
console.info('Got IPC message: ', msg);

View File

@@ -372,7 +372,7 @@ class SideMenuContentComponent extends Component {
items.push(this.renderSidebarButton('folder_header', _('Notebooks'), 'md-folder'));
if (this.props.folders.length) {
const result = shared.renderFolders(this.props, this.renderFolderItem);
const result = shared.renderFolders(this.props, this.renderFolderItem, false);
const folderItems = result.items;
items = items.concat(folderItems);
}

View File

@@ -266,6 +266,7 @@
AE82E4A72599FA3A0013551B = {
CreatedOnToolsVersion = 12.0.1;
DevelopmentTeam = A9BXAFS6CT;
ProvisioningStyle = Automatic;
};
};
};
@@ -485,13 +486,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 63;
CURRENT_PROJECT_VERSION = 65;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.8.0;
MARKETING_VERSION = 20.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -513,12 +514,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 63;
CURRENT_PROJECT_VERSION = 65;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.8.0;
MARKETING_VERSION = 20.0.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -658,12 +659,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 65;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 20.0.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -687,12 +690,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 65;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 20.0.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -240,7 +240,7 @@ PODS:
- React-cxxreact (= 0.63.3)
- React-jsi (= 0.63.3)
- React-jsinspector (0.63.3)
- react-native-alarm-notification (1.7.1):
- react-native-alarm-notification (1.0.3):
- React
- react-native-camera (3.40.0):
- React-Core
@@ -391,7 +391,7 @@ DEPENDENCIES:
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-alarm-notification (from `../node_modules/react-native-alarm-notification`)
- react-native-alarm-notification (from `../node_modules/joplin-rn-alarm-notification`)
- react-native-camera (from `../node_modules/react-native-camera`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
@@ -475,7 +475,7 @@ EXTERNAL SOURCES:
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-alarm-notification:
:path: "../node_modules/react-native-alarm-notification"
:path: "../node_modules/joplin-rn-alarm-notification"
react-native-camera:
:path: "../node_modules/react-native-camera"
react-native-document-picker:
@@ -568,7 +568,7 @@ SPEC CHECKSUMS:
React-jsi: df07aa95b39c5be3e41199921509bfa929ed2b9d
React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
react-native-alarm-notification: 8f2adb4521bf7ca867278d4c236553e154dbbbc6
react-native-alarm-notification: 466e4ad56fbd948ecac26e657f292dca8bf483d5
react-native-camera: 5c1fbfecf63b802b8ca4a71c60d30a71550fb348
react-native-document-picker: b3e78a8f7fef98b5cb069f20fc35797d55e68e28
react-native-geolocation: cbd9d6bd06bac411eed2671810f454d4908484a8

View File

@@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
@@ -33,7 +33,7 @@
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsText</key>
<integer>1</integer>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>

View File

@@ -23,6 +23,7 @@
"@react-native-community/slider": "^3.0.3",
"buffer": "^5.0.8",
"events": "^3.2.0",
"joplin-rn-alarm-notification": "^1.0.3",
"jsc-android": "241213.1.0",
"md5": "^2.2.1",
"prop-types": "^15.6.0",
@@ -30,7 +31,6 @@
"react": "16.13.1",
"react-native": "0.63.3",
"react-native-action-button": "^2.8.5",
"react-native-alarm-notification": "^1.7.1",
"react-native-camera": "^3.40.0",
"react-native-dialogbox": "^0.6.10",
"react-native-document-picker": "^4.0.0",

View File

@@ -2,7 +2,7 @@ const React = require('react');
import shim from '@joplin/lib/shim';
shim.setReact(React);
import setUpQuickActions from './setUpQuickActions';
import setupQuickActions from './setupQuickActions';
import PluginAssetsLoader from './PluginAssetsLoader';
import AlarmService from '@joplin/lib/services/AlarmService';
import Alarm from '@joplin/lib/models/Alarm';
@@ -36,7 +36,7 @@ const SafeAreaView = require('./components/SafeAreaView');
const { connect, Provider } = require('react-redux');
const { BackButtonService } = require('./services/back-button.js');
import NavService from '@joplin/lib/services/NavService';
const { createStore, applyMiddleware } = require('redux');
import { createStore, applyMiddleware } from 'redux';
const reduxSharedMiddleware = require('@joplin/lib/components/shared/reduxSharedMiddleware');
const { shimInit } = require('./utils/shim-init-react.js');
const { AppNav } = require('./components/app-nav.js');
@@ -97,6 +97,8 @@ import EncryptionService from '@joplin/lib/services/EncryptionService';
import MigrationService from '@joplin/lib/services/MigrationService';
import { clearSharedFilesCache } from './utils/ShareUtils';
import setIgnoreTlsErrors from './utils/TlsUtils';
import ShareService from '@joplin/lib/services/share/ShareService';
import setupNotifications from './utils/setupNotifications';
let storeDispatch = function(_action: any) {};
@@ -525,6 +527,7 @@ async function initialize(dispatch: Function) {
EncryptionService.instance().setLogger(mainLogger);
// eslint-disable-next-line require-atomic-updates
BaseItem.encryptionService_ = EncryptionService.instance();
BaseItem.shareService_ = ShareService.instance();
DecryptionWorker.instance().dispatch = dispatch;
DecryptionWorker.instance().setLogger(mainLogger);
DecryptionWorker.instance().setKvStore(KvStore.instance());
@@ -536,6 +539,8 @@ async function initialize(dispatch: Function) {
// / E2EE SETUP
// ----------------------------------------------------------------
await ShareService.instance().initialize(store);
reg.logger().info('Loading folders...');
await FoldersScreenUtils.refreshFolders();
@@ -711,7 +716,9 @@ class AppComponent extends React.Component {
await this.handleShareData();
setUpQuickActions(this.props.dispatch, this.props.selectedFolderId);
setupQuickActions(this.props.dispatch, this.props.selectedFolderId);
await setupNotifications(this.props.dispatch);
}
componentWillUnmount() {

View File

@@ -1,7 +1,7 @@
import Logger from '@joplin/lib/Logger';
import { Notification } from '@joplin/lib/models/Alarm';
const ReactNativeAN = require('react-native-alarm-notification').default;
const ReactNativeAN = require('joplin-rn-alarm-notification').default;
export default class AlarmServiceDriver {
@@ -30,9 +30,11 @@ export default class AlarmServiceDriver {
// Returns -1 if could not be found
private alarmJoplinAlarmId(alarm: any): number {
if (!alarm.data) return -1;
const m = alarm.data.match(/joplinNotificationId==>(\d+)/);
return m ? Number(m[1]) : -1;
if (!alarm.data || !alarm.data.joplinNotificationId) {
return -1;
} else {
return alarm.data.joplinNotificationId;
}
}
private async alarmByJoplinNotificationId(joplinNotificationId: number) {
@@ -51,9 +53,12 @@ export default class AlarmServiceDriver {
title: notification.title,
message: notification.body ? notification.body : '-', // Required
channel: 'net.cozic.joplin.notification',
small_icon: 'ic_launcher',
color: 'white',
data: { joplinNotificationId: `${notification.id}` },
small_icon: 'ic_launcher_foreground', // Android requires the icon to be transparent
color: 'blue',
data: {
joplinNotificationId: `${notification.id}`,
noteId: notification.noteId,
},
};
// ReactNativeAN expects a string as a date and it seems this utility

View File

@@ -1,4 +1,4 @@
import { Platform, NativeModules } from 'react-native';
const { Platform, NativeModules } = require('react-native');
export default async function setIgnoreTlsErrors(ignore: boolean): Promise<boolean> {
if (Platform.OS === 'android') {

View File

@@ -0,0 +1,37 @@
import { Dispatch } from 'redux';
const { NativeEventEmitter, NativeModules, Platform } = require('react-native');
interface NotificationData {
joplinNotificationId: string;
noteId: string;
}
export default async (dispatch: Dispatch) => {
if (Platform.OS === 'android') {
const RNAlarmNotification = NativeModules.RNAlarmNotification;
const RNAlarmEmitter = new NativeEventEmitter(RNAlarmNotification);
const handleNotification = async (notification: NotificationData) => {
if (notification) {
const noteId = notification.noteId;
if (noteId) {
dispatch({ type: 'NAV_BACK' });
dispatch({ type: 'SIDE_MENU_CLOSE' });
dispatch({
type: 'NAV_GO',
noteId: noteId,
routeName: 'Note',
});
}
}
};
// receive notification click events when the app is running
RNAlarmEmitter.addListener('OnNotificationOpened', handleNotification);
// retrieve notification info if the app was started after the user clicked notification
const notification = await RNAlarmNotification.getAlarmInfo();
await handleNotification(notification);
}
};

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "generator-joplin",
"version": "1.8.1",
"version": "2.0.0",
"description": "Scaffolds out a new Joplin plugin",
"homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin",
"author": {
@@ -34,4 +34,4 @@
"repository": "https://github.com/laurent22/generator-joplin",
"license": "MIT",
"private": true
}
}

View File

@@ -1,9 +1,4 @@
/* eslint-disable no-unused-vars */
const time = require('@joplin/lib/time').default;
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('./test-utils.js');
const ArrayUtils = require('@joplin/lib/ArrayUtils');
const ArrayUtils = require('./ArrayUtils');
describe('ArrayUtils', function() {

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