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

Compare commits

..

180 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
Laurent Cozic
e49bca8315 iOS 13.0.6 2024-07-06 13:23:17 +02:00
Laurent Cozic
636fbdf7d0 Android 3.0.8 2024-07-06 13:21:55 +02:00
Laurent Cozic
ee97434bb0 Tools: Skip more minor changes for changelog generation 2024-07-06 12:19:17 +02:00
Laurent Cozic
599cf5b86f Desktop release v3.0.13 2024-07-06 12:12:46 +02:00
Henry Heino
2fd6a3a2fa Desktop: Fixes #10685: Fix shift-delete asks to permanently delete the current note, rather than cut text, when the editor is selected. (#10687) 2024-07-06 12:05:35 +02:00
Henry Heino
a3e04103de Desktop: Fixes #10679: Fix incorrect text rendering on MacOS by changing the default font to Avenir Next (#10686) 2024-07-05 19:58:09 +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
Henry Heino
320d0df60d Desktop, Mobile: Fixes #10674: Fix sidebar performance regression with many nested notebooks (#10676) 2024-07-04 14:56:57 +02:00
Joplin Bot
7e4533d811 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-03 12:22:19 +00:00
cedecode
f32fe63205 All: Update de_DE.po (small improvements) (#10673) 2024-07-03 13:53:32 +02: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
3e0fb48e44 Api: Do not return deleted notes in folders/:id/notes call 2024-07-03 10:41:31 +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
Laurent Cozic
4a475f1b53 CLI v3.0.1 2024-07-02 20:58:38 +02:00
Laurent Cozic
8679cc5704 Lock file 2024-07-02 20:41:46 +02:00
Laurent Cozic
a48c4ba93f Releasing sub-packages 2024-07-02 20:40: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
Laurent Cozic
16e82b5462 iOS 13.0.5 2024-07-01 17:48:06 +02:00
Laurent Cozic
1a82255865 Android 3.0.7 2024-07-01 17:47:33 +02:00
Laurent Cozic
a64d6e3270 Desktop release v3.0.12 2024-07-01 17:40:41 +02:00
Laurent Cozic
ae1620dd50 Update translations 2024-07-01 17:39:00 +02:00
rnbastos
d804e95d06 Update pt_BR.po (#10667) 2024-07-01 17:33:36 +02:00
rnbastos
6c091910cd Update pt_BR.po (#10667) 2024-07-01 17:32:43 +02:00
pedr
78d9a7e636 Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore (#10649) 2024-07-01 17:31:58 +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
Laurent Cozic
3270122419 Fixed tests 2024-06-29 18:36:43 +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
e4b8976aa0 All: Set min version for synchronising to 3.0.0 2024-06-29 12:29:41 +02:00
Laurent Cozic
a86ee1d34e Merge branch 'release-3.0' into dev 2024-06-29 12:25:11 +02:00
Laurent Cozic
17e1eecb11 iOS 13.0.4 2024-06-29 12:21:51 +02:00
Laurent Cozic
dd9a389711 Android 3.0.6 2024-06-29 12:20:36 +02:00
Laurent Cozic
55eaedb8b2 Desktop release v3.0.11 2024-06-29 11:34:25 +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
cedecode
f8d772de87 All: Update de_DE.po (#10660) 2024-06-27 11:43:26 +02:00
qx100
77c39ac084 Translate Update zh_CN.po (#10659) 2024-06-26 18:39:32 +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
3557138c84 iOS: Fix voice typing URL setting incorrectly visible (#10643) 2024-06-21 17:16:25 +01:00
Henry Heino
77e74112ad Mobile: Fixes #10637: Fix refocusing the note editor (#10644) 2024-06-21 17:16:12 +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
Laurent Cozic
4115e2054f lock file 2024-06-19 16:33:21 +01:00
Laurent Cozic
8485277dcf iOS 13.0.3 2024-06-19 16:29:43 +01:00
Laurent Cozic
fac9ea3b42 Android 3.0.5 2024-06-19 16:28:45 +01:00
Laurent Cozic
45f8e27d6a Desktop release v3.0.10 2024-06-19 12:38:09 +01:00
Laurent Cozic
c8a478d970 Update translations 2024-06-19 12:37:57 +01:00
pedr
75dfb0af5f Mobile: Fixes #10252 Not able to change notebook when using 'New Note' quick action (#10588) 2024-06-19 12:33:22 +01:00
Mohammad Ashouri
5e592a3096 All: Update farsi/persian translation fa.po (#10634) 2024-06-19 11:00:31 +01:00
Laurent Cozic
84e46ad874 Updating farsi/persian translation (by mimeyn) 2024-06-19 10:59:51 +01:00
XPhyro
0ec917bb96 All: Don't render empty title page for Fountain (#10631) 2024-06-19 10:55:05 +01:00
Henry Heino
818f9f58d1 Desktop: Fixes #10061: Fix paste adds newlines in the Rich Text Editor when certain plugins are enabled (#10635) 2024-06-19 10:54:34 +01:00
renovate[bot]
e1abe0b4cb Update contributor-assistant/github-action action to v2.3.2 (#10633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 10:46:09 +01:00
Henry Heino
c972ce223e Mobile: Fixes #10593: Fix plugin list not cached in config screen (#10599)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-18 10:02:42 +01:00
Henry Heino
d9dadf28cb Mobile: Resolves #10594: Move mobile plugin setting tabs under a separate section (#10600) 2024-06-18 10:02:11 +01:00
Henry Heino
1fb392ff4e Mobile: Fix cmd-i no longer italicizes text (#10604) 2024-06-18 10:02:01 +01:00
pedr
affa620983 Desktop: Fixes: #10030: Prevent application from crashing when the syncInfoCache is corrupted (#10546) 2024-06-18 10:01:35 +01:00
Henry Heino
ed31d8202b Mobile: Fixes #10589: Fix selected note changes on moving to a different folder (#10630) 2024-06-18 09:59:08 +01:00
renovate[bot]
573ea6051c Update dependency @react-native-community/netinfo to v11.3.1 (#10632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 23:20:07 +01:00
renovate[bot]
f1ec54532f Update dependency jsdom to v23 (#10628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:25:40 +01:00
renovate[bot]
5eb96d71e1 Update dependency sass to v1.71.0 (#10627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:56 +01:00
renovate[bot]
73251bac4a Update dependency react-native-webview to v13.8.1 (#10626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:50 +01:00
renovate[bot]
f40a0da195 Update dependency css-loader to v6.10.0 (#10625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:34 +01:00
Laurent Cozic
8dc1ab2cc5 Update renovate.json5 2024-06-17 21:23:55 +01:00
renovate[bot]
1b46c9f5e7 Update dependency chokidar to v3.6.0 (#10623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:23:07 +01:00
renovate[bot]
483ab55a36 Update dependency @react-native-community/netinfo to v11.3.0 (#10622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:56 +01:00
renovate[bot]
502002f9f6 Update dependency style-to-js to v1.1.11 (#10619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:16 +01:00
renovate[bot]
8d8014511f Update dependency turndown to v7.1.3 (#10620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:09 +01:00
renovate[bot]
28569e652e Update dependency react-native-image-picker to v7.1.1 (#10618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:54 +01:00
renovate[bot]
9acf36d802 Update dependency nodemailer to v6.9.12 (#10617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:45 +01:00
renovate[bot]
06d26767ed Update dependency koa to v2.15.1 (#10615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:26 +01:00
renovate[bot]
88858d4413 Update dependency follow-redirects to v1.15.6 (#10614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:13 +01:00
github-actions[bot]
27309427a1 @XPhyro has signed the CLA in laurent22/joplin#10631 2024-06-17 19:50:09 +00:00
renovate[bot]
e83a18a907 Update dependency @types/node to v18.19.26 (#10613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 14:25:46 +00:00
ERYpTION
9bd8b11f67 Update da_DK.po (#10606) 2024-06-17 14:50:34 +01:00
renovate[bot]
3a14c7ce2d Update dependency @types/node to v18.19.25 (#10610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 09:13:03 +01:00
github-actions[bot]
fe4c9a2401 @kohi9noor has signed the CLA in laurent22/joplin#10607 2024-06-16 08:42:17 +00:00
Arda Kılıçdağı
d2fb19cf6d All: Translation: Update tr_TR.po (#10603) 2024-06-15 14:35:15 -04:00
Laurent Cozic
1f8e3fb620 Update translations 2024-06-15 10:17:54 +01:00
Laurent Cozic
8bbe1d30b4 Update French translation 2024-06-15 10:16:16 +01:00
Laurent Cozic
ec92f716de Chore: Removed unncessary string 2024-06-15 10:15:22 +01:00
Laurent Cozic
ab819d9210 Update translations 2024-06-15 10:06:44 +01:00
Henry Heino
e465b45d6e Mobile: Resolves #10592: Make mobile plugin settings screen UI closer to desktop (#10598) 2024-06-15 10:00:21 +01:00
tiberiusteng
a4a4170d49 Mobile: Resolves #8639: implement callback url (#9803)
Co-authored-by: Tib Teng <661892+tiberiusteng@users.noreply.github.com>
2024-06-15 09:58:23 +01:00
Henry Heino
1dcf528443 Chore: Refactor editor package: Move functions in editorStateUtils into separate files (#10591) 2024-06-14 19:40:07 +01:00
Henry Heino
8cf4ef88b5 Desktop: Fixes #10586: Don't re-order the note list when in search (#10587) 2024-06-14 19:39:26 +01:00
Henry Heino
bf634270be Mobile: Mark plugin support as in beta (#10585) 2024-06-14 19:38:50 +01:00
Henry Heino
56437d3e1b Mobile: Don't show an "expand" arrow by "Installed plugins" when no plugins are installed (#10583)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:38:16 +01:00
Henry Heino
d095ab2be7 All: Fixes #10581: English: Use the plural form of a localization for negative and zero items (#10582)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:37:26 +01:00
Henry Heino
4751b4dd74 Mobile: Plugin settings screen: Fix plugin states not set correctly when installing multiple plugins at once (#10580)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:36:44 +01:00
Henry Heino
ce22d8238c Mobile: Plugin settings: Fix plugins without settings can't be disabled without reinstall (#10579)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:36:26 +01:00
pedr
9e2b9e5b8d Desktop: Fixes error when Joplin Cloud login is finished before the settings are saved (#10575) 2024-06-14 19:31:50 +01:00
pedr
4952980e0a Desktop, Mobile: Start synchronisation just after login is complete (#10574) 2024-06-14 14:24:35 +01:00
Joplin Bot
59989d2735 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-13 12:22:09 +00:00
Laurent Cozic
eb7f2855b0 Chore: Trying to fix Android build on CI by using debug keys 2024-06-13 09:01:45 +01:00
356 changed files with 30302 additions and 25662 deletions

View File

@@ -515,16 +515,20 @@ packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/components/ActionButton.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
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
@@ -595,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
@@ -610,7 +635,8 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js
packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
@@ -618,9 +644,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
@@ -628,6 +655,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
@@ -656,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
@@ -713,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
@@ -721,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
@@ -765,6 +772,9 @@ packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
@@ -777,10 +787,23 @@ packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/util/editorStateUtils.test.js
packages/editor/CodeMirror/util/editorStateUtils.js
packages/editor/CodeMirror/util/isInSyntaxNode.js
packages/editor/CodeMirror/util/setupVim.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
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
packages/editor/events.js
packages/editor/types.js
@@ -879,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
@@ -925,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
@@ -946,6 +972,7 @@ packages/lib/ntp.js
packages/lib/onedrive-api.test.js
packages/lib/onedrive-api.js
packages/lib/path-utils.js
packages/lib/reducer.test.js
packages/lib/reducer.js
packages/lib/registry.test.js
packages/lib/registry.js
@@ -1090,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
@@ -1366,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"

View File

@@ -47,5 +47,7 @@ jobs:
- name: Assemble Android Release
run: |
cd packages/app-mobile/android && ./gradlew assembleRelease
cd packages/app-mobile/android
sed -i -- 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/' app/build.gradle
./gradlew assembleRelease

View File

@@ -13,7 +13,7 @@ jobs:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
uses: contributor-assistant/github-action@v2.3.1
uses: contributor-assistant/github-action@v2.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret

98
.gitignore vendored
View File

@@ -494,16 +494,20 @@ packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/components/ActionButton.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
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
@@ -574,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
@@ -589,7 +614,8 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js
packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
@@ -597,9 +623,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
@@ -607,6 +634,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
@@ -635,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
@@ -692,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
@@ -700,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
@@ -744,6 +751,9 @@ packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
@@ -756,10 +766,23 @@ packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/util/editorStateUtils.test.js
packages/editor/CodeMirror/util/editorStateUtils.js
packages/editor/CodeMirror/util/isInSyntaxNode.js
packages/editor/CodeMirror/util/setupVim.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
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
packages/editor/events.js
packages/editor/types.js
@@ -858,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
@@ -904,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
@@ -925,6 +951,7 @@ packages/lib/ntp.js
packages/lib/onedrive-api.test.js
packages/lib/onedrive-api.js
packages/lib/path-utils.js
packages/lib/reducer.test.js
packages/lib/reducer.js
packages/lib/registry.test.js
packages/lib/registry.js
@@ -1069,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
@@ -1345,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

@@ -35,7 +35,7 @@
],
"owner": "Laurent Cozic"
},
"version": "3.0.0",
"version": "3.0.1",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
@@ -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.24",
"@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

@@ -204,7 +204,7 @@ class Application extends BaseApplication {
public updateEditorFont() {
const fontFamilies = [];
if (Setting.value('style.editor.fontFamily')) fontFamilies.push(`"${Setting.value('style.editor.fontFamily')}"`);
fontFamilies.push('Avenir, Arial, sans-serif');
fontFamilies.push('\'Avenir Next\', Avenir, Arial, sans-serif');
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
// https://github.com/laurent22/joplin/issues/155
@@ -420,6 +420,7 @@ class Application extends BaseApplication {
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
reg.setDispatch(this.dispatch.bind(this));
reg.setShowErrorMessageBoxHandler((message: string) => { bridge().showErrorMessageBox(message); });
if (Setting.value('flagOpenDevTools')) {

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

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import PluginService, { defaultPluginSetting, Plugins, PluginSetting, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { _ } from '@joplin/lib/locale';
import styled from 'styled-components';
@@ -214,8 +214,11 @@ export default function(props: Props) {
props.onChange({ value: pluginService.serializePluginSettings(event.value) });
}, [pluginService, props.onChange]);
const onDelete = useOnDeleteHandler(pluginSettings, onPluginSettingsChange, false);
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettings, repoApi, onPluginSettingsChange, true);
const pluginSettingsRef = useRef(pluginSettings);
pluginSettingsRef.current = pluginSettings;
const onDelete = useOnDeleteHandler(pluginSettingsRef, onPluginSettingsChange, false);
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettingsRef, repoApi, onPluginSettingsChange, true);
const onToolsClick = useCallback(async () => {
const template = [

View File

@@ -40,7 +40,10 @@ export default function(props: Props) {
const [installingPluginsIds, setInstallingPluginIds] = useState<Record<string, boolean>>({});
const [searchResultCount, setSearchResultCount] = useState(null);
const onInstall = useOnInstallHandler(setInstallingPluginIds, props.pluginSettings, props.repoApi, props.onPluginSettingsChange, false);
const pluginSettingsRef = useRef(props.pluginSettings);
pluginSettingsRef.current = props.pluginSettings;
const onInstall = useOnInstallHandler(setInstallingPluginIds, pluginSettingsRef, props.repoApi, props.onPluginSettingsChange, false);
useEffect(() => {
setSearchResultCount(null);

View File

@@ -9,6 +9,7 @@ import { Dispatch } from 'redux';
import { reducer, defaultState, generateApplicationConfirmUrl, checkIfLoginWasSuccessful } from '@joplin/lib/services/joplinCloudUtils';
import { AppState } from '../app.reducer';
import Logger from '@joplin/utils/Logger';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('JoplinCloudLoginScreen');
const { connect } = require('react-redux');
@@ -38,6 +39,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
if (response && response.success) {
dispatch({ type: 'COMPLETED' });
clearInterval(interval);
void reg.scheduleSync(0);
}
} catch (error) {
logger.error(error);

View File

@@ -97,6 +97,7 @@ interface Props {
notesSortOrderField: string;
notesSortOrderReverse: boolean;
notesColumns: NoteListColumns;
showInvalidJoplinCloudCredential: boolean;
}
interface ShareFolderDialogOptions {
@@ -592,6 +593,13 @@ class MainScreenComponent extends React.Component<Props, State> {
});
};
const onViewJoplinCloudLoginScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'JoplinCloudLogin',
});
};
const onViewSyncSettingsScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
@@ -684,6 +692,12 @@ class MainScreenComponent extends React.Component<Props, State> {
);
} else if (this.props.mustUpgradeAppMessage) {
msg = this.renderNotificationMessage(this.props.mustUpgradeAppMessage);
} else if (this.props.showInvalidJoplinCloudCredential) {
msg = this.renderNotificationMessage(
_('Your Joplin Cloud credentials are invalid, please login.'),
_('Login to Joplin Cloud.'),
onViewJoplinCloudLoginScreen,
);
}
return (
@@ -705,7 +719,8 @@ class MainScreenComponent extends React.Component<Props, State> {
props.isSafeMode ||
this.showShareInvitationNotification(props) ||
this.props.needApiAuth ||
!!this.props.mustUpgradeAppMessage;
!!this.props.mustUpgradeAppMessage ||
props.showInvalidJoplinCloudCredential;
}
public registerCommands() {
@@ -965,6 +980,7 @@ const mapStateToProps = (state: AppState) => {
notesSortOrderField: state.settings['notes.sortOrder.field'],
notesSortOrderReverse: state.settings['notes.sortOrder.reverse'],
notesColumns: validateColumns(state.settings['notes.columns']),
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && state.mustAuthenticate,
};
};

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

@@ -4,7 +4,7 @@ import KeymapService, { KeymapItem } from '@joplin/lib/services/KeymapService';
import { EditorCommand } from '../../../../utils/types';
import shim from '@joplin/lib/shim';
import { reg } from '@joplin/lib/registry';
import setupVim from '@joplin/editor/CodeMirror/util/setupVim';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import { EventName } from '@joplin/lib/eventManager';
import normalizeAccelerator from '../../utils/normalizeAccelerator';
import { CodeMirrorVersion } from '../../utils/types';

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

@@ -8,7 +8,7 @@ import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
import shim from '@joplin/lib/shim';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import setupVim from '@joplin/editor/CodeMirror/util/setupVim';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import { dirname } from 'path';
import useKeymap from './utils/useKeymap';
import useEditorSearch from '../utils/useEditorSearchExtension';

View File

@@ -6,7 +6,7 @@ import Resource from '@joplin/lib/models/Resource';
const bridge = require('@electron/remote').require('./bridge').default;
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import htmlUtils from '@joplin/lib/htmlUtils';
import rendererHtmlUtils, { extractHtmlBody } from '@joplin/renderer/htmlUtils';
import rendererHtmlUtils, { extractHtmlBody, removeWrappingParagraphAndTrailingEmptyElements } from '@joplin/renderer/htmlUtils';
import Logger from '@joplin/utils/Logger';
import { fileUriToPath } from '@joplin/utils/url';
import { MarkupLanguage } from '@joplin/renderer';
@@ -220,6 +220,13 @@ export async function processPastedHtml(html: string, htmlToMd: HtmlToMarkdownHa
if (htmlToMd && mdToHtml) {
const md = await htmlToMd(MarkupLanguage.Markdown, html, '');
html = (await mdToHtml(MarkupLanguage.Markdown, md, markupRenderOptions({ bodyOnly: true }))).html;
// When plugins that add to the end of rendered content are installed, bodyOnly can
// fail to remove the wrapping paragraph. This works around that issue by removing
// the wrapping paragraph in more cases. See issue #10061.
if (!md.trim().includes('\n')) {
html = removeWrappingParagraphAndTrailingEmptyElements(html);
}
}
return extractHtmlBody(rendererHtmlUtils.sanitizeHtml(html, {

View File

@@ -104,10 +104,17 @@ const useOnKeyDown = (
event.preventDefault();
}
if (noteIds.length && (key === 'Delete' || (key === 'Backspace' && event.metaKey))) {
event.preventDefault();
if (CommandService.instance().isEnabled('deleteNote')) {
void CommandService.instance().execute('deleteNote', noteIds);
if (noteIds.length) {
if (key === 'Delete' && event.shiftKey) {
event.preventDefault();
if (CommandService.instance().isEnabled('permanentlyDeleteNote')) {
void CommandService.instance().execute('permanentlyDeleteNote', noteIds);
}
} else if (key === 'Delete' || (key === 'Backspace' && event.metaKey)) {
event.preventDefault();
if (CommandService.instance().isEnabled('deleteNote')) {
void CommandService.instance().execute('deleteNote', noteIds);
}
}
}

View File

@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { FolderListItem, HeaderId, HeaderListItem, ListItem, ListItemType, TagListItem } from '../types';
import { FolderEntity, TagsWithNoteCountEntity } from '@joplin/lib/services/database/types';
import { renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
import { buildFolderTree, renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
import { _ } from '@joplin/lib/locale';
import CommandService from '@joplin/lib/services/CommandService';
import Setting from '@joplin/lib/models/Setting';
@@ -35,10 +35,13 @@ const useSidebarListData = (props: Props): ListItem[] => {
});
}, [props.tags]);
const folderTree = useMemo(() => {
return buildFolderTree(props.folders);
}, [props.folders]);
const folderItems = useMemo(() => {
const renderProps = {
folders: props.folders,
folderTree,
collapsedFolderIds: props.collapsedFolderIds,
};
return renderFolders<ListItem>(renderProps, (folder, hasChildren, depth): FolderListItem => {
@@ -50,7 +53,7 @@ const useSidebarListData = (props: Props): ListItem[] => {
key: folder.id,
};
});
}, [props.folders, props.collapsedFolderIds]);
}, [folderTree, props.collapsedFolderIds]);
return useMemo(() => {
const foldersHeader: HeaderListItem = {

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

@@ -1,5 +1,7 @@
import { test, expect } from './util/test';
import MainScreen from './models/MainScreen';
import activateMainMenuItem from './util/activateMainMenuItem';
import setMessageBoxResponse from './util/setMessageBoxResponse';
test.describe('noteList', () => {
test('should be possible to edit notes in a different notebook when searching', async ({ mainWindow }) => {
@@ -35,4 +37,40 @@ test.describe('noteList', () => {
// Updating the title should force the sidebar to update sooner
await expect(editor.noteTitleInput).toHaveValue('note-1');
});
test('shift-delete should ask to permanently delete notes, but only when the note list is focused', async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
const sidebar = mainScreen.sidebar;
const folderBHeader = await sidebar.createNewFolder('Folder B');
const folderAHeader = await sidebar.createNewFolder('Folder A');
await expect(folderAHeader).toBeVisible();
await mainScreen.createNewNote('test note 1');
await mainScreen.createNewNote('test note 2');
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);
const pressShiftDelete = async () => {
await mainWindow.keyboard.press('Shift');
await mainWindow.keyboard.press('Delete');
await mainWindow.keyboard.up('Delete');
await mainWindow.keyboard.up('Shift');
};
await pressShiftDelete();
await expect(mainScreen.noteListContainer.getByText('test note 2')).not.toBeVisible();
// Should not delete when the editor is focused
await mainScreen.noteEditor.focusCodeMirrorEditor();
await mainWindow.keyboard.type('test');
await pressShiftDelete();
await folderBHeader.click();
await folderAHeader.click();
await expect(mainScreen.noteListContainer.getByText('test note 1')).toBeVisible();
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.0.9",
"version": "3.0.13",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -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.24",
"@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

@@ -79,8 +79,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097743
versionName "3.0.4"
versionCode 2097747
versionName "3.0.8"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -92,6 +92,15 @@
<data android:mimeType="*/*" />
</intent-filter>
<!-- /SHARE EXTENSION -->
<!-- EXTERNAL LINK -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="joplin" />
</intent-filter>
<!-- /EXTERNAL LINK -->
</activity>
</application>

View File

@@ -0,0 +1,40 @@
import * as React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { themeStyle } from './global-style';
import { useMemo } from 'react';
import { _ } from '@joplin/lib/locale';
import { connect } from 'react-redux';
import { AppState } from '../utils/types';
interface Props {
themeId: number;
size: number;
}
const useStyles = (themeId: number, size: number) => {
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
container: {
borderColor: theme.color4,
color: theme.color4,
fontSize: size,
opacity: 0.8,
borderRadius: 12,
borderWidth: 1,
padding: 4,
flexGrow: 0,
textAlign: 'center',
textAlignVertical: 'center',
},
});
}, [themeId, size]);
};
const BetaChip: React.FC<Props> = props => {
const styles = useStyles(props.themeId, props.size);
return <View><Text style={styles.container}>{_('Beta')}</Text></View>;
};
export default connect((state: AppState) => ({ themeId: state.settings.theme }))(BetaChip);

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

@@ -14,6 +14,7 @@ import createEditor from '@joplin/editor/CodeMirror/createEditor';
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
import WebViewToRNMessenger from '../../../utils/ipc/WebViewToRNMessenger';
import { WebViewToEditorApi } from '../types';
import { focus } from '@joplin/lib/utils/focusHandler';
export const initCodeMirror = (
parentElement: HTMLElement,
@@ -26,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);
},
@@ -47,6 +63,15 @@ export const initCodeMirror = (
}
});
// Note: Just adding an onclick listener seems sufficient to focus the editor when its background
// is tapped.
parentElement.addEventListener('click', (event) => {
const activeElement = document.querySelector(':focus');
if (!parentElement.contains(activeElement) && event.target === parentElement) {
focus('initial editor focus', control);
}
});
messenger.setLocalInterface(control);
return control;
};

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';
}
@@ -101,10 +104,6 @@ function useHtml(initialCss: string): string {
scrolling. */
.cm-scroller {
overflow: none;
/* Ensure that the editor can be focused by clicking on the lower half of the screen.
Don't use 100vh to prevent a scrollbar being present for empty notes. */
min-height: 80vh;
}
</style>
<style class=${JSON.stringify(themeStyleSheetClassName)}>
@@ -377,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) {
@@ -385,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,
@@ -454,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

@@ -16,6 +16,7 @@ interface WrapperProps {
mustUpgradeAppMessage?: string;
shareInvitations?: ShareInvitation[];
processingShareInvitationResponse?: boolean;
showInvalidJoplinCloudCredential?: boolean;
}
const WarningBannerWrapper: React.FC<WrapperProps> = props => {
@@ -29,6 +30,7 @@ const WarningBannerWrapper: React.FC<WrapperProps> = props => {
mustUpgradeAppMessage={props.mustUpgradeAppMessage ?? ''}
shareInvitations={props.shareInvitations ?? []}
processingShareInvitationResponse={props.processingShareInvitationResponse ?? false}
showInvalidJoplinCloudCredential={props.showInvalidJoplinCloudCredential ?? false}
/>;
};

View File

@@ -19,6 +19,7 @@ interface Props {
mustUpgradeAppMessage: string;
shareInvitations: ShareInvitation[];
processingShareInvitationResponse: boolean;
showInvalidJoplinCloudCredential: boolean;
}
@@ -50,6 +51,9 @@ export const WarningBannerComponent: React.FC<Props> = props => {
if (props.hasDisabledEncryptionItems) {
warningComps.push(renderWarningBox('Status', _('Some items cannot be decrypted.')));
}
if (props.showInvalidJoplinCloudCredential) {
warningComps.push(renderWarningBox('JoplinCloudLogin', _('Your Joplin Cloud credentials are invalid, please login.')));
}
const shareInvitation = props.shareInvitations.find(inv => inv.status === ShareUserStatus.Waiting);
if (
@@ -85,5 +89,6 @@ export default connect((state: AppState) => {
mustUpgradeAppMessage: state.mustUpgradeAppMessage,
shareInvitations: state.shareService.shareInvitations,
processingShareInvitationResponse: state.shareService.processingShareInvitationResponse,
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && state.mustAuthenticate,
};
})(WarningBannerComponent);

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

@@ -1,127 +0,0 @@
import * as React from 'react';
import Setting, { AppType, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { ConfigScreenStyles } from './configScreenStyles';
import { FlatList, Text, View, ViewStyle } from 'react-native';
import { settingsSections } from '@joplin/lib/components/shared/config/config-shared';
import Icon from '../../Icon';
import { _ } from '@joplin/lib/locale';
import { TouchableRipple } from 'react-native-paper';
interface Props {
styles: ConfigScreenStyles;
width: number|undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
settings: any;
selectedSectionName: string|null;
openSection: (sectionName: string)=> void;
}
const SectionSelector: FunctionComponent<Props> = props => {
const sections = useMemo(() => {
return settingsSections({ device: AppType.Mobile, settings: props.settings });
}, [props.settings]);
const styles = props.styles.styleSheet;
const itemHeight = styles.sidebarButton.height;
const onRenderButton = ({ item }: { item: SettingMetadataSection }) => {
const section = item;
const selected = props.selectedSectionName === section.name;
const icon = Setting.sectionNameToIcon(section.name, AppType.Mobile);
const label = Setting.sectionNameToLabel(section.name);
const shortDescription = Setting.sectionMetadataToSummary(section);
const isPlugin = item.source === SettingSectionSource.Plugin;
const titleStyle = selected ? styles.sidebarSelectedButtonText : styles.sidebarButtonMainText;
const sourceIcon = isPlugin ? (
<Icon
name='fas fa-puzzle-piece'
accessibilityLabel={_('From a plugin')}
style={titleStyle}
/>
) : null;
return (
<TouchableRipple
key={section.name}
role='tab'
aria-selected={selected}
onPress={() => props.openSection(section.name)}
>
<View
style={selected ? styles.selectedSidebarButton : styles.sidebarButton}
>
<Icon
name={icon}
accessibilityLabel={null}
style={styles.sidebarIcon}
/>
<View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<Text
numberOfLines={1}
style={titleStyle}
>
{label}
</Text>
<Text
style={styles.sidebarButtonDescriptionText}
numberOfLines={1}
ellipsizeMode='tail'
>
{shortDescription ?? ''}
</Text>
</View>
{sourceIcon}
</View>
</TouchableRipple>
);
};
const [flatListRef, setFlatListRef] = useState<FlatList|null>(null);
useEffect(() => {
if (flatListRef && props.selectedSectionName) {
let selectedIndex = 0;
for (const section of sections) {
if (section.name === props.selectedSectionName) {
break;
}
selectedIndex ++;
}
flatListRef.scrollToIndex({
index: selectedIndex,
viewPosition: 0.5,
});
}
}, [props.selectedSectionName, flatListRef, sections]);
const containerStyle: ViewStyle = useMemo(() => ({
width: props.width,
maxWidth: props.width,
minWidth: props.width,
flex: 1,
}), [props.width]);
return (
<View style={containerStyle}>
<FlatList
role='tablist'
ref={setFlatListRef}
data={sections}
renderItem={onRenderButton}
keyExtractor={item => item.name}
getItemLayout={(_data, index) => ({
length: itemHeight, offset: itemHeight * index, index,
})}
/>
</View>
);
};
export default SectionSelector;

View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import { ConfigScreenStyles } from '../configScreenStyles';
import Icon from '../../../Icon';
import BetaChip from '../../../BetaChip';
import { TouchableRipple, Text } from 'react-native-paper';
import { View } from 'react-native';
import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting';
interface Props {
selected: boolean;
section: SettingMetadataSection;
styles: ConfigScreenStyles;
onPress: ()=> void;
}
const SectionTab: React.FC<Props> = ({ styles, onPress, selected, section }) => {
const icon = Setting.sectionNameToIcon(section.name, AppType.Mobile);
const label = Setting.sectionNameToLabel(section.name);
const shortDescription = Setting.sectionMetadataToSummary(section);
const styleSheet = styles.styleSheet;
const titleStyle = selected ? styleSheet.sidebarSelectedButtonText : styleSheet.sidebarButtonMainText;
const isBeta = section.name === 'plugins';
const betaChip = isBeta ? <BetaChip size={10}/> : null;
return (
<TouchableRipple
key={section.name}
role='tab'
aria-selected={selected}
onPress={onPress}
>
<View
style={selected ? styleSheet.selectedSidebarButton : styleSheet.sidebarButton}
>
<Icon
name={icon}
accessibilityLabel={null}
style={styleSheet.sidebarIcon}
/>
<View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text
numberOfLines={1}
style={titleStyle}
>
{label}
</Text>
{betaChip}
</View>
<Text
style={styleSheet.sidebarButtonDescriptionText}
numberOfLines={1}
ellipsizeMode='tail'
>
{shortDescription ?? ''}
</Text>
</View>
</View>
</TouchableRipple>
);
};
export default SectionTab;

View File

@@ -0,0 +1,116 @@
import * as React from 'react';
import { AppType, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { ConfigScreenStyles } from '../configScreenStyles';
import { FlatList, View, ViewStyle } from 'react-native';
import { Text } from 'react-native-paper';
import { settingsSections } from '@joplin/lib/components/shared/config/config-shared';
import { _ } from '@joplin/lib/locale';
import SectionTab from './SectionTab';
interface Props {
styles: ConfigScreenStyles;
width: number|undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
settings: Record<string, any>;
selectedSectionName: string|null;
openSection: (sectionName: string)=> void;
}
type SectionDivider = { divider: boolean; label: string; name: string; source?: string };
const SectionSelector: FunctionComponent<Props> = props => {
type SectionListType = (SectionDivider|SettingMetadataSection)[];
const sections = useMemo((): SectionListType => {
const allSections = settingsSections({ device: AppType.Mobile, settings: props.settings });
const builtInSections = [];
const pluginSections = [];
for (const section of allSections) {
if (section.source === SettingSectionSource.Plugin) {
pluginSections.push(section);
} else {
builtInSections.push(section);
}
}
let result: SectionListType = builtInSections;
if (pluginSections.length > 0) {
result = result.concat([
{ label: _('Plugins'), name: 'plugins-divider', divider: true },
...pluginSections,
]);
}
return result;
}, [props.settings]);
const styles = props.styles.styleSheet;
const onRenderButton = ({ item }: { item: SettingMetadataSection|SectionDivider }) => {
const section = item;
const selected = props.selectedSectionName === section.name;
if ('divider' in item && item.divider) {
return (
<View style={styles.sidebarHeader}>
<Text variant='labelLarge' style={styles.sidebarHeaderText}>{item.label}</Text>
</View>
);
} else {
return (
<SectionTab
selected={selected}
section={section as SettingMetadataSection}
styles={props.styles}
onPress={() => props.openSection(section.name)}
/>
);
}
};
const [flatListRef, setFlatListRef] = useState<FlatList|null>(null);
useEffect(() => {
if (flatListRef && props.selectedSectionName) {
let selectedIndex = 0;
for (const section of sections) {
if (section.name === props.selectedSectionName) {
break;
}
selectedIndex ++;
}
flatListRef.scrollToIndex({
index: selectedIndex,
viewPosition: 0.5,
});
}
}, [props.selectedSectionName, flatListRef, sections]);
const containerStyle: ViewStyle = useMemo(() => ({
width: props.width,
maxWidth: props.width,
minWidth: props.width,
flex: 1,
}), [props.width]);
return (
<View style={containerStyle}>
<FlatList
role='tablist'
ref={setFlatListRef}
data={sections}
renderItem={onRenderButton}
keyExtractor={item => item.name}
onScrollToIndexFailed={({ index, averageItemLength }) => {
// Scrolling to a particular index can fail if the item at that index hasn't been rendered yet.
// This shouldn't happen often, so a guess should be sufficient.
flatListRef.scrollToOffset({ offset: (index + 0.5) * averageItemLength });
}}
/>
</View>
);
};
export default SectionSelector;

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

@@ -33,6 +33,8 @@ export interface ConfigScreenStyleSheet {
sidebarButtonMainText: TextStyle;
sidebarSelectedButtonText: TextStyle;
sidebarButtonDescriptionText: TextStyle;
sidebarHeader: ViewStyle;
sidebarHeaderText: TextStyle;
settingControl: TextStyle;
}
@@ -204,6 +206,18 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => {
fontWeight: 'bold',
},
sidebarButtonDescriptionText,
sidebarHeader: {
paddingLeft: 12,
height: sidebarButtonHeight / 2,
flexDirection: 'column',
justifyContent: 'center',
backgroundColor: theme.oddBackgroundColor,
},
sidebarHeaderText: {
color: theme.color,
fontWeight: 'bold',
fontSize: theme.fontSize,
},
});
return {

View File

@@ -0,0 +1,57 @@
import * as React from 'react';
import { Chip, ChipProps } from 'react-native-paper';
import { useMemo } from 'react';
import { connect } from 'react-redux';
import { AppState } from '../../../../../utils/types';
import { themeStyle } from '../../../../global-style';
type Props = {
themeId: number;
color?: string;
faded?: boolean;
onPress?: ()=> void;
icon?: string;
children: React.ReactNode;
};
const fadedStyle = { opacity: 0.87 };
const PluginChip: React.FC<Props> = props => {
const themeOverride = useMemo(() => {
const theme = themeStyle(props.themeId);
const foreground = props.color ?? theme.color;
const background = theme.backgroundColor;
return {
colors: {
secondaryContainer: background,
onSecondaryContainer: foreground,
primary: foreground,
outline: foreground,
onPrimary: foreground,
onSurfaceVariant: foreground,
},
};
}, [props.themeId, props.color]);
const accessibilityProps: Partial<ChipProps> = {};
if (!props.onPress) {
// Note: May have no effect until a future version of RN Paper.
// See https://github.com/callstack/react-native-paper/pull/4327
accessibilityProps.accessibilityRole = 'text';
}
return <Chip
theme={themeOverride}
style={props.faded ? fadedStyle : null}
mode='outlined'
{...accessibilityProps}
{...props}
/>;
};
export default connect((state: AppState) => {
return {
themeId: state.settings.theme,
};
})(PluginChip);

View File

@@ -2,10 +2,10 @@ import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import shim from '@joplin/lib/shim';
import * as React from 'react';
import { Alert, Linking, View, ViewStyle } from 'react-native';
import { View, ViewStyle } from 'react-native';
import { _ } from '@joplin/lib/locale';
import { PluginCallback } from '../utils/usePluginCallbacks';
import StyledChip from './StyledChip';
import PluginChip from './PluginChip';
import { themeStyle } from '../../../../global-style';
interface Props {
@@ -19,23 +19,6 @@ interface Props {
onShowPluginLog?: PluginCallback;
}
const onRecommendedPress = () => {
Alert.alert(
'',
_('The Joplin team has vetted this plugin and it meets our standards for security and performance.'),
[
{
text: _('Learn more'),
onPress: () => Linking.openURL('https://github.com/joplin/plugins/blob/master/readme/recommended.md'),
},
{
text: _('OK'),
},
],
{ cancelable: true },
);
};
const containerStyle: ViewStyle = {
flexDirection: 'row',
gap: 4,
@@ -54,43 +37,28 @@ const PluginChips: React.FC<Props> = props => {
if (!props.hasErrors) return null;
return (
<StyledChip
background={theme.backgroundColor2}
foreground={theme.colorError2}
<PluginChip
color={theme.colorError2}
icon='alert'
mode='flat'
onPress={() => props.onShowPluginLog({ item })}
>
{_('Error')}
</StyledChip>
</PluginChip>
);
};
const renderRecommendedChip = () => {
if (!props.item.manifest._recommended || !props.isCompatible) {
return null;
}
return <StyledChip
background={theme.searchMarkerBackgroundColor}
foreground={theme.searchMarkerColor}
icon='crown'
onPress={onRecommendedPress}
>{_('Recommended')}</StyledChip>;
};
const renderBuiltInChip = () => {
if (!props.item.builtIn) {
return null;
}
return <StyledChip icon='code-tags-check'>{_('Built-in')}</StyledChip>;
return <PluginChip icon='code-tags-check'>{_('Built-in')}</PluginChip>;
};
const renderIncompatibleChip = () => {
if (props.isCompatible) return null;
return (
<StyledChip
background={theme.backgroundColor3}
foreground={theme.color3}
<PluginChip
color={theme.color3}
icon='alert'
onPress={() => {
void shim.showMessageBox(
@@ -98,7 +66,7 @@ const PluginChips: React.FC<Props> = props => {
{ buttons: [_('OK')] },
);
}}
>{_('Incompatible')}</StyledChip>
>{_('Incompatible')}</PluginChip>
);
};
@@ -106,7 +74,7 @@ const PluginChips: React.FC<Props> = props => {
if (!props.isCompatible || !props.canUpdate) return null;
return (
<StyledChip>{_('Update available')}</StyledChip>
<PluginChip>{_('Update available')}</PluginChip>
);
};
@@ -114,21 +82,20 @@ const PluginChips: React.FC<Props> = props => {
if (props.item.enabled || !props.item.installed) {
return null;
}
return <StyledChip>{_('Disabled')}</StyledChip>;
return <PluginChip faded={true}>{_('Disabled')}</PluginChip>;
};
const renderInstalledChip = () => {
if (!props.showInstalledChip) {
return null;
}
return <StyledChip>{_('Installed')}</StyledChip>;
return <PluginChip faded={true}>{_('Installed')}</PluginChip>;
};
return <View style={containerStyle}>
{renderIncompatibleChip()}
{renderInstalledChip()}
{renderErrorsChip()}
{renderRecommendedChip()}
{renderBuiltInChip()}
{renderUpdatableChip()}
{renderDisabledChip()}

View File

@@ -0,0 +1,74 @@
import { _ } from '@joplin/lib/locale';
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
import * as React from 'react';
import IconButton from '../../../../IconButton';
import { Alert, Linking, StyleSheet } from 'react-native';
import { themeStyle } from '../../../../global-style';
import { useMemo } from 'react';
const onRecommendedPress = () => {
Alert.alert(
'',
_('The Joplin team has vetted this plugin and it meets our standards for security and performance.'),
[
{
text: _('Learn more'),
onPress: () => Linking.openURL('https://github.com/joplin/plugins/blob/master/readme/recommended.md'),
},
{
text: _('OK'),
},
],
{ cancelable: true },
);
};
interface Props {
themeId: number;
manifest: PluginManifest;
isCompatible: boolean;
}
const useStyles = (themeId: number) => {
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
container: {
opacity: 0.8,
},
wrapper: {
borderColor: theme.colorWarn,
borderWidth: 1,
borderRadius: 20,
justifyContent: 'center',
height: 32,
width: 32,
textAlign: 'center',
},
icon: {
fontSize: 14,
color: theme.colorWarn,
marginLeft: 'auto',
marginRight: 'auto',
},
});
}, [themeId]);
};
const RecommendedBadge: React.FC<Props> = props => {
const styles = useStyles(props.themeId);
if (!props.manifest._recommended || !props.isCompatible) return null;
return <IconButton
onPress={onRecommendedPress}
iconName='fas fa-crown'
containerStyle={styles.container}
contentWrapperStyle={styles.wrapper}
iconStyle={styles.icon}
themeId={props.themeId}
description={_('Recommended')}
/>;
};
export default RecommendedBadge;

View File

@@ -1,39 +0,0 @@
import * as React from 'react';
import { Chip, ChipProps } from 'react-native-paper';
import { useMemo } from 'react';
type Props = ({
foreground: string;
background: string;
}|{
foreground?: undefined;
background?: undefined;
}) & ChipProps;
const RecommendedChip: React.FC<Props> = props => {
const themeOverride = useMemo(() => {
if (!props.foreground) return {};
return {
colors: {
secondaryContainer: props.background,
onSecondaryContainer: props.foreground,
primary: props.foreground,
},
};
}, [props.foreground, props.background]);
const accessibilityProps: Partial<Props> = {};
if (!props.onPress) {
// Note: May have no effect until a future version of RN Paper.
// See https://github.com/callstack/react-native-paper/pull/4327
accessibilityProps.accessibilityRole = 'text';
}
return <Chip
theme={themeOverride}
{...accessibilityProps}
{...props}
/>;
};
export default RecommendedChip;

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