1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-24 20:19:10 +02:00

Compare commits

...

100 Commits

Author SHA1 Message Date
Laurent Cozic
9bf1e19d61 Server v3.0.1 2024-07-25 18:18:29 +01:00
github-actions[bot]
f7a970f466 @Aarya01Patil has signed the CLA in laurent22/joplin#10783 2024-07-25 11:57:59 +00:00
Henry Heino
f7fa7a195f Server: Allow web client sync (#10775) 2024-07-24 23:45:11 +01:00
Laurent Cozic
e6ec27a501 Server: Prevent item size calculation task from failing when a user has been deleted 2024-07-24 19:16:40 +01:00
Henry Heino
331f7ebe5c Mobile: Plugins: Fix incorrect Node exports emulation (#10776) 2024-07-23 20:10:59 +01:00
joe
afcd2d2a39 update zh_TW.po file (#10773) 2024-07-23 12:56:08 +01:00
github-actions[bot]
8129f4a89f @fishpcblog has signed the CLA in laurent22/joplin#10773 2024-07-23 11:08:23 +00:00
github-actions[bot]
72c1bb3724 @BHAV0207 has signed the CLA in laurent22/joplin#10769 2024-07-20 10:59:43 +00:00
Joplin Bot
b69a7403bc Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-19 18:19:04 +00:00
Laurent Cozic
bdc9fa9dc3 Doc: Added sponsor 2024-07-19 15:54:41 +01:00
Laurent Cozic
9c07e57e28 lock file 2024-07-18 15:06:47 +01:00
Henry Heino
821daeca94 Chore: Mobile: Add NoteBodyViewer tests (#10747) 2024-07-18 09:44:13 +01:00
ERYpTION
480bf238f6 Update da_DK.po (#10758) 2024-07-18 09:44:00 +01:00
Henry Heino
8ff13e5fc4 Android: Fixes #10681: Fix Dropbox sync for certain device languages (#10759) 2024-07-18 09:43:49 +01:00
Henry Heino
8e1970d08e Desktop: Update bundled Backup plugin to v1.4.2 (#10760)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-07-18 09:43:20 +01:00
Henry Heino
86d92dd302 Chore: Fix indentation in generate-plugin-doc/package.json (#10762)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-07-18 09:43:07 +01:00
Henry Heino
71b466507f Mobile: Upgrade react-native-webview to 13.8.6 to fix CI (#10761) 2024-07-17 22:49:10 +01:00
renovate[bot]
11ce5f6c52 Update dependency nodemailer to v6.9.13 (#10699) 2024-07-17 20:38:53 +00:00
renovate[bot]
630b4061f0 Update dependency pg to v8.11.5 (#10700) 2024-07-17 20:38:51 +00:00
renovate[bot]
912c943114 Update dependency react-native-webview to v13.8.4 (#10702) 2024-07-17 20:38:49 +00:00
renovate[bot]
8e377e0306 Update dependency sharp to v0.33.3 (#10704) 2024-07-17 20:38:13 +00:00
renovate[bot]
1535e020a3 Update dependency style-to-js to v1.1.12 (#10705) 2024-07-17 20:38:11 +00:00
renovate[bot]
23d5d3426d Update dependency tar to v6.2.1 (#10706) 2024-07-17 20:38:04 +00:00
Laurent Cozic
6ab7a0836e Tools: Automerge pull requests with the "automerge" label 2024-07-17 20:51:54 +01:00
Laurent Cozic
278691211d Tools: Automerge pull requests with the "automerge" label 2024-07-17 20:06:26 +01:00
Laurent Cozic
356d4688a0 Tools: Automerge pull requests with the "automerge" label 2024-07-17 19:52:52 +01:00
renovate[bot]
6b1d31387b Update dependency @react-native-community/geolocation to v3.2.1 (#10757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 18:56:46 +00:00
Henry Heino
71f70f4d2c Mobile: Resolves #9017: Support pasting images (#10751) 2024-07-16 19:28:05 +01:00
Liffindra Angga Zaaldian
2d984ce9a8 Update Indonesian translation (#10741) 2024-07-16 19:25:54 +01:00
Henry Heino
eaf160e0b1 Docs: Update user-facing plugin documentation to reflect that plugins are now supported on mobile (#10738) 2024-07-16 19:25:38 +01:00
Henry Heino
624bfd9175 Desktop: Fixes #10733: Fix not-yet-created images lost while editing with the Rich Text Editor (#10734) 2024-07-16 19:25:23 +01:00
Henry Heino
9ad1249f11 Chore: Mobile: Migrate shim-init-react to TypeScript (#10731) 2024-07-16 19:23:03 +01:00
sysescool
668849603d Desktop: Fixes #10716: fix joplin install fails because ldconfig not found libfuse2 but it is indeed installed. (#10717) 2024-07-16 19:20:51 +01:00
renovate[bot]
24f4c8e6ab Update dependency terminal-kit to v3.1.1 (#10713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:03:33 +01:00
renovate[bot]
46f5784edc Update dependency react-native-get-random-values to v1.11.0 (#10712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:49 +01:00
renovate[bot]
fae2443481 Update dependency react-native-device-info to v10.13.1 (#10711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:40 +01:00
renovate[bot]
37d65e000a Update dependency jsdom to v23.2.0 (#10709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:17 +01:00
renovate[bot]
6dd90eb03f Update dependency @react-native-community/geolocation to v3.2.0 (#10708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:07 +01:00
CUI Hao
3d8f713eb7 Chore: Add a sleep in the note duplication test (#10719)
Fixes a test failure on very fast computers.
2024-07-15 07:33:59 -07:00
renovate[bot]
c35efe15d2 Update dependency @react-native-community/slider to v4.5.2 (#10744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-15 02:46:52 +00:00
Henry Heino
1596b46b86 Chore: Fixes #10721: Fix test failure in CI (#10735) 2024-07-11 12:59:56 -07:00
Henry Heino
4de0236194 Chore: Docs: Fix missing closing tag (#10730)
The video element is not a void element. As such, if it's missing a closing tag, it's considered to be unclosed.
See https://developer.mozilla.org/en-US/docs/Glossary/Void_element#self-closing_tags.
2024-07-11 10:16:33 -07:00
Jeremy Kao
2ab9702e32 Docs: Update the UI path for sync status on the mobile app (#10718)
The instructions for how to view synchronisation status on mobile were out of date.
2024-07-11 07:44:09 -07:00
github-actions[bot]
24954bd0f0 @shubhiscoding has signed the CLA in laurent22/joplin#10728 2024-07-09 11:06:20 +00:00
renovate[bot]
2d4322be56 Update dependency @types/node to v18.19.31 (#10727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 10:37:55 +00:00
renovate[bot]
abb069bf50 Update dependency @react-native-community/slider to v4.5.1 (#10726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 10:35:50 +00:00
renovate[bot]
a81d9fe17a Update dependency koa to v2.15.2 (#10698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 08:11:32 +00:00
renovate[bot]
6d44158050 Update dependency glob to v10.3.12 (#10697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 08:10:19 +00:00
Dmitriy Q
a63cf3a90d All: Translation: Update ru_RU.po (#10720) 2024-07-09 04:08:49 -04:00
github-actions[bot]
ddb4f8c45b @cuihaoleo has signed the CLA in laurent22/joplin#10719 2024-07-08 04:28:43 +00:00
github-actions[bot]
d7adab59ef @imsardine has signed the CLA in laurent22/joplin#10718 2024-07-08 03:49:24 +00:00
github-actions[bot]
e41374496e @sysescool has signed the CLA in laurent22/joplin#10717 2024-07-08 03:19:52 +00:00
Arda Kılıçdağı
62d514463c Turkish Translations updated (#10692) 2024-07-06 18:26:25 +02:00
jduar
332078b4ea Update Portuguese pt_PT.po translation. (#10691) 2024-07-06 18:26:13 +02:00
github-actions[bot]
c60e11646d @jduar has signed the CLA in laurent22/joplin#10691 2024-07-06 14:06:06 +00:00
Joplin Bot
c607fe9c75 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-06 12:20:41 +00:00
Laurent Cozic
1a4ba2c74a Merge branch 'release-3.0' into dev 2024-07-06 13:41:53 +02:00
renovate[bot]
731260926d Update dependency @types/node to v18.19.30 (#10684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-05 13:49:54 +02:00
ben-igel
a43635610a Update de_DE.po to make the german translation of the sort lines command better understandable (#10682) 2024-07-05 13:18:58 +02:00
github-actions[bot]
e307459652 @ben-igel has signed the CLA in laurent22/joplin#10682 2024-07-04 17:51:49 +00:00
Henry Heino
c197a83de8 Desktop: Fix error in plugin content scripts generated with Webpack (#10680) 2024-07-04 14:58:26 +02:00
Joplin Bot
7e4533d811 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-03 12:22:19 +00:00
Laurent Cozic
be117bca86 Tools: Run packageJsonLint hook only when JSON files are being committed 2024-07-03 10:54:13 +02:00
Laurent Cozic
2b7bd902f3 Merge branch 'release-3.0' into dev 2024-07-03 10:47:27 +02:00
Laurent Cozic
6d7fd19167 Chore: Add word to dic 2024-07-03 00:52:08 +02:00
Laurent Cozic
c3520d9eb1 CLI v3.0.1 2024-07-02 21:00:43 +02:00
Laurent Cozic
5fd3cecc96 Lock file 2024-07-02 21:00:43 +02:00
Laurent Cozic
0d8666c946 Releasing sub-packages 2024-07-02 21:00:42 +02:00
Joplin Bot
12db667128 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-02 18:18:51 +00:00
Laurent Cozic
6215de6080 Doc: Added new sponsor 2024-07-02 19:16:15 +02:00
renovate[bot]
7d2f384475 Update dependency @types/node to v18.19.29 (#10671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 19:13:33 +02:00
Laurent Cozic
6ea1ac09a4 Doc: Fixed news filename 2024-07-02 15:17:20 +02:00
Laurent Cozic
f2841a9a94 Tools: Add script to validate Markdown filenames on commit 2024-07-02 15:16:16 +02:00
Joplin Bot
46ade2e0f8 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-01 18:19:29 +00:00
Henry Heino
d89be23069 Chore: Migrate SQL queries in preparation for web support (#10670) 2024-07-01 19:56:40 +02:00
Laurent Cozic
337d50437b Doc: Add release notes 3.0 2024-07-01 18:16:08 +02:00
Laurent Cozic
2479a8471e Merge branch 'release-3.0' into dev 2024-07-01 18:02:27 +02:00
rnbastos
6c091910cd Update pt_BR.po (#10667) 2024-07-01 17:32:43 +02:00
pedr
a074532497 Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore (#10649) 2024-07-01 17:21:17 +02:00
Joplin Bot
5d2df358ac Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-01 00:47:08 +00:00
Laurent Cozic
dfdc2fda27 Fixed tests 2024-06-29 18:38:18 +02:00
Laurent Cozic
a1f9c9c3d8 All: Set min version for synchronising to 3.0.0 2024-06-29 18:38:18 +02:00
Joplin Bot
838da6f161 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-29 12:22:44 +00:00
Laurent Cozic
a86ee1d34e Merge branch 'release-3.0' into dev 2024-06-29 12:25:11 +02:00
Laurent Cozic
5f34a1bc92 Doc: Add Multi-factor authentication guide 2024-06-28 19:43:59 +02:00
renovate[bot]
f781face3a Update dependency @types/node to v18.19.28 (#10663)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-28 13:03:05 +02:00
Laurent Cozic
78ecd28d73 Merge branch 'release-3.0' into dev 2024-06-27 11:54:19 +02:00
github-actions[bot]
85e57a3953 @cedecode has signed the CLA in laurent22/joplin#10658 2024-06-25 22:11:34 +00:00
qx100
95968f6690 All: Update zh_CN.po (#10651) 2024-06-25 15:05:06 +02:00
Henry Heino
f0b73ee916 Docs: Document creating and managing to-do notes (#10563) 2024-06-25 15:01:54 +02:00
Henry Heino
a44412ae78 Chore: Increase strength of Settings types (#10605) 2024-06-25 15:01:39 +02:00
Henry Heino
c7116b135f Chore: Refactor mobile plugin logic into locations more consistent with other parts of the app (#10636) 2024-06-25 14:59:59 +02:00
Siddhant Paritosh Rao
801d36c41f Mobile: Fixes #10596: remove search bar from plugins screen (#10648) 2024-06-25 14:59:41 +02:00
Henry Heino
1d46adf801 Mobile: Fix dayjs-related startup error (#10652) 2024-06-24 17:15:57 +02:00
Laurent Cozic
94edaea210 Doc: Removed outdated hot reload information 2024-06-23 09:36:56 +01:00
Henry Heino
5db88995c0 Chore: Fix CI (#10646) 2024-06-21 09:37:25 +01:00
Laurent Cozic
8eda8d3c84 Fixed test 2024-06-20 18:25:51 +01:00
Laurent Cozic
1437dd5f27 Desktop: Use relative time in note list for today and yesterday 2024-06-20 14:01:13 +01:00
Laurent Cozic
9eb4944614 Server: Remove USERS_WITH_REPLICATION env variable 2024-06-19 23:34:00 +01:00
Joplin Bot
b4ef5abb88 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-19 18:18:05 +00:00
177 changed files with 5113 additions and 3596 deletions

View File

@@ -520,12 +520,15 @@ packages/app-mobile/components/CameraView.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/types.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
@@ -596,6 +599,27 @@ packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/components/plugins/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/components/plugins/backgroundPage/startStopPlugin.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.test.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.js
packages/app-mobile/components/plugins/backgroundPage/utils/makeSandboxedIframe.js
packages/app-mobile/components/plugins/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/components/plugins/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/components/plugins/dialogs/PluginDialogManager.js
packages/app-mobile/components/plugins/dialogs/PluginDialogWebView.js
packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.js
packages/app-mobile/components/plugins/dialogs/PluginUserWebView.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogSize.js
packages/app-mobile/components/plugins/dialogs/hooks/useViewInfos.js
packages/app-mobile/components/plugins/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/components/plugins/types.js
packages/app-mobile/components/plugins/utils/createOnLogHandler.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js
@@ -660,36 +684,11 @@ packages/app-mobile/components/screens/status.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
packages/app-mobile/gulpfile.js
packages/app-mobile/plugins/PlatformImplementation.js
packages/app-mobile/plugins/PluginRunner/PluginRunner.js
packages/app-mobile/plugins/PluginRunner/PluginRunnerWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializeDialogWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/startStopPlugin.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.test.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/makeSandboxedIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogManager.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginPanelViewer.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginUserWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogSize.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useViewInfos.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/plugins/PluginRunner/types.js
packages/app-mobile/plugins/PluginRunner/utils/createOnLogHandler.js
packages/app-mobile/plugins/hooks/usePlugin.js
packages/app-mobile/plugins/loadPlugins.test.js
packages/app-mobile/plugins/loadPlugins.js
packages/app-mobile/plugins/testing/MockPluginRunner.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/plugins/PlatformImplementation.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
@@ -717,7 +716,10 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/pickDocument.js
@@ -725,6 +727,7 @@ packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareHandler.js
packages/app-mobile/utils/shim-init-react.js
packages/app-mobile/utils/showMessageBox.js
packages/app-mobile/utils/testing/createMockReduxStore.js
packages/app-mobile/utils/types.js
@@ -798,6 +801,7 @@ packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
packages/editor/CodeMirror/utils/formatting/types.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/SelectionFormatting.js
@@ -898,6 +902,7 @@ packages/lib/geolocation-node.js
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useEventListener.js
packages/lib/hooks/usePlugin.js
packages/lib/hooks/usePrevious.js
packages/lib/htmlUtils.test.js
packages/lib/htmlUtils.js
@@ -944,8 +949,10 @@ packages/lib/models/Tag.test.js
packages/lib/models/Tag.js
packages/lib/models/dateTimeFormats.test.js
packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/builtInMetadata.js
packages/lib/models/settings/settingValidations.test.js
packages/lib/models/settings/settingValidations.js
packages/lib/models/settings/types.js
packages/lib/models/utils/getCollator.js
packages/lib/models/utils/getConflictFolderId.js
packages/lib/models/utils/isItemId.js
@@ -1110,7 +1117,11 @@ packages/lib/services/plugins/api/noteListType.js
packages/lib/services/plugins/api/types.js
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js
packages/lib/services/plugins/loadPlugins.test.js
packages/lib/services/plugins/loadPlugins.js
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/testing/MockPlatformImplementation.js
packages/lib/services/plugins/testing/MockPluginRunner.js
packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
@@ -1386,6 +1397,7 @@ packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/translation.js
packages/tools/validateFilenames.js
packages/tools/website/build.js
packages/tools/website/buildTranslations.js
packages/tools/website/processDocs.test.js

17
.github/workflows/automerge.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: automerge
on:
schedule:
- cron: '*/10 * * * *'
jobs:
automerge:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- id: automerge
name: automerge
uses: "pascalgn/automerge-action@v0.16.3"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_METHOD: "squash"
LOG: "DEBUG"

66
.gitignore vendored
View File

@@ -499,12 +499,15 @@ packages/app-mobile/components/CameraView.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/types.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
@@ -575,6 +578,27 @@ packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/components/plugins/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/components/plugins/backgroundPage/startStopPlugin.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.test.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.js
packages/app-mobile/components/plugins/backgroundPage/utils/makeSandboxedIframe.js
packages/app-mobile/components/plugins/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/components/plugins/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/components/plugins/dialogs/PluginDialogManager.js
packages/app-mobile/components/plugins/dialogs/PluginDialogWebView.js
packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.js
packages/app-mobile/components/plugins/dialogs/PluginUserWebView.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogSize.js
packages/app-mobile/components/plugins/dialogs/hooks/useViewInfos.js
packages/app-mobile/components/plugins/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/components/plugins/types.js
packages/app-mobile/components/plugins/utils/createOnLogHandler.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js
@@ -639,36 +663,11 @@ packages/app-mobile/components/screens/status.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
packages/app-mobile/gulpfile.js
packages/app-mobile/plugins/PlatformImplementation.js
packages/app-mobile/plugins/PluginRunner/PluginRunner.js
packages/app-mobile/plugins/PluginRunner/PluginRunnerWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializeDialogWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/startStopPlugin.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.test.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/makeSandboxedIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogManager.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginPanelViewer.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginUserWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogSize.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useViewInfos.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/plugins/PluginRunner/types.js
packages/app-mobile/plugins/PluginRunner/utils/createOnLogHandler.js
packages/app-mobile/plugins/hooks/usePlugin.js
packages/app-mobile/plugins/loadPlugins.test.js
packages/app-mobile/plugins/loadPlugins.js
packages/app-mobile/plugins/testing/MockPluginRunner.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/plugins/PlatformImplementation.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
@@ -696,7 +695,10 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/pickDocument.js
@@ -704,6 +706,7 @@ packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareHandler.js
packages/app-mobile/utils/shim-init-react.js
packages/app-mobile/utils/showMessageBox.js
packages/app-mobile/utils/testing/createMockReduxStore.js
packages/app-mobile/utils/types.js
@@ -777,6 +780,7 @@ packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
packages/editor/CodeMirror/utils/formatting/types.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/SelectionFormatting.js
@@ -877,6 +881,7 @@ packages/lib/geolocation-node.js
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useEventListener.js
packages/lib/hooks/usePlugin.js
packages/lib/hooks/usePrevious.js
packages/lib/htmlUtils.test.js
packages/lib/htmlUtils.js
@@ -923,8 +928,10 @@ packages/lib/models/Tag.test.js
packages/lib/models/Tag.js
packages/lib/models/dateTimeFormats.test.js
packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/builtInMetadata.js
packages/lib/models/settings/settingValidations.test.js
packages/lib/models/settings/settingValidations.js
packages/lib/models/settings/types.js
packages/lib/models/utils/getCollator.js
packages/lib/models/utils/getConflictFolderId.js
packages/lib/models/utils/isItemId.js
@@ -1089,7 +1096,11 @@ packages/lib/services/plugins/api/noteListType.js
packages/lib/services/plugins/api/types.js
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js
packages/lib/services/plugins/loadPlugins.test.js
packages/lib/services/plugins/loadPlugins.js
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/testing/MockPlatformImplementation.js
packages/lib/services/plugins/testing/MockPluginRunner.js
packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
@@ -1365,6 +1376,7 @@ packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/translation.js
packages/tools/validateFilenames.js
packages/tools/website/build.js
packages/tools/website/buildTranslations.js
packages/tools/website/processDocs.test.js

View File

@@ -0,0 +1,118 @@
# Fixes sync issues caused by locale-sensitive lowercasing
# of HTTP headers.
# See https://github.com/laurent22/joplin/issues/10681
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
index 8ac9e7a855162cefbf99024eb013c8a3b11de1ec..1c639cf9d84821b6ffc132960e2d1c044bedbd48 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
@@ -2,6 +2,7 @@ package com.RNFetchBlob;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
+import java.util.Locale;
class RNFetchBlobConfig {
@@ -33,7 +34,7 @@ class RNFetchBlobConfig {
}
if(options.hasKey("binaryContentTypes"))
this.binaryContentTypes = options.getArray("binaryContentTypes");
- if(this.path != null && path.toLowerCase().contains("?append=true")) {
+ if(this.path != null && path.toLowerCase(Locale.ROOT).contains("?append=true")) {
this.overwrite = false;
}
if(options.hasKey("overwrite"))
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
index a4d70153f41e6c14eec65412b5b59822f1c6750b..d98c439f7b0aeb79afc82ab9f653e9c021086426 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+import java.util.Locale;
class RNFetchBlobFS {
@@ -210,7 +211,7 @@ class RNFetchBlobFS {
return;
}
- switch (encoding.toLowerCase()) {
+ switch (encoding.toLowerCase(Locale.ROOT)) {
case "base64" :
promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
break;
@@ -1050,7 +1051,7 @@ class RNFetchBlobFS {
if(encoding.equalsIgnoreCase("ascii")) {
return data.getBytes(Charset.forName("US-ASCII"));
}
- else if(encoding.toLowerCase().contains("base64")) {
+ else if(encoding.toLowerCase(Locale.ROOT).contains("base64")) {
return Base64.decode(data, Base64.NO_WRAP);
}
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
index a8abd71833879201e3438b2fa51d712a311c4551..b70cc13c004229f69157de5f82ae5ec3abf4358e 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
@@ -49,6 +49,7 @@ import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@@ -300,14 +301,14 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
responseFormat = ResponseFormat.UTF8;
}
else {
- builder.header(key.toLowerCase(), value);
- mheaders.put(key.toLowerCase(), value);
+ builder.header(key.toLowerCase(Locale.ROOT), value);
+ mheaders.put(key.toLowerCase(Locale.ROOT), value);
}
}
}
if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
- String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
+ String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase(Locale.ROOT);
if(rawRequestBodyArray != null) {
requestType = RequestType.Form;
@@ -323,7 +324,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
|| rawRequestBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
requestType = RequestType.SingleFile;
}
- else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) {
+ else if (cType.toLowerCase(Locale.ROOT).contains(";base64") || cType.toLowerCase(Locale.ROOT).startsWith("application/octet")) {
cType = cType.replace(";base64","").replace(";BASE64","");
if(mheaders.containsKey("content-type"))
mheaders.put("content-type", cType);
@@ -686,7 +687,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
boolean isCustomBinary = false;
if(options.binaryContentTypes != null) {
for(int i = 0; i< options.binaryContentTypes.size();i++) {
- if(ctype.toLowerCase().contains(options.binaryContentTypes.getString(i).toLowerCase())) {
+ if(ctype.toLowerCase(Locale.ROOT).contains(options.binaryContentTypes.getString(i).toLowerCase(Locale.ROOT))) {
isCustomBinary = true;
break;
}
@@ -698,13 +699,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
private String getHeaderIgnoreCases(Headers headers, String field) {
String val = headers.get(field);
if(val != null) return val;
- return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
+ return headers.get(field.toLowerCase(Locale.ROOT)) == null ? "" : headers.get(field.toLowerCase(Locale.ROOT));
}
private String getHeaderIgnoreCases(HashMap<String,String> headers, String field) {
String val = headers.get(field);
if(val != null) return val;
- String lowerCasedValue = headers.get(field.toLowerCase());
+ String lowerCasedValue = headers.get(field.toLowerCase(Locale.ROOT));
return lowerCasedValue == null ? "" : lowerCasedValue;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,4 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Fri, 01 Mar 2024 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Fri, 01 Mar 2024 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 2.14]]></title><description><![CDATA[<h2>OCR<a name="ocr" href="#ocr" class="heading-anchor">🔗</a></h2>
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 01 Jul 2024 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.0]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
<h3>Trash folder<a name="trash-folder" href="#trash-folder" class="heading-anchor">🔗</a></h3>
<p>Joplin now support a trash folder - any deleted notes or notebooks will be moved to that folder. You can also choose to have these notes permanently deleted after a number of days.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-trash.png" alt=""></p>
<p>Support for the trash folder has a somewhat long history in Joplin since it's an obvious and important feature to add, yet it can be particularly tricky once you start realising how many parts of the app it's going to impact.</p>
<p>Many attempts have been made over time: my first attempt was based on the note history feature. Indeed since this feature already saves versions of notes, it seems to make sense to use it for the trash feature, and indeed the note history feature <a href="https://joplinapp.org/news/20190523-221026">was designed for this originally</a>. However that approach turned to be needlessly complicated and after modifying hundreds of files just for this, the idea was dropped.</p>
<p>The next one was based on using a <a href="https://github.com/laurent22/joplin/issues/483">special &quot;trash&quot; tag</a> - deleted notes would have this tag attached to them and would appear in a special &quot;trash&quot; folder. This approach also had <a href="https://github.com/laurent22/joplin/issues/483">many issues</a> probably the main one being that notebooks can't be tagged, which means we would have to add support for tagged notebooks and that in itself would also be a massive change.</p>
<p><a href="https://discourse.joplinapp.org/t/trashcan/3998/16">Various</a>, <a href="https://discourse.joplinapp.org/t/poll-trash-bin-plugin/19951">ideas,</a> were also attempted using plugins, by creating a special &quot;trash folder&quot;, but in the end no such plugin was ever created, probably due to limitations of the plugin API.</p>
<p>In the end, turned out that this <a href="https://github.com/laurent22/joplin/issues/483#issuecomment-585655742">old idea</a> of adding a &quot;deleted&quot; property to each note and notebook was the easiest approach. With this it was simpler to get to a working solution relatively quickly, and then it was a matter of ensuring that deleted notes don't appear where they shouldn't, such as search results, etc.</p>
<h3>Joplin Cloud multi-factor authentication<a name="joplin-cloud-multi-factor-authentication" href="#joplin-cloud-multi-factor-authentication" class="heading-anchor">🔗</a></h3>
<p>Multi-factor authentication (MFA), also known as two-factor authentication (2FA) is a security process that requires you to provide two or more verification factors to gain access to a system or account. It typically includes something you know (password), something you have (security token), and something you are (biometric verification).</p>
<p>To better secure your account, Joplin Cloud and all Joplin applications now support MFA. To enable it, go to your Joplin Cloud profile, click on &quot;Enable multi-factor authentication&quot; and follow the instructions. Please note that all your applications will then be disconnected, so you will need to login again (your data of course will remain on the app so you won't have to download it again).</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-mfa.png" alt=""></p>
<h3>Note list with multiple columns<a name="note-list-with-multiple-columns" href="#note-list-with-multiple-columns" class="heading-anchor">🔗</a></h3>
<p>In this release we add support for multiple columns in the note list. You can display various properties of the notes, as well as sort the notes by these properties. As usual this feature can be controlled and customised by plugins so for example it should be possible to display custom columns, and display custom information including thumbnails.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-note-list-multi.png" alt=""></p>
<h3>Plugin API enhancement<a name="plugin-api-enhancement" href="#plugin-api-enhancement" class="heading-anchor">🔗</a></h3>
<p>The plugin API has received several updates to facilitate easy customisation of the app As mentioned above, it is now possible to customise the new note list. Besides this, we've added support for loading PDFs and creating images from them, which can for example be used to create thumbnails.</p>
<p>Many other small enhancements have been made to the plugin API to help you tailor the app to your needs!</p>
<h3>View OCR data<a name="view-ocr-data" href="#view-ocr-data" class="heading-anchor">🔗</a></h3>
<p>Now when you right-click on an image or PDF you have an option to view the OCR (Optical character recognition) data associated with it. That will allow you for example to easily copy and paste the text.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-ocr-data.png" alt=""></p>
<h2>Plugin support on mobile<a name="plugin-support-on-mobile" href="#plugin-support-on-mobile" class="heading-anchor">🔗</a></h2>
<p>As always, most of the above changes also apply to mobile (iOS and Android), for example the trash folder and MFA support.</p>
<p>Additionally the mobile application now adds support for plugins. To enable the feature, go to the settings then to the &quot;Plugins&quot; section. The feature is currently in Beta, in particular it means that some plugins do not work or only partially work. Normally the app should not offer you to install a non-working plugin but that may still happen. In general if you notice any issue with this beta feature please let me us know as we're keen to improve it.</p>
<p>Support for cross-platform plugins in Joplin is great news as it means a lot of new features become available on mobile. As of now, we have checked the following plugins and can confirm that they work on mobile:</p>
<ul>
<li><a href="https://joplinapp.org/plugins/plugin/com.whatever.quick-links/">Quick Links</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/com.whatever.inline-tags/">Inline Tags</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/io.github.personalizedrefrigerator.codemirror6-settings/">CodeMirror 6 settings</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/com.hieuthi.joplin.function-plot/">Function plot</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/joplin.plugin.space-indenter/">Space indenter</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/joplin.plugin.alondmnt.tag-navigator/">Inline Tag Navigator</a></li>
</ul>
<p>Those are just some examples - many more are working!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-mobile-plugins.png" alt=""></p>
<h1>Full changelogs<a name="full-changelogs" href="#full-changelogs" class="heading-anchor">🔗</a></h1>
<p>This is just an overview of the main features. The full changelogs are available there:</p>
<ul>
<li>Desktop: <a href="https://joplinapp.org/help/about/changelog/desktop">https://joplinapp.org/help/about/changelog/desktop</a></li>
<li>Android: <a href="https://joplinapp.org/help/about/changelog/android/">https://joplinapp.org/help/about/changelog/android/</a></li>
<li>iOS: <a href="https://joplinapp.org/help/about/changelog/ios/">https://joplinapp.org/help/about/changelog/ios/</a></li>
</ul>
]]></description><link>https://joplinapp.org/news/20240701-release-3-0</link><guid isPermaLink="false">20240701-release-3-0</guid><pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate><twitter-text>What&apos;s new in Joplin 3.0</twitter-text></item><item><title><![CDATA[What's new in Joplin 2.14]]></title><description><![CDATA[<h2>OCR<a name="ocr" href="#ocr" class="heading-anchor">🔗</a></h2>
<p>Optical Character Recognition (OCR) in Joplin enables the transformation of text-containing images into machine-readable text formats. From this version you can enable OCR in the Configuration screen under the &quot;General&quot; section. Once activated, Joplin scans images and PDFs, extracting text data for searchability.</p>
<p>While OCR search is available on both desktop and mobile apps, document scanning is limited to the desktop due to resource demands. For more information head to the <a href="https://joplinapp.org/help/apps/ocr">OCR official documentation</a>!</p>
<h2>Bundled plugins<a name="bundled-plugins" href="#bundled-plugins" class="heading-anchor">🔗</a></h2>
@@ -394,7 +437,4 @@ sys 0m38.013s</p>
</tr>
</tbody>
</table>
]]></description><link>https://joplinapp.org/news/20220522-gsoc-contributors</link><guid isPermaLink="false">20220522-gsoc-contributors</guid><pubDate>Sun, 22 May 2022 00:00:00 GMT</pubDate><twitter-text>Joplin received 6 Contributor Projects for GSoC 2022! Welcome to our new contributors who will be working on these projects over summer!</twitter-text></item><item><title><![CDATA[GSoC "Contributor Proposals" phase is starting now!]]></title><description><![CDATA[<p>The &quot;Contributor Proposals&quot; phase of GSoC 2022 is starting today! If you would like to be a contributor, now is the time to choose your project idea, write your proposal, and upload it to <a href="https://summerofcode.withgoogle.com/">https://summerofcode.withgoogle.com/</a></p>
<p>When it's done, please also let us know by posting an update on your forum introduction post.</p>
<p>If you haven't created a pull request yet, it's still time to create one. Doing so will greatly increase your chances of being selected!</p>
]]></description><link>https://joplinapp.org/news/20220405-gsoc-contributor-proposals</link><guid isPermaLink="false">20220405-gsoc-contributor-proposals</guid><pubDate>Tue, 05 Apr 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
]]></description><link>https://joplinapp.org/news/20220522-gsoc-contributors</link><guid isPermaLink="false">20220522-gsoc-contributors</guid><pubDate>Sun, 22 May 2022 00:00:00 GMT</pubDate><twitter-text>Joplin received 6 Contributor Projects for GSoC 2022! Welcome to our new contributors who will be working on these projects over summer!</twitter-text></item></channel></rss>

View File

@@ -120,9 +120,10 @@ fi
print "Checking dependencies..."
## Check if libfuse2 is present.
if [[ $(command -v ldconfig) ]]; then
LIBFUSE=$(ldconfig -p | grep "libfuse.so.2" || echo '')
else
LIBFUSE=$(find /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib -name "libfuse.so.2" 2>/dev/null | grep "libfuse.so.2" || echo '')
LIBFUSE=$(ldconfig -p | grep "libfuse.so.2" || echo '')
fi
if [[ $LIBFUSE == "" ]]; then
LIBFUSE=$(find /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib -name "libfuse.so.2" 2>/dev/null | grep "libfuse.so.2" || echo '')
fi
if [[ $LIBFUSE == "" ]]; then
print "${COLOR_RED}Error: Can't get libfuse2 on system, please install libfuse2${COLOR_RESET}"

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -15,7 +15,6 @@
# SLAVE_POSTGRES_USER=joplin
# SLAVE_POSTGRES_PORT=5433
# SLAVE_POSTGRES_HOST=localhost
# USERS_WITH_REPLICATION=ID1,ID2,...
version: '2'

View File

@@ -21,7 +21,8 @@ module.exports = {
// See https://github.com/lint-staged/lint-staged/issues/934#issuecomment-743299357
'*.{js,jsx,ts,tsx,task1}': 'yarn checkIgnoredFiles',
'*.{js,jsx,ts,tsx,task2}': 'yarn spellcheck',
'*.{js,jsx,ts,tsx,task3}': 'yarn packageJsonLint',
'*.{js,jsx,ts,tsx,task4}': 'yarn linter-precommit',
'*.{md,mdx}': 'yarn spellcheck',
'*.{js,jsx,ts,tsx,task3}': 'yarn linter-precommit',
'*.{json,task4}': 'yarn packageJsonLint',
'*.{md,mdx,task5}': 'yarn spellcheck',
'*.{md,mdx,task6}': 'yarn validateFilenames',
};

View File

@@ -59,6 +59,7 @@
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
"updateNews": "node ./packages/tools/website/updateNews",
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
"validateFilenames": "node ./packages/tools/validateFilenames.js",
"watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch",
"watchWebsite": "nodemon --delay 1 --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --watch packages/doc-builder/build --ext md,ts,js,mustache,css,tsx,gif,png,svg --exec \"node packages/tools/website/build.js && http-server --port 8077 ../joplin-website/docs -a localhost\""
},
@@ -82,7 +83,7 @@
"eslint-plugin-react": "7.33.2",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "10.3.10",
"glob": "10.3.12",
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
@@ -109,6 +110,7 @@
"@react-native-community/slider": "patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch",
"husky": "patch:husky@npm%3A3.1.0#./.yarn/patches/husky-npm-3.1.0-5cc13e4e34.patch",
"chokidar@^2.0.0": "3.5.3",
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch"
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch",
"rn-fetch-blob@0.12.0": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch"
}
}

View File

@@ -1,4 +1,4 @@
import Setting, { SettingStorage } from '@joplin/lib/models/Setting';
import Setting, { AppType, SettingStorage } from '@joplin/lib/models/Setting';
import { SettingItemType } from '@joplin/lib/services/plugins/api/types';
import shim from '@joplin/lib/shim';
@@ -61,7 +61,7 @@ class Command extends BaseCommand {
const description: string[] = [];
if (md.label && md.label()) description.push(md.label());
if (md.description && md.description('desktop')) description.push(md.description('desktop'));
if (md.description && md.description(AppType.Desktop)) description.push(md.description(AppType.Desktop));
if (description.length) props.description = description.join('. ');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@@ -57,13 +57,13 @@
"proper-lockfile": "4.1.2",
"read-chunk": "2.1.0",
"server-destroy": "1.0.1",
"sharp": "0.33.2",
"sharp": "0.33.3",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
"strip-ansi": "6.0.1",
"tcp-port-used": "1.0.2",
"terminal-kit": "3.0.2",
"terminal-kit": "3.1.1",
"tkwidgets": "0.5.27",
"url-parse": "1.5.10",
"word-wrap": "1.2.5",
@@ -73,7 +73,7 @@
"@joplin/tools": "~3.0",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.8",
"@types/node": "18.19.26",
"@types/node": "18.19.31",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",

View File

@@ -2,13 +2,16 @@ import MdToHtml from '@joplin/renderer/MdToHtml';
const { filename } = require('@joplin/lib/path-utils');
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import shim from '@joplin/lib/shim';
import { RenderOptions } from '@joplin/renderer/types';
import { isResourceUrl, resourceUrlToId } from '@joplin/lib/models/utils/resourceUtils';
const { themeStyle } = require('@joplin/lib/theme');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function newTestMdToHtml(options: any = null) {
options = {
ResourceModel: {
isResourceUrl: () => false,
isResourceUrl: isResourceUrl,
urlToId: resourceUrlToId,
},
fsDriver: shim.fsDriver(),
...options,
@@ -39,7 +42,7 @@ describe('MdToHtml', () => {
// if (mdFilename !== 'sanitize_9.md') continue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const mdToHtmlOptions: any = {
const mdToHtmlOptions: RenderOptions = {
bodyOnly: true,
};
@@ -51,6 +54,8 @@ describe('MdToHtml', () => {
};
} else if (mdFilename.startsWith('sourcemap_')) {
mdToHtmlOptions.mapsToLine = true;
} else if (mdFilename.startsWith('resource_')) {
mdToHtmlOptions.resources = {};
}
const markdown = await shim.fsDriver().readFile(mdFilePath);

View File

@@ -0,0 +1,48 @@
<p>Markdown images:</p>
<ul>
<li>
With ALT and title:
<div
class="not-loaded-resource not-loaded-image-resource resource-status-test"
data-original-alt="test"
data-original-title="testing"
data-resource-id="0415d61cc33e47afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
<li>
With neither ALT nor title:
<div
class="not-loaded-resource not-loaded-image-resource resource-status-error"
data-original-alt=""
data-original-title=""
data-resource-id="0a25d61cc33e57afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
</ul>
<p>HTML images:</p>
<ul>
<li>
<div
class="not-loaded-resource not-loaded-image-resource resource-status-error"
data-original-before=" width=&quot;230&quot;"
data-original-after=" style=&quot;border: 32px inset red;&quot;/"
data-resource-id="0415d61cc33e47afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
<li>
<div
class="not-loaded-resource not-loaded-image-resource resource-status-error"
data-original-after="/"
data-resource-id="0415d61cc33e47afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
</ul>

View File

@@ -0,0 +1,9 @@
Markdown images:
- With ALT and title:![test](:/0415d61cc33e47afa6dde45948c3177f "testing")
- With neither ALT nor title:![](:/0a25d61cc33e57afa6dde45948c3177f)
HTML images:
- <img width="230" src=":/0415d61cc33e47afa6dde45948c3177f" style="border: 32px inset red;"/>
- <img src=":/0415d61cc33e47afa6dde45948c3177f" />

View File

@@ -0,0 +1,15 @@
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22345" data-original-alt data-original-title="test" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></div>
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22346" data-original-alt="test" data-original-title contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></div>
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22347" data-original-before=" " data-original-after=" class=&quot;jop-noMdConv&quot;/" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></div>

View File

@@ -0,0 +1,3 @@
![](:/a1test2a1test2a1test2a1test22345 "test")
![test](:/a1test2a1test2a1test2a1test22346)
<img src=":/a1test2a1test2a1test2a1test22347"/>

View File

@@ -405,7 +405,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
return (
<div key={key} style={rowStyle}>
{label}
{this.renderDescription(this.props.themeId, md.description ? md.description() : null)}
{this.renderDescription(this.props.themeId, md.description ? md.description(AppType.Desktop) : null)}
<SettingComponent
metadata={md}
value={value}
@@ -667,7 +667,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
};
const label = [md.label()];
if (md.unitLabel) label.push(`(${md.unitLabel()})`);
if (md.unitLabel) label.push(`(${md.unitLabel(md.value)})`);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const inputStyle: any = { ...textInputBaseStyle };

View File

@@ -713,7 +713,7 @@ function useMenu(props: Props) {
label: layoutButtonSequenceOptions[value],
type: 'checkbox',
click: () => {
Setting.setValue('layoutButtonSequence', value);
Setting.setValue('layoutButtonSequence', Number(value));
},
});
}

View File

@@ -385,6 +385,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
ref={editorRef}
settings={editorSettings}
pluginStates={props.plugins}
onPasteFile={null}
onEvent={onEditorEvent}
onLogMessage={logDebug}
onEditorPaste={onEditorPaste}

View File

@@ -45,6 +45,9 @@
<script src="./scrollmap.js"></script>
<script>
// Fixes a warning when loading some TypeScript plugins generated with webpack.
window.exports = {};
// This is function used internally to send message from the webview to
// the host.
const ipcProxySendToHost = (methodName, arg) => {

View File

@@ -51,6 +51,7 @@ test.describe('noteList', () => {
await activateMainMenuItem(electronApp, 'Note list', 'Focus');
await expect(mainScreen.noteListContainer.getByText('test note 1')).toBeVisible();
await expect(mainScreen.noteListContainer.getByText('test note 2')).toBeVisible();
await setMessageBoxResponse(electronApp, /^Delete/i);
@@ -61,9 +62,6 @@ test.describe('noteList', () => {
await mainWindow.keyboard.up('Shift');
};
await pressShiftDelete();
await folderBHeader.click();
await folderAHeader.click();
await expect(mainScreen.noteListContainer.getByText('test note 2')).not.toBeVisible();
// Should not delete when the editor is focused

View File

@@ -126,14 +126,14 @@
"@playwright/test": "1.42.1",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.8",
"@types/node": "18.19.26",
"@types/node": "18.19.31",
"@types/react": "18.2.58",
"@types/react-redux": "7.1.33",
"@types/styled-components": "5.1.32",
"@types/tesseract.js": "2.0.0",
"electron": "29.1.0",
"electron-builder": "24.13.3",
"glob": "10.3.10",
"glob": "10.3.12",
"gulp": "4.0.2",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",

View File

@@ -2,6 +2,9 @@
const webviewApiPromises_ = {};
let viewMessageHandler_ = () => {};
// This silences a warning when running plugins generated with Webpack.
window.exports ??= {};
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const webviewApi = {
postMessage: function(message) {

View File

@@ -60,7 +60,8 @@ export const setNotesSortOrder = (field?: string, reverse?: boolean) => {
nextReverse = !!nextReverse;
if (perFieldReverse[nextField] !== nextReverse) {
perFieldReverse[nextField] = nextReverse;
Setting.setValue('notes.perFieldReverse', { ...perFieldReverse });
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactor of old code before rule was applied
Setting.setValue('notes.perFieldReverse', { ...perFieldReverse } as any);
}
}
};

View File

@@ -1,4 +1,4 @@
import Setting from '@joplin/lib/models/Setting';
import Setting, { AppType } from '@joplin/lib/models/Setting';
import bridge from '../bridge';
import processStartFlags from '@joplin/lib/utils/processStartFlags';
import { safeModeFlagFilename } from '@joplin/lib/BaseApplication';
@@ -13,7 +13,7 @@ const restartInSafeModeFromMain = async () => {
// a large amount of other code) to the database.
const appName = bridge().appName();
Setting.setConstant('appId', `net.cozic.${appName}`);
Setting.setConstant('appType', 'desktop');
Setting.setConstant('appType', AppType.Desktop);
Setting.setConstant('appName', appName);
// Load just enough for us to write a file in the profile directory

View File

@@ -66,14 +66,10 @@ yarn-error.log
lib/csstojs/
lib/rnInjectedJs/
dist/
plugins/sources/*
plugins/PluginRunner/**/*.bundle.js
components/NoteBodyViewer/**/*.bundle.js
components/NoteBodyViewer/**/*.bundle.js.LICENSE.txt
components/NoteEditor/**/*.bundle.js
components/NoteEditor/**/*.bundle.js.md5
components/NoteEditor/**/*.bundle.min.js
components/NoteEditor/**/*.bundle.js.LICENSE.txt
components/**/*.bundle.js
components/**/*.bundle.js.LICENSE.txt
components/**/*.bundle.js.md5
components/**/*.bundle.min.js
utils/fs-driver-android.js
android/app/build-*

View File

@@ -0,0 +1,83 @@
import * as React from 'react';
import {
forwardRef, Ref, useEffect, useImperativeHandle, useMemo, useRef,
} from 'react';
import { View } from 'react-native';
import Logger from '@joplin/utils/Logger';
import { Props, WebViewControl } from './types';
import { JSDOM } from 'jsdom';
const logger = Logger.create('ExtendedWebView');
const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
const dom = useMemo(() => {
// Note: Adding `runScripts: 'dangerously'` to allow running inline <script></script>s.
// Use with caution.
return new JSDOM(props.html, { runScripts: 'dangerously' });
}, [props.html]);
useImperativeHandle(ref, (): WebViewControl => {
const result = {
injectJS(js: string) {
return dom.window.eval(js);
},
postMessage(message: unknown) {
const messageEventContent = {
data: message,
source: 'react-native',
};
return dom.window.eval(`
window.dispatchEvent(
new MessageEvent('message', ${JSON.stringify(messageEventContent)}),
);
`);
},
};
return result;
}, [dom]);
const onMessageRef = useRef(props.onMessage);
onMessageRef.current = props.onMessage;
// Don't re-load when injected JS changes. This should match the behavior of the native webview.
const injectedJavaScriptRef = useRef(props.injectedJavaScript);
injectedJavaScriptRef.current = props.injectedJavaScript;
useEffect(() => {
dom.window.eval(`
window.setWebViewApi = (api) => {
window.ReactNativeWebView = api;
};
`);
dom.window.setWebViewApi({
postMessage: (message: unknown) => {
logger.debug('Got message', message);
onMessageRef.current({ nativeEvent: { data: message } });
},
});
dom.window.eval(injectedJavaScriptRef.current);
}, [dom]);
const onLoadEndRef = useRef(props.onLoadEnd);
onLoadEndRef.current = props.onLoadEnd;
const onLoadStartRef = useRef(props.onLoadStart);
onLoadStartRef.current = props.onLoadStart;
useEffect(() => {
logger.debug(`DOM at ${dom.window?.location?.href} is reloading.`);
onLoadStartRef.current?.();
onLoadEndRef.current?.();
}, [dom]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- HACK: Allow wrapper testing logic to access the DOM.
const additionalProps: any = { document: dom?.window?.document };
return (
<View style={props.style} testID={props.testID} {...additionalProps}/>
);
};
export default forwardRef(ExtendedWebView);

View File

@@ -5,73 +5,17 @@ import * as React from 'react';
import {
forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState,
} from 'react';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import { WebViewErrorEvent, WebViewEvent, WebViewSource } from 'react-native-webview/lib/WebViewTypes';
import { WebView } from 'react-native-webview';
import { WebViewErrorEvent, WebViewSource } from 'react-native-webview/lib/WebViewTypes';
import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim';
import { StyleProp, ViewStyle } from 'react-native';
import Logger from '@joplin/utils/Logger';
import { Props, WebViewControl } from './types';
const logger = Logger.create('ExtendedWebView');
export interface WebViewControl {
// Evaluate the given [script] in the context of the page.
// Unlike react-native-webview/WebView, this does not need to return true.
injectJS(script: string): void;
// message must be convertible to JSON
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
postMessage(message: any): void;
}
interface SourceFileUpdateEvent {
uri: string;
baseUrl: string;
filePath: string;
}
export type OnMessageCallback = (event: WebViewMessageEvent)=> void;
export type OnErrorCallback = (event: WebViewErrorEvent)=> void;
export type OnLoadCallback = (event: WebViewEvent)=> void;
type OnFileUpdateCallback = (event: SourceFileUpdateEvent)=> void;
interface Props {
// A name to be associated with the WebView (e.g. NoteEditor)
// This name should be unique.
webviewInstanceId: string;
// If HTML is still being loaded, [html] should be an empty string.
html: string;
// Allow a secure origin to load content from any other origin.
// Defaults to 'never'.
// See react-native-webview's prop with the same name.
mixedContentMode?: 'never' | 'always';
allowFileAccessFromJs?: boolean;
hasPluginScripts?: boolean;
// Initial javascript. Must evaluate to true.
injectedJavaScript: string;
// iOS only: Scroll the outer content of the view. Set this to `false` if
// the main view container doesn't scroll.
scrollEnabled?: boolean;
style?: StyleProp<ViewStyle>;
onMessage: OnMessageCallback;
onError?: OnErrorCallback;
onLoadStart?: OnLoadCallback;
onLoadEnd?: OnLoadCallback;
// Triggered when the file containing [html] is overwritten with new content.
onFileUpdate?: OnFileUpdateCallback;
// Defaults to the resource directory
baseDirectory?: string;
}
export { WebViewControl, Props };
const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
const webviewRef = useRef(null);
@@ -124,11 +68,6 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
baseUrl,
};
setSource(newSource);
props.onFileUpdate?.({
...newSource,
filePath: tempFile,
});
}
if (props.html && props.html.length > 0) {
@@ -140,7 +79,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
return () => {
cancelled = true;
};
}, [props.html, props.webviewInstanceId, props.onFileUpdate, baseDirectory, baseUrl]);
}, [props.html, props.webviewInstanceId, baseDirectory, baseUrl]);
const onError = useCallback((event: WebViewErrorEvent) => {
logger.error('Error', event.nativeEvent.description);

View File

@@ -0,0 +1,46 @@
import { StyleProp, ViewStyle } from 'react-native';
import { WebViewErrorEvent } from 'react-native-webview/lib/WebViewTypes';
export interface WebViewControl {
// Evaluate the given [script] in the context of the page.
// Unlike react-native-webview/WebView, this does not need to return true.
injectJS(script: string): void;
// message must be convertible to JSON
postMessage(message: unknown): void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Needs to interface with old code from before rule was applied.
export type OnMessageEvent = { nativeEvent: { data: any } };
export type OnMessageCallback = (event: OnMessageEvent)=> void;
export type OnErrorCallback = (event: WebViewErrorEvent)=> void;
export type OnLoadCallback = ()=> void;
export interface Props {
// A name to be associated with the WebView (e.g. NoteEditor)
// This name should be unique.
webviewInstanceId: string;
testID?: string;
hasPluginScripts?: boolean;
// Forwarded to RN WebView
scrollEnabled?: boolean;
allowFileAccessFromJs?: boolean;
mixedContentMode?: 'never'|'always';
// If HTML is still being loaded, [html] should be an empty string.
html: string;
// Initial javascript. Must evaluate to true.
injectedJavaScript: string;
style?: StyleProp<ViewStyle>;
onMessage: OnMessageCallback;
onError?: OnErrorCallback;
onLoadStart?: OnLoadCallback;
onLoadEnd?: OnLoadCallback;
// Defaults to the resource directory
baseDirectory?: string;
}

View File

@@ -0,0 +1,181 @@
import * as React from 'react';
import { describe, it, beforeEach } from '@jest/globals';
import { render, screen, waitFor } from '@testing-library/react-native';
import '@testing-library/jest-native/extend-expect';
import NoteBodyViewer from './NoteBodyViewer';
import Setting from '@joplin/lib/models/Setting';
import { MenuProvider } from 'react-native-popup-menu';
import { resourceFetcher, setupDatabaseAndSynchronizer, supportDir, switchClient, synchronizerStart } from '@joplin/lib/testing/test-utils';
import { MarkupLanguage } from '@joplin/renderer';
import { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
import Resource from '@joplin/lib/models/Resource';
import shim from '@joplin/lib/shim';
import Note from '@joplin/lib/models/Note';
interface WrapperProps {
noteBody: string;
highlightedKeywords?: string[];
noteResources?: unknown;
onJoplinLinkClick?: HandleMessageCallback;
onScroll?: (percent: number)=> void;
onMarkForDownload?: OnMarkForDownloadCallback;
}
const emptyObject = {};
const emptyArray: string[] = [];
const noOpFunction = () => {};
const WrappedNoteViewer: React.FC<WrapperProps> = (
{
noteBody,
highlightedKeywords = emptyArray,
noteResources = emptyObject,
onJoplinLinkClick = noOpFunction,
onScroll = noOpFunction,
onMarkForDownload,
}: WrapperProps,
) => {
return <MenuProvider>
<NoteBodyViewer
themeId={Setting.THEME_LIGHT}
style={emptyObject}
noteBody={noteBody}
noteMarkupLanguage={MarkupLanguage.Markdown}
highlightedKeywords={highlightedKeywords}
noteResources={noteResources}
paddingBottom={0}
initialScroll={0}
noteHash={''}
onJoplinLinkClick={onJoplinLinkClick}
onMarkForDownload={onMarkForDownload}
onScroll={onScroll}
pluginStates={emptyObject}
/>
</MenuProvider>;
};
const getNoteViewerDom = async (): Promise<Document> => {
const webviewContent = await screen.findByTestId('NoteBodyViewer');
expect(webviewContent).toBeVisible();
await waitFor(() => {
expect(!!webviewContent.props.document).toBe(true);
});
// Return the composite ExtendedWebView component
// See https://callstack.github.io/react-native-testing-library/docs/advanced/testing-env#tree-navigation
return webviewContent.props.document;
};
describe('NoteBodyViewer', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(0);
await switchClient(0);
});
afterEach(() => {
screen.unmount();
});
it('should render markdown and re-render on change', async () => {
render(<WrappedNoteViewer noteBody='# Test'/>);
const expectHeaderToBe = async (text: string) => {
const noteViewer = await getNoteViewerDom();
await waitFor(async () => {
expect(noteViewer.querySelector('h1').textContent).toBe(text);
});
};
await expectHeaderToBe('Test');
screen.rerender(<WrappedNoteViewer noteBody='# Test 2'/>);
await expectHeaderToBe('Test 2');
screen.rerender(<WrappedNoteViewer noteBody='# Test 3'/>);
await expectHeaderToBe('Test 3');
});
it.each([
{ keywords: ['match'], body: 'A match and another match. Both should be highlighted.', expectedMatchCount: 2 },
{ keywords: ['test'], body: 'No match.', expectedMatchCount: 0 },
{ keywords: ['a', 'b'], body: 'a, a, a, b, b, b', expectedMatchCount: 6 },
])('should highlight search terms (case %#)', async ({ keywords, body, expectedMatchCount }) => {
render(
<WrappedNoteViewer
highlightedKeywords={keywords}
noteBody={body}
/>,
);
let noteViewerDom = await getNoteViewerDom();
await waitFor(() => {
expect(noteViewerDom.querySelectorAll('.highlighted-keyword')).toHaveLength(expectedMatchCount);
});
// Should update highlights when the keywords change
screen.rerender(
<WrappedNoteViewer
highlightedKeywords={[]}
noteBody={body}
/>,
);
noteViewerDom = await getNoteViewerDom();
await waitFor(() => {
expect(noteViewerDom.querySelectorAll('.highlighted-keyword')).toHaveLength(0);
});
});
it('tapping on resource download icons should mark the resources for download', async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
let note1 = await Note.save({ title: 'Note 1', parent_id: '' });
note1 = await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`);
await synchronizerStart();
await switchClient(0);
Setting.setValue('sync.resourceDownloadMode', 'manual');
await synchronizerStart();
const allResources = await Resource.all();
expect(allResources.length).toBe(1);
const localResource = allResources[0];
const localState = await Resource.localState(localResource);
expect(localState.fetch_status).toBe(Resource.FETCH_STATUS_IDLE);
const onMarkForDownload: OnMarkForDownloadCallback = jest.fn(({ resourceId }) => {
return resourceFetcher().markForDownload([resourceId]);
});
render(
<WrappedNoteViewer
noteBody={note1.body}
noteResources={{ [localResource.id]: { localState, item: localResource } }}
onMarkForDownload={onMarkForDownload}
/>,
);
// The resource placeholder should have rendered
const noteViewerDom = await getNoteViewerDom();
let resourcePlaceholder: HTMLElement|null = null;
await waitFor(() => {
const placeholders = noteViewerDom.querySelectorAll<HTMLElement>(`[data-resource-id=${JSON.stringify(localResource.id)}]`);
expect(placeholders).toHaveLength(1);
resourcePlaceholder = placeholders[0];
});
expect([...resourcePlaceholder.classList]).toContain('resource-status-notDownloaded');
// Clicking on the placeholder should download its resource
await waitFor(() => {
resourcePlaceholder.click();
expect(onMarkForDownload).toHaveBeenCalled();
});
await resourceFetcher().waitForAllFinished();
await waitFor(async () => {
expect(await Resource.localState(localResource.id)).toMatchObject({ fetch_status: Resource.FETCH_STATUS_DONE });
});
});
});

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import useOnMessage, { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
import { useRef, useCallback, useState, useMemo } from 'react';
import { View } from 'react-native';
import { View, ViewStyle } from 'react-native';
import BackButtonDialogBox from '../BackButtonDialogBox';
import ExtendedWebView, { WebViewControl } from '../ExtendedWebView';
import useOnResourceLongPress from './hooks/useOnResourceLongPress';
@@ -14,13 +14,13 @@ import Setting from '@joplin/lib/models/Setting';
import uuid from '@joplin/lib/uuid';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import useContentScripts from './hooks/useContentScripts';
import { MarkupLanguage } from '@joplin/renderer';
interface Props {
themeId: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
style: any;
style: ViewStyle;
noteBody: string;
noteMarkupLanguage: number;
noteMarkupLanguage: MarkupLanguage;
highlightedKeywords: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
noteResources: any;
@@ -111,6 +111,7 @@ export default function NoteBodyViewer(props: Props) {
<ExtendedWebView
ref={webviewRef}
webviewInstanceId='NoteBodyViewer'
testID='NoteBodyViewer'
html={html}
allowFileAccessFromJs={true}
injectedJavaScript={injectedJs}

View File

@@ -1,3 +1,4 @@
/** @jest-environment jsdom */
import Setting from '@joplin/lib/models/Setting';
import Renderer, { RendererSettings, RendererSetupOptions } from './Renderer';
import shim from '@joplin/lib/shim';

View File

@@ -6,6 +6,7 @@ import Renderer from './Renderer';
declare global {
interface Window {
rendererWebViewOptions: RendererWebViewOptions;
webviewLib: { postMessage: (message: string)=> void };
}
}
@@ -32,6 +33,8 @@ webviewLib.initialize({
messenger.remoteApi.onPostMessage(message);
},
});
// Share the webview library globally so that the renderer can access it.
window.webviewLib = webviewLib;
const renderer = new Renderer({
...window.rendererWebViewOptions,
@@ -58,5 +61,5 @@ const onMainContentScroll = () => {
// scroll. However, window.addEventListener('scroll', callback) does.
// - iOS needs a listener to be added to scrollingElement -- events aren't received when
// the listener is added to window with window.addEventListener('scroll', ...).
document.scrollingElement.addEventListener('scroll', onMainContentScroll);
document.scrollingElement?.addEventListener('scroll', onMainContentScroll);
window.addEventListener('scroll', onMainContentScroll);

View File

@@ -59,8 +59,8 @@ const useContentScripts = (pluginStates: PluginStates) => {
if (event.cancelled) return;
const contentScriptModule = `(function () {
const module = { exports: null };
const exports = {};
const module = { exports: exports };
${content}

View File

@@ -19,7 +19,10 @@ const getEditIconSrc = (theme: Theme) => {
// Copy in the background -- the edit icon popover script doesn't need the
// icon immediately.
void (async () => {
await shim.fsDriver().copy(iconUri, destPath);
// Can be '' in a testing environment.
if (iconUri) {
await shim.fsDriver().copy(iconUri, destPath);
}
})();
return destPath;

View File

@@ -1,4 +1,4 @@
import { Dispatch, RefObject, SetStateAction, useEffect, useMemo } from 'react';
import { Dispatch, RefObject, SetStateAction, useEffect, useMemo, useRef } from 'react';
import { WebViewControl } from '../../ExtendedWebView';
import { OnScrollCallback, OnWebViewMessageHandler } from '../types';
import RNToWebViewMessenger from '../../../utils/ipc/RNToWebViewMessenger';
@@ -35,11 +35,17 @@ const onPostPluginMessage = async (contentScriptId: string, message: any) => {
};
const useRenderer = (props: Props) => {
const onScrollRef = useRef(props.onScroll);
onScrollRef.current = props.onScroll;
const onPostMessageRef = useRef(props.onPostMessage);
onPostMessageRef.current = props.onPostMessage;
const messenger = useMemo(() => {
const fsDriver = shim.fsDriver();
const localApi = {
onScroll: props.onScroll,
onPostMessage: props.onPostMessage,
onScroll: (fraction: number) => onScrollRef.current?.(fraction),
onPostMessage: (message: string) => onPostMessageRef.current?.(message),
onPostPluginMessage,
fsDriver: {
writeFile: async (path: string, content: string, encoding?: string) => {
@@ -58,7 +64,7 @@ const useRenderer = (props: Props) => {
return new RNToWebViewMessenger<NoteViewerRemoteApi, NoteViewerLocalApi>(
'note-viewer', props.webviewRef, localApi,
);
}, [props.onScroll, props.onPostMessage, props.webviewRef, props.tempDir]);
}, [props.webviewRef, props.tempDir]);
useEffect(() => {
props.setOnWebViewMessage(() => (event: WebViewMessageEvent) => {

View File

@@ -1,4 +1,4 @@
import { WebViewMessageEvent } from 'react-native-webview';
import { OnMessageEvent } from '../ExtendedWebView/types';
export type OnScrollCallback = (scrollTop: number)=> void;
export type OnWebViewMessageHandler = (event: WebViewMessageEvent)=> void;
export type OnWebViewMessageHandler = (event: OnMessageEvent)=> void;

View File

@@ -27,6 +27,21 @@ export const initCodeMirror = (
initialText,
settings,
onPasteFile: async (data) => {
const reader = new FileReader();
return new Promise<void>((resolve, reject) => {
reader.onload = async () => {
const dataUrl = reader.result as string;
const base64 = dataUrl.replace(/^data:.*;base64,/, '');
await messenger.remoteApi.onPasteFile(data.type, base64);
resolve();
};
reader.onerror = () => reject(new Error('Failed to load file.'));
reader.readAsDataURL(data);
});
},
onLogMessage: message => {
void messenger.remoteApi.logMessage(message);
},

View File

@@ -7,11 +7,11 @@ import { themeStyle } from '@joplin/lib/theme';
import { Theme } from '@joplin/lib/themes/type';
import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Alert, BackHandler } from 'react-native';
import { WebViewMessageEvent } from 'react-native-webview';
import ExtendedWebView, { WebViewControl } from '../../ExtendedWebView';
import { clearAutosave, writeAutosave } from './autosave';
import { LocalizedStrings } from './js-draw/types';
import VersionInfo from 'react-native-version-info';
import { OnMessageEvent } from '../../ExtendedWebView/types';
const logger = Logger.create('ImageEditor');
@@ -280,7 +280,7 @@ const ImageEditor = (props: Props) => {
})();`);
}, [webviewRef, props.resourceFilename]);
const onMessage = useCallback(async (event: WebViewMessageEvent) => {
const onMessage = useCallback(async (event: OnMessageEvent) => {
const data = event.nativeEvent.data;
if (data.startsWith('error:')) {
logger.error('ImageEditor:', data);

View File

@@ -38,7 +38,7 @@ describe('NoteEditor', () => {
onChange={()=>{}}
onSelectionChange={()=>{}}
onUndoRedoDepthChange={()=>{}}
onAttach={()=>{}}
onAttach={async ()=>{}}
plugins={{}}
/>
</MenuProvider>,
@@ -78,5 +78,7 @@ describe('NoteEditor', () => {
}
});
}
wrappedNoteEditor.unmount();
});
});

View File

@@ -21,16 +21,19 @@ import { EditorCommandType, EditorKeymap, EditorLanguageType, SearchState } from
import SelectionFormatting, { defaultSelectionFormatting } from '@joplin/editor/SelectionFormatting';
import useCodeMirrorPlugins from './hooks/useCodeMirrorPlugins';
import RNToWebViewMessenger from '../../utils/ipc/RNToWebViewMessenger';
import { WebViewMessageEvent } from 'react-native-webview';
import { WebViewErrorEvent } from 'react-native-webview/lib/RNCWebViewNativeComponent';
import Logger from '@joplin/utils/Logger';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import useEditorCommandHandler from './hooks/useEditorCommandHandler';
import { OnMessageEvent } from '../ExtendedWebView/types';
import { join, dirname } from 'path';
import * as mimeUtils from '@joplin/lib/mime-utils';
import uuid from '@joplin/lib/uuid';
type ChangeEventHandler = (event: ChangeEvent)=> void;
type UndoRedoDepthChangeHandler = (event: UndoRedoDepthChangeEvent)=> void;
type SelectionChangeEventHandler = (event: SelectionRangeChangeEvent)=> void;
type OnAttachCallback = ()=> void;
type OnAttachCallback = (filePath?: string)=> Promise<void>;
const logger = Logger.create('NoteEditor');
@@ -50,7 +53,7 @@ interface Props {
}
function fontFamilyFromSettings() {
const font = editorFont(Setting.value('style.editor.fontFamily'));
const font = editorFont(Setting.value('style.editor.fontFamily') as number);
return font ? `${font}, sans-serif` : 'sans-serif';
}
@@ -373,6 +376,9 @@ function NoteEditor(props: Props, ref: any) {
const onEditorEvent = useRef((_event: EditorEvent) => {});
const onAttachRef = useRef(props.onAttach);
onAttachRef.current = props.onAttach;
const editorMessenger = useMemo(() => {
const localApi: WebViewToEditorApi = {
async onEditorEvent(event) {
@@ -381,6 +387,16 @@ function NoteEditor(props: Props, ref: any) {
async logMessage(message) {
logger.debug('CodeMirror:', message);
},
async onPasteFile(type, data) {
const tempFilePath = join(Setting.value('tempDir'), `paste.${uuid.createNano()}.${mimeUtils.toFileExtension(type)}`);
await shim.fsDriver().mkdir(dirname(tempFilePath));
try {
await shim.fsDriver().writeFile(tempFilePath, data, 'base64');
await onAttachRef.current(tempFilePath);
} finally {
await shim.fsDriver().remove(tempFilePath);
}
},
};
const messenger = new RNToWebViewMessenger<WebViewToEditorApi, EditorBodyControl>(
'editor', webviewRef, localApi,
@@ -450,7 +466,7 @@ function NoteEditor(props: Props, ref: any) {
editorMessenger.onWebViewLoaded();
}, [editorMessenger]);
const onMessage = useCallback((event: WebViewMessageEvent) => {
const onMessage = useCallback((event: OnMessageEvent) => {
const data = event.nativeEvent.data;
if (data.indexOf('error:') === 0) {

View File

@@ -1,3 +1,5 @@
/** @jest-environment jsdom */
import CommandService from '@joplin/lib/services/CommandService';
import useEditorCommandHandler from './useEditorCommandHandler';
import commandDeclarations from '../commandDeclarations';

View File

@@ -57,4 +57,5 @@ export interface SelectionRange {
export interface WebViewToEditorApi {
onEditorEvent(event: EditorEvent): Promise<void>;
logMessage(message: string): Promise<void>;
onPasteFile(type: string, dataBase64: string): Promise<void>;
}

View File

@@ -1,18 +1,18 @@
import BasePluginRunner from '@joplin/lib/services/plugins/BasePluginRunner';
import PluginApiGlobal from '@joplin/lib/services/plugins/api/Global';
import Plugin from '@joplin/lib/services/plugins/Plugin';
import { WebViewControl } from '../../components/ExtendedWebView';
import { WebViewControl } from '../ExtendedWebView';
import { RefObject } from 'react';
import { WebViewMessageEvent } from 'react-native-webview';
import RNToWebViewMessenger from '../../utils/ipc/RNToWebViewMessenger';
import { PluginMainProcessApi, PluginWebViewApi } from './types';
import shim from '@joplin/lib/shim';
import Logger from '@joplin/utils/Logger';
import createOnLogHander from './utils/createOnLogHandler';
import { OnMessageEvent } from '../ExtendedWebView/types';
const logger = Logger.create('PluginRunner');
type MessageEventListener = (event: WebViewMessageEvent)=> boolean;
type MessageEventListener = (event: OnMessageEvent)=> boolean;
export default class PluginRunner extends BasePluginRunner {
private messageEventListeners: MessageEventListener[] = [];
@@ -71,7 +71,7 @@ export default class PluginRunner extends BasePluginRunner {
`);
}
public onWebviewMessage = (event: WebViewMessageEvent) => {
public onWebviewMessage = (event: OnMessageEvent) => {
this.messageEventListeners = this.messageEventListeners.filter(
// Remove all listeners that return false
listener => listener(event),

View File

@@ -1,10 +1,9 @@
import * as React from 'react';
import ExtendedWebView, { WebViewControl } from '../../components/ExtendedWebView';
import ExtendedWebView, { WebViewControl } from '../ExtendedWebView';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import shim from '@joplin/lib/shim';
import PluginRunner from './PluginRunner';
import loadPlugins from '../loadPlugins';
import loadPlugins from '@joplin/lib/services/plugins/loadPlugins';
import { connect, useStore } from 'react-redux';
import Logger from '@joplin/utils/Logger';
import { View } from 'react-native';
@@ -14,6 +13,7 @@ import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import PluginDialogManager from './dialogs/PluginDialogManager';
import { AppState } from '../../utils/types';
import usePrevious from '@joplin/lib/hooks/usePrevious';
import PlatformImplementation from '../../services/plugins/PlatformImplementation';
const logger = Logger.create('PluginRunnerWebView');
@@ -42,7 +42,14 @@ const usePlugins = (
return;
}
await loadPlugins({ pluginRunner, pluginSettings, store, reloadAll: reloadAllRef.current, cancelEvent: event });
await loadPlugins({
pluginRunner,
pluginSettings,
platformImplementation: PlatformImplementation.instance(),
store,
reloadAll: reloadAllRef.current,
cancelEvent: event,
});
// A full reload, if it was necessary, has been completed.
if (!event.cancelled) {

View File

@@ -1,3 +1,4 @@
/** @jest-environment jsdom */
import getFormData from './getFormData';
describe('getFormData', () => {

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { useCallback, useMemo, useState } from 'react';
import { PluginHtmlContents, PluginStates, ViewInfo } from '@joplin/lib/services/plugins/reducer';
import { StyleSheet, View, useWindowDimensions } from 'react-native';
import usePlugin from '../../hooks/usePlugin';
import usePlugin from '@joplin/lib/hooks/usePlugin';
import { DialogContentSize, DialogWebViewApi } from '../types';
import { Button } from 'react-native-paper';
import { themeStyle } from '@joplin/lib/theme';

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { PluginHtmlContents, ViewInfo } from '@joplin/lib/services/plugins/reducer';
import ExtendedWebView, { WebViewControl } from '../../../components/ExtendedWebView';
import ExtendedWebView, { WebViewControl } from '../../ExtendedWebView';
import { ViewStyle } from 'react-native';
import usePlugin from '../../hooks/usePlugin';
import usePlugin from '@joplin/lib/hooks/usePlugin';
import shim from '@joplin/lib/shim';
import useDialogMessenger from './hooks/useDialogMessenger';
import useWebViewSetup from './hooks/useWebViewSetup';

View File

@@ -1,7 +1,7 @@
import { useMemo, RefObject } from 'react';
import { DialogMainProcessApi, DialogWebViewApi } from '../../types';
import Logger from '@joplin/utils/Logger';
import { WebViewControl } from '../../../../components/ExtendedWebView';
import { WebViewControl } from '../../../ExtendedWebView';
import createOnLogHander from '../../utils/createOnLogHandler';
import RNToWebViewMessenger from '../../../../utils/ipc/RNToWebViewMessenger';
import { SerializableData } from '@joplin/lib/utils/ipc/types';

View File

@@ -475,7 +475,7 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
}
const settingComp = this.settingToComponent(md.key, settings[md.key]);
const relatedText = [md.label?.() ?? '', md.description?.() ?? ''];
const relatedText = [md.label?.() ?? '', md.description?.(AppType.Mobile) ?? ''];
addSettingComponent(
settingComp,
relatedText,
@@ -830,8 +830,11 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
}
let screenHeadingText = _('Configuration');
let showSearchButton = true;
if (currentSectionName) {
screenHeadingText = Setting.sectionNameToLabel(currentSectionName);
showSearchButton = currentSectionName !== 'plugins';
}
return (
@@ -839,7 +842,7 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
<ScreenHeader
title={screenHeadingText}
showSaveButton={true}
showSearchButton={true}
showSearchButton={showSearchButton}
showSideMenuButton={false}
saveButtonDisabled={!this.hasUnsavedChanges()}
onSaveButtonPress={this.saveButton_press}

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { UpdateSettingValueCallback } from './types';
import { View, Text, TextInput } from 'react-native';
import Setting from '@joplin/lib/models/Setting';
import Setting, { AppType } from '@joplin/lib/models/Setting';
import Dropdown from '../../Dropdown';
import { ConfigScreenStyles } from './configScreenStyles';
import Slider from '@react-native-community/slider';
@@ -32,7 +32,7 @@ const SettingComponent: React.FunctionComponent<Props> = props => {
const output: any = null;
const md = Setting.settingMetadata(props.settingId);
const settingDescription = md.description ? md.description() : '';
const settingDescription = md.description ? md.description(AppType.Mobile) : '';
const styleSheet = props.styles.styleSheet;

View File

@@ -52,6 +52,8 @@ const backlinksPluginId = 'joplin.plugin.ambrt.backlinksToNote';
describe('PluginStates.installed', () => {
beforeEach(async () => {
jest.useRealTimers();
await setupDatabaseAndSynchronizer(0);
await switchClient(0);
reduxStore = createMockReduxStore();
@@ -60,6 +62,9 @@ describe('PluginStates.installed', () => {
await mockMobilePlatform('android');
await mockRepositoryApiConstructor();
// Fake timers are necessary to prevent a warning.
jest.useFakeTimers();
});
afterEach(async () => {
for (const pluginId of PluginService.instance().pluginIds) {
@@ -228,7 +233,7 @@ describe('PluginStates.installed', () => {
// After updating, the update button should read "updated". Use a large
// timeout because updating plugins can be slow, particularly in CI.
const updatedButton = await screen.findByRole('button', { name: 'Updated', disabled: true, timeout: 16000 });
const updatedButton = await screen.findByRole('button', { name: 'Updated', disabled: true }, { timeout: 16000 });
expect(updatedButton).toBeVisible();
// Should be marked as updated.

View File

@@ -34,6 +34,7 @@ let reduxStore: Store<AppState>;
describe('PluginStates.search', () => {
beforeEach(async () => {
jest.useRealTimers();
await setupDatabaseAndSynchronizer(0);
await switchClient(0);
reduxStore = createMockReduxStore();
@@ -42,6 +43,7 @@ describe('PluginStates.search', () => {
resetRepoApi();
await mockRepositoryApiConstructor();
jest.useFakeTimers();
});
it('should find results', async () => {

View File

@@ -2,7 +2,7 @@ import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
import { useMemo, useRef } from 'react';
import usePlugin from '../../../../../plugins/hooks/usePlugin';
import usePlugin from '@joplin/lib/hooks/usePlugin';
// initialItem is used when the plugin is not installed. For example, if the plugin item is being
// created from search results.

View File

@@ -6,10 +6,9 @@ import UndoRedoService from '@joplin/lib/services/UndoRedoService';
import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer';
import checkPermissions from '../../utils/checkPermissions';
import NoteEditor from '../NoteEditor/NoteEditor';
import { Size } from '@joplin/utils/types';
const FileViewer = require('react-native-file-viewer').default;
const React = require('react');
import { Keyboard, View, TextInput, StyleSheet, Linking, Image, Share, NativeSyntheticEvent } from 'react-native';
import { Keyboard, View, TextInput, StyleSheet, Linking, Share, NativeSyntheticEvent } from 'react-native';
import { Platform, PermissionsAndroid } from 'react-native';
const { connect } = require('react-redux');
// const { MarkdownEditor } = require('@joplin/lib/../MarkdownEditor/index.js');
@@ -36,7 +35,6 @@ import { BaseScreenComponent } from '../base-screen';
import { themeStyle, editorFont } from '../global-style';
const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
import ImageResizer from '@bam.tech/react-native-image-resizer';
import shared, { BaseNoteScreenComponent } from '@joplin/lib/components/shared/note-screen-shared';
import { Asset, ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker';
import SelectDateTimeDialog from '../SelectDateTimeDialog';
@@ -65,6 +63,8 @@ import debounce from '../../utils/debounce';
import { focus } from '@joplin/lib/utils/focusHandler';
import CommandService from '@joplin/lib/services/CommandService';
import * as urlUtils from '@joplin/lib/urlUtils';
import getImageDimensions from '../../utils/image/getImageDimensions';
import resizeImage from '../../utils/image/resizeImage';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const emptyArray: any[] = [];
@@ -682,24 +682,9 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
return result;
}
public async imageDimensions(uri: string): Promise<Size> {
return new Promise((resolve, reject) => {
Image.getSize(
uri,
(width: number, height: number) => {
resolve({ width: width, height: height });
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(error: any) => {
reject(error);
},
);
});
}
public async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
const maxSize = Resource.IMAGE_MAX_DIMENSION;
const dimensions = await this.imageDimensions(localFilePath);
const dimensions = await getImageDimensions(localFilePath);
reg.logger().info('Original dimensions ', dimensions);
const saveOriginalImage = async () => {
@@ -711,30 +696,14 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
dimensions.height = maxSize;
reg.logger().info('New dimensions ', dimensions);
const format = mimeType === 'image/png' ? 'PNG' : 'JPEG';
reg.logger().info(`Resizing image ${localFilePath}`);
const resizedImage = await ImageResizer.createResizedImage(
localFilePath,
dimensions.width,
dimensions.height,
format,
85, // quality
undefined, // rotation
undefined, // outputPath
true, // keep metadata
);
const resizedImagePath = resizedImage.uri;
reg.logger().info('Resized image ', resizedImagePath);
reg.logger().info(`Moving ${resizedImagePath} => ${targetPath}`);
await shim.fsDriver().copy(resizedImagePath, targetPath);
try {
await shim.fsDriver().unlink(resizedImagePath);
} catch (error) {
reg.logger().warn('Error when unlinking cached file: ', error);
}
await resizeImage({
inputPath: localFilePath,
outputPath: targetPath,
maxWidth: dimensions.width,
maxHeight: dimensions.height,
quality: 85,
format: mimeType === 'image/png' ? 'PNG' : 'JPEG',
});
return true;
};
@@ -1140,11 +1109,19 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
const buttonId = await dialogs.pop(this, _('Choose an option'), buttons);
if (buttonId === 'takePhoto') this.takePhoto_onPress();
if (buttonId === 'attachFile') void this.attachFile_onPress();
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
if (buttonId === 'takePhoto') await this.takePhoto_onPress();
if (buttonId === 'attachFile') await this.attachFile_onPress();
if (buttonId === 'attachPhoto') await this.attachPhoto_onPress();
}
public onAttach = async (filePath?: string) => {
if (filePath) {
await this.attachFile({ uri: filePath }, 'all');
} else {
await this.showAttachMenu();
}
};
// private vosk_:Vosk;
// private async getVosk() {
@@ -1585,7 +1562,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
onChange={this.onMarkdownEditorTextChange}
onSelectionChange={this.onMarkdownEditorSelectionChange}
onUndoRedoDepthChange={this.onUndoRedoDepthChange}
onAttach={() => this.showAttachMenu()}
onAttach={this.onAttach}
readOnly={this.state.readOnly}
plugins={this.props.plugins}
style={{

View File

@@ -12,7 +12,7 @@ module.exports = {
'\\.(ts|tsx)$': 'ts-jest',
},
testEnvironment: 'jsdom',
testEnvironment: 'node',
testMatch: ['**/*.test.(ts|tsx)'],
testPathIgnorePatterns: ['<rootDir>/node_modules/'],
@@ -20,7 +20,7 @@ module.exports = {
// Do transform most packages in node_modules (transformations correct unrecognized
// import syntax)
transformIgnorePatterns: ['<rootDir>/node_modules/jest', '<rootDir>/node_modules/js-draw'],
transformIgnorePatterns: ['<rootDir>/node_modules/jest', '<rootDir>/node_modules/js-draw', 'node_modules/jsdom'],
slowTestThreshold: 40,
};

View File

@@ -1,9 +1,12 @@
/* eslint-disable jest/require-top-level-describe */
const { afterEachCleanUp, afterAllCleanUp } = require('@joplin/lib/testing/test-utils.js');
const shim = require('@joplin/lib/shim').default;
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const injectedJs = require('./utils/injectedJs.js').default;
const { mkdir, rm } = require('fs-extra');
const path = require('path');
const sharp = require('sharp');
const { tmpdir } = require('os');
const uuid = require('@joplin/lib/uuid').default;
const sqlite3 = require('sqlite3');
@@ -19,7 +22,14 @@ window.setImmediate = setImmediate;
shimInit({
nodeSqlite: sqlite3,
React,
sharp,
});
shim.injectedJs = (name) => {
if (!(name in injectedJs)) {
throw new Error(`Cannot find injected JS with ID ${name}`);
}
return injectedJs[name];
};
// This library has the following error when running within Jest:
// Invariant Violation: `new NativeEventEmitter()` requires a non-null argument.
@@ -40,17 +50,27 @@ jest.doMock('react-native-version-info', () => {
});
// react-native-webview expects native iOS/Android code so needs to be mocked.
jest.mock('react-native-webview', () => {
const { View } = require('react-native');
return {
WebView: View,
};
jest.mock('./components/ExtendedWebView', () => {
return require('./components/ExtendedWebView/index.jest.js');
});
jest.mock('@react-native-clipboard/clipboard', () => {
return { default: { getString: jest.fn(), setString: jest.fn() } };
});
jest.mock('react-native-share', () => {
return { default: { } };
});
// Used by the renderer
jest.doMock('react-native-vector-icons/Ionicons', () => {
return {
default: class extends require('react-native').View {
static getImageSourceSync = () => ({ uri: '' });
},
};
});
// react-native-fs's CachesDirectoryPath export doesn't work in a testing environment.
// Use a temporary folder instead.
const tempDirectoryPath = path.join(tmpdir(), `appmobile-test-${uuid.createNano()}`);

View File

@@ -27,10 +27,10 @@
"@joplin/utils": "~3.0",
"@react-native-clipboard/clipboard": "1.14.1",
"@react-native-community/datetimepicker": "8.0.0",
"@react-native-community/geolocation": "3.1.0",
"@react-native-community/geolocation": "3.2.1",
"@react-native-community/netinfo": "11.3.1",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-community/slider": "4.5.0",
"@react-native-community/slider": "4.5.2",
"assert-browserify": "2.0.0",
"buffer": "6.0.3",
"constants-browserify": "1.0.0",
@@ -45,7 +45,7 @@
"react": "18.2.0",
"react-native": "0.74.1",
"react-native-camera": "4.2.1",
"react-native-device-info": "10.12.1",
"react-native-device-info": "10.13.1",
"react-native-dialogbox": "0.6.10",
"react-native-document-picker": "9.1.1",
"react-native-dropdownalert": "5.1.0",
@@ -53,7 +53,7 @@
"react-native-file-viewer": "2.1.5",
"react-native-fingerprint-scanner": "6.0.0",
"react-native-fs": "2.20.0",
"react-native-get-random-values": "1.10.0",
"react-native-get-random-values": "1.11.0",
"react-native-image-picker": "7.1.1",
"react-native-localize": "3.0.6",
"react-native-modal-datetime-picker": "17.1.0",
@@ -70,7 +70,7 @@
"react-native-vector-icons": "10.1.0",
"react-native-version-info": "1.1.1",
"react-native-vosk": "0.1.12",
"react-native-webview": "13.8.1",
"react-native-webview": "13.8.6",
"react-native-zip-archive": "6.1.0",
"react-redux": "8.1.3",
"redux": "4.2.1",
@@ -108,10 +108,11 @@
"jest-environment-jsdom": "29.7.0",
"jetifier": "2.0.0",
"js-draw": "1.20.2",
"jsdom": "23.0.1",
"jsdom": "23.2.0",
"nodemon": "3.0.3",
"punycode": "2.3.1",
"react-test-renderer": "18.2.0",
"sharp": "0.33.2",
"sqlite3": "5.1.6",
"ts-jest": "29.1.1",
"ts-loader": "9.5.1",

View File

@@ -14,7 +14,7 @@ import ResourceService from '@joplin/lib/services/ResourceService';
import KvStore from '@joplin/lib/services/KvStore';
import NoteScreen from './components/screens/Note';
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen';
import Setting, { Env } from '@joplin/lib/models/Setting';
import Setting, { AppType, Env } from '@joplin/lib/models/Setting';
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
import reducer, { NotesParent, parseNotesParent, serializeNotesParent } from '@joplin/lib/reducer';
import ShareExtension from './utils/ShareExtension';
@@ -41,7 +41,7 @@ const { BackButtonService } = require('./services/back-button.js');
import NavService from '@joplin/lib/services/NavService';
import { createStore, applyMiddleware, Dispatch } from 'redux';
import reduxSharedMiddleware from '@joplin/lib/components/shared/reduxSharedMiddleware';
const { shimInit } = require('./utils/shim-init-react.js');
import shimInit from './utils/shim-init-react';
const { AppNav } = require('./components/app-nav.js');
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
@@ -122,14 +122,15 @@ import { ReactNode } from 'react';
import { parseShareCache } from '@joplin/lib/services/share/reducer';
import autodetectTheme, { onSystemColorSchemeChange } from './utils/autodetectTheme';
import runOnDeviceFsDriverTests from './utils/fs-driver/runOnDeviceTests';
import PluginRunnerWebView from './plugins/PluginRunner/PluginRunnerWebView';
import PluginRunnerWebView from './components/plugins/PluginRunnerWebView';
import { refreshFolders, scheduleRefreshFolders } from '@joplin/lib/folders-screen-utils';
import KeymapService from '@joplin/lib/services/KeymapService';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import initializeCommandService from './utils/initializeCommandService';
import PlatformImplementation from './plugins/PlatformImplementation';
import PlatformImplementation from './services/plugins/PlatformImplementation';
import ShareManager from './components/screens/ShareManager';
import appDefaultState, { DEFAULT_ROUTE } from './utils/appDefaultState';
import { setDateFormat, setTimeFormat, setTimeLocale } from '@joplin/utils/time';
type SideMenuPosition = 'left' | 'right';
@@ -186,10 +187,13 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) =>
if ((action.type === 'SETTING_UPDATE_ONE' && (action.key === 'dateFormat' || action.key === 'timeFormat')) || (action.type === 'SETTING_UPDATE_ALL')) {
time.setDateFormat(Setting.value('dateFormat'));
time.setTimeFormat(Setting.value('timeFormat'));
setDateFormat(Setting.value('dateFormat'));
setTimeFormat(Setting.value('timeFormat'));
}
if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'locale' || action.type === 'SETTING_UPDATE_ALL') {
setLocale(Setting.value('locale'));
setTimeLocale(Setting.value('locale'));
}
if ((action.type === 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type === 'SETTING_UPDATE_ALL')) {
@@ -501,9 +505,9 @@ async function initialize(dispatch: Dispatch) {
value: profileConfig,
});
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
Setting.setConstant('env', __DEV__ ? Env.Dev : Env.Prod);
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
Setting.setConstant('appType', 'mobile');
Setting.setConstant('appType', AppType.Mobile);
Setting.setConstant('tempDir', await initializeTempDir());
Setting.setConstant('cacheDir', `${getProfilesRootDir()}/cache`);
const resourceDir = getResourceDir(currentProfile, isSubProfile);
@@ -616,7 +620,7 @@ async function initialize(dispatch: Dispatch) {
reg.logger().info(`First start: detected locale as ${detectedLocale}`);
Setting.skipDefaultMigrations();
Setting.setValue('firstStart', 0);
Setting.setValue('firstStart', false);
} else {
Setting.applyDefaultMigrations();
}
@@ -785,7 +789,7 @@ async function initialize(dispatch: Dispatch) {
const pluginSettings = pluginService.unserializePluginSettings(Setting.value('plugins.states'));
const updatedSettings = pluginService.clearUpdateState(pluginSettings);
Setting.setValue('plugins.states', pluginService.serializePluginSettings(updatedSettings));
Setting.setValue('plugins.states', updatedSettings);
// ----------------------------------------------------------------------------
// Keep this below to test react-native-rsa-native

View File

@@ -16,7 +16,7 @@ const jsDrawBundle = new BundledFile(
const pluginBackgroundPageBundle = new BundledFile(
'pluginBackgroundPage',
`${mobileDir}/plugins/PluginRunner/backgroundPage/pluginRunnerBackgroundPage.ts`,
`${mobileDir}/components/plugins/backgroundPage/pluginRunnerBackgroundPage.ts`,
);
const noteViewerBundle = new BundledFile(

View File

@@ -0,0 +1,18 @@
import { Size } from '@joplin/utils/types';
import { Image as NativeImage } from 'react-native';
const getImageDimensions = async (uri: string): Promise<Size> => {
return new Promise((resolve, reject) => {
NativeImage.getSize(
uri,
(width: number, height: number) => {
resolve({ width: width, height: height });
},
(error: unknown) => {
reject(error);
},
);
});
};
export default getImageDimensions;

View File

@@ -0,0 +1,43 @@
import shim from '@joplin/lib/shim';
import Logger from '@joplin/utils/Logger';
import ImageResizer from '@bam.tech/react-native-image-resizer';
const logger = Logger.create('resizeImage');
type OutputFormat = 'PNG' | 'JPEG';
interface Options {
inputPath: string;
outputPath: string;
maxWidth: number;
maxHeight: number;
format: OutputFormat;
quality: number;
}
const resizeImage = async (options: Options) => {
const resizedImage = await ImageResizer.createResizedImage(
options.inputPath,
options.maxWidth,
options.maxHeight,
options.format,
options.quality, // quality
undefined, // rotation
undefined, // outputPath
true, // keep metadata
);
const resizedImagePath = resizedImage.uri;
logger.info('Resized image ', resizedImagePath);
logger.info(`Moving ${resizedImagePath} => ${options.outputPath}`);
await shim.fsDriver().copy(resizedImagePath, options.outputPath);
try {
await shim.fsDriver().unlink(resizedImagePath);
} catch (error) {
logger.warn('Error when unlinking cached file: ', error);
}
};
export default resizeImage;

View File

@@ -0,0 +1,10 @@
const injectedJs = {
webviewLib: require('@joplin/lib/rnInjectedJs/webviewLib'),
codeMirrorBundle: require('../lib/rnInjectedJs/codeMirrorBundle.bundle'),
svgEditorBundle: require('../lib/rnInjectedJs/svgEditorBundle.bundle'),
pluginBackgroundPage: require('../lib/rnInjectedJs/pluginBackgroundPage.bundle'),
noteBodyViewerBundle: require('../lib/rnInjectedJs/noteBodyViewerBundle.bundle'),
};
export default injectedJs;

View File

@@ -1,9 +1,9 @@
import RemoteMessenger from '@joplin/lib/utils/ipc/RemoteMessenger';
import { SerializableData } from '@joplin/lib/utils/ipc/types';
import { WebViewMessageEvent } from 'react-native-webview';
import { WebViewControl } from '../../components/ExtendedWebView';
import { RefObject } from 'react';
import { OnMessageEvent } from '../../components/ExtendedWebView/types';
export default class RNToWebViewMessenger<LocalInterface, RemoteInterface> extends RemoteMessenger<LocalInterface, RemoteInterface> {
public constructor(channelId: string, private webviewControl: WebViewControl|RefObject<WebViewControl>, localApi: LocalInterface) {
@@ -32,7 +32,7 @@ export default class RNToWebViewMessenger<LocalInterface, RemoteInterface> exten
`);
}
public onWebViewMessage = (event: WebViewMessageEvent) => {
public onWebViewMessage = (event: OnMessageEvent) => {
if (!this.hasBeenClosed()) {
void this.onMessage(JSON.parse(event.nativeEvent.data));
}

View File

@@ -1,28 +1,22 @@
const shim = require('@joplin/lib/shim').default;
import shim from '@joplin/lib/shim';
const { GeolocationReact } = require('./geolocation-react.js');
const PoorManIntervals = require('@joplin/lib/PoorManIntervals').default;
const RNFetchBlob = require('rn-fetch-blob').default;
const { generateSecureRandom } = require('react-native-securerandom');
const FsDriverRN = require('./fs-driver/fs-driver-rn').default;
const { Buffer } = require('buffer');
const { Linking, Platform } = require('react-native');
const showMessageBox = require('./showMessageBox.js').default;
const mimeUtils = require('@joplin/lib/mime-utils.js');
const { basename, fileExtension } = require('@joplin/lib/path-utils');
const uuid = require('@joplin/lib/uuid').default;
const Resource = require('@joplin/lib/models/Resource').default;
const { getLocales } = require('react-native-localize');
const { setLocale, defaultLocale, closestSupportedLocale } = require('@joplin/lib/locale');
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
import RNFetchBlob from 'rn-fetch-blob';
import { generateSecureRandom } from 'react-native-securerandom';
import FsDriverRN from './fs-driver/fs-driver-rn';
import { Buffer } from 'buffer';
import { Linking, Platform } from 'react-native';
import showMessageBox from './showMessageBox.js';
import * as mimeUtils from '@joplin/lib/mime-utils';
import { basename, fileExtension } from '@joplin/lib/path-utils';
import uuid from '@joplin/lib/uuid';
import Resource from '@joplin/lib/models/Resource';
import { getLocales } from 'react-native-localize';
import { setLocale, defaultLocale, closestSupportedLocale } from '@joplin/lib/locale';
import type SettingType from '@joplin/lib/models/Setting';
import injectedJs from './injectedJs';
const injectedJs = {
webviewLib: require('@joplin/lib/rnInjectedJs/webviewLib'),
codeMirrorBundle: require('../lib/rnInjectedJs/codeMirrorBundle.bundle'),
svgEditorBundle: require('../lib/rnInjectedJs/svgEditorBundle.bundle'),
pluginBackgroundPage: require('../lib/rnInjectedJs/pluginBackgroundPage.bundle'),
noteBodyViewerBundle: require('../lib/rnInjectedJs/noteBodyViewerBundle.bundle'),
};
function shimInit() {
export default function shimInit() {
shim.Geolocation = GeolocationReact;
shim.sjclModule = require('@joplin/lib/vendor/sjcl-rn.js');
@@ -33,7 +27,7 @@ function shimInit() {
return shim.fsDriver_;
};
shim.randomBytes = async count => {
shim.randomBytes = async (count: number) => {
const randomBytes = await generateSecureRandom(count);
const temp = [];
for (const n in randomBytes) {
@@ -91,7 +85,7 @@ function shimInit() {
/* eslint-enable */
shim.detectAndSetLocale = (Setting) => {
shim.detectAndSetLocale = (Setting: typeof SettingType) => {
// [
// {
// "countryCode": "US",
@@ -179,7 +173,7 @@ function shimInit() {
try {
const response = await shim.fetchWithRetry(doFetchBlob, options);
// Returns an object that's roughtly compatible with a standard Response object
// Returns an object that's roughly compatible with a standard Response object
const output = {
ok: response.respInfo.status < 400,
path: response.data,
@@ -212,7 +206,7 @@ function shimInit() {
trusty: options.ignoreTlsErrors,
}).fetch(method, url, headers, RNFetchBlob.wrap(options.path));
// Returns an object that's roughtly compatible with a standard Response object
// Returns an object that's roughly compatible with a standard Response object
return {
ok: response.respInfo.status < 400,
data: response.data,
@@ -239,7 +233,7 @@ function shimInit() {
shim.showMessageBox = showMessageBox;
shim.openUrl = url => {
Linking.openURL(url);
return Linking.openURL(url);
};
shim.httpAgent = () => {
@@ -247,7 +241,7 @@ function shimInit() {
};
shim.waitForFrame = () => {
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
resolve();
});
@@ -299,7 +293,7 @@ function shimInit() {
shim.injectedJs = function(name) {
if (!(name in injectedJs)) throw new Error(`Cannot find injectedJs file (add it to "injectedJs" object): ${name}`);
return injectedJs[name];
return injectedJs[name as keyof typeof injectedJs];
};
shim.setTimeout = (fn, interval) => {
@@ -320,4 +314,3 @@ function shimInit() {
}
module.exports = { shimInit };

View File

@@ -2,6 +2,6 @@
"io.github.jackgruber.backup": {
"cloneUrl": "https://github.com/JackGruber/joplin-plugin-backup.git",
"branch": "master",
"commit": "52d898315cab259da638698cc41120e9fae593ef"
"commit": "5ba57c18ac0f24f20832f012e015a080b138f0c4"
}
}

View File

@@ -39,6 +39,7 @@ describe('createEditor', () => {
settings: editorSettings,
onEvent: _event => {},
onLogMessage: _message => {},
onPasteFile: null,
});
// Force the generation of the syntax tree now.
@@ -66,6 +67,7 @@ describe('createEditor', () => {
settings: editorSettings,
onEvent: _event => {},
onLogMessage: _message => {},
onPasteFile: null,
});
const getContentScriptJs = jest.fn(async () => {
@@ -133,6 +135,7 @@ describe('createEditor', () => {
settings: editorSettings,
onEvent: _event => {},
onLogMessage: _message => {},
onPasteFile: null,
});
const getContentScriptJs = jest.fn(async () => {

View File

@@ -30,6 +30,7 @@ import configFromSettings from './configFromSettings';
import getScrollFraction from './getScrollFraction';
import CodeMirrorControl from './CodeMirrorControl';
import insertLineAfter from './editorCommands/insertLineAfter';
import handlePasteEvent from './utils/handlePasteEvent';
const createEditor = (
parentElement: HTMLElement, props: EditorProps,
@@ -257,6 +258,24 @@ const createEditor = (
fraction: getScrollFraction(view),
});
},
paste: (event, view) => {
if (props.onPasteFile) {
handlePasteEvent(event, view, props.onPasteFile);
}
},
dragover: (event, _view) => {
if (props.onPasteFile && event.dataTransfer.files.length) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
return true;
}
return false;
},
drop: (event, view) => {
if (props.onPasteFile) {
handlePasteEvent(event, view, props.onPasteFile);
}
},
}),
EditorState.tabSize.of(4),

View File

@@ -82,7 +82,7 @@ export default class PluginLoader {
scriptElement.appendChild(document.createTextNode(`
(async () => {
const exports = {};
const module = {};
const module = { exports: exports };
const require = window.__pluginLoaderRequireFunctions[${JSON.stringify(this.pluginLoaderId)}];
const joplin = {
require,
@@ -90,7 +90,7 @@ export default class PluginLoader {
${js};
window.__pluginLoaderScriptLoadCallbacks[${JSON.stringify(scriptId)}](module.exports || exports);
window.__pluginLoaderScriptLoadCallbacks[${JSON.stringify(scriptId)}](module.exports);
})();
`));

View File

@@ -10,6 +10,7 @@ const createEditorControl = (initialText: string) => {
settings: editorSettings,
onEvent: _event => {},
onLogMessage: _message => {},
onPasteFile: null,
});
};

View File

@@ -0,0 +1,29 @@
import { EditorView } from '@codemirror/view';
import { PasteFileCallback } from '../../types';
const handlePasteEvent = (event: ClipboardEvent|DragEvent, _view: EditorView, onPaste: PasteFileCallback) => {
const dataTransfer = 'clipboardData' in event ? event.clipboardData : event.dataTransfer;
const files = dataTransfer.files;
let fileToPaste: File|null = null;
// Prefer image files, if available.
for (const file of files) {
if (['image/png', 'image/jpeg', 'image/svg+xml'].includes(file.type)) {
fileToPaste = file;
break;
}
}
// Fall back to other files
if (files.length && !fileToPaste) {
fileToPaste = files[0];
}
if (fileToPaste) {
event.preventDefault();
void onPaste(fileToPaste);
}
};
export default handlePasteEvent;

View File

@@ -168,11 +168,14 @@ export interface EditorSettings {
export type LogMessageCallback = (message: string)=> void;
export type OnEventCallback = (event: EditorEvent)=> void;
export type PasteFileCallback = (data: File)=> Promise<void>;
export interface EditorProps {
settings: EditorSettings;
initialText: string;
// If null, paste and drag-and-drop will not work for resources unless handled elsewhere.
onPasteFile: PasteFileCallback|null;
onEvent: OnEventCallback;
onLogMessage: LogMessageCallback;
}

View File

@@ -46,7 +46,7 @@
},
"devDependencies": {
"@types/jest": "29.5.8",
"@types/node": "18.19.26",
"@types/node": "18.19.31",
"@typescript-eslint/eslint-plugin": "6.8.0",
"@typescript-eslint/parser": "6.8.0",
"coveralls": "3.1.1",

View File

@@ -3,7 +3,7 @@
"packageManager": "yarn@3.6.0",
"private": true,
"scripts": {
"buildPluginDoc_": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme '../../Assets/PluginDocTheme/' --readme '../../Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../../../joplin-website/docs/api/references/plugin_api ../lib/services/plugins/api/"
"buildPluginDoc_": "typedoc --exclude '../lib/models/**' --name 'Joplin Plugin API Documentation' --mode file -theme '../../Assets/PluginDocTheme/' --readme '../../Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../../../joplin-website/docs/api/references/plugin_api ../lib/services/plugins/api/"
},
"dependencies": {
"typedoc": "0.17.8",

View File

@@ -21,6 +21,7 @@ import BaseItem from './models/BaseItem';
import Note from './models/Note';
import Tag from './models/Tag';
import { splitCommandString } from '@joplin/utils';
import { setDateFormat, setTimeFormat, setTimeLocale } from '@joplin/utils/time';
import { reg } from './registry';
import time from './time';
import BaseSyncTarget from './BaseSyncTarget';
@@ -357,6 +358,9 @@ export default class BaseApplication {
const sideEffects: any = {
'dateFormat': async () => {
time.setLocale(Setting.value('locale'));
setTimeLocale(Setting.value('locale'));
setDateFormat(Setting.value('dateFormat'));
setTimeFormat(Setting.value('timeFormat'));
time.setDateFormat(Setting.value('dateFormat'));
time.setTimeFormat(Setting.value('timeFormat'));
},
@@ -683,7 +687,7 @@ export default class BaseApplication {
const tempDir = `${profileDir}/tmp`;
const cacheDir = `${profileDir}/cache`;
Setting.setConstant('env', initArgs.env);
Setting.setConstant('env', initArgs.env as Env);
Setting.setConstant('resourceDirName', resourceDirName);
Setting.setConstant('resourceDir', resourceDir);
Setting.setConstant('tempDir', tempDir);
@@ -785,12 +789,12 @@ export default class BaseApplication {
Setting.skipDefaultMigrations();
if (Setting.value('env') === 'dev') {
Setting.setValue('showTrayIcon', 0);
Setting.setValue('autoUpdateEnabled', 0);
Setting.setValue('showTrayIcon', false);
Setting.setValue('autoUpdateEnabled', false);
Setting.setValue('sync.interval', 3600);
}
Setting.setValue('firstStart', 0);
Setting.setValue('firstStart', false);
} else {
Setting.applyDefaultMigrations();
Setting.applyUserSettingMigration();

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