1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-02-07 08:10:38 +02:00

Compare commits

...

160 Commits

Author SHA1 Message Date
renovate[bot]
269260ac48 Update dependency python to 3.14 2026-02-04 18:05:35 +00:00
renovate[bot]
4346616cae Update dependency sharp to v0.34.5 (#14264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 18:02:14 +00:00
Laurent Cozic
ef646adafa Doc: Added mobile performance analysis report 2026-02-04 15:21:52 +00:00
Laurent Cozic
4ce47807b1 iOS 13.6.1 2026-02-04 15:13:08 +00:00
Laurent Cozic
9b0bc4d600 Chore: Limit build cleaning function to just Android 2026-02-04 15:13:08 +00:00
renovate[bot]
91b8e4d34d Update dependency style-to-js to v1.1.19 (#14244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 14:51:04 +00:00
renovate[bot]
9e9bd662dc Update dependency nan to v2.23.1 (#14263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 14:48:21 +00:00
Laurent Cozic
defbbd5d72 Android 3.6.12 2026-02-04 14:45:02 +00:00
Laurent Cozic
b2c9dd40dc Chore: Fixed Android release script 2026-02-04 14:43:56 +00:00
Laurent Cozic
a9049111e4 Chore: Add a marker to tell when the application is ready on mobile 2026-02-04 12:40:51 +00:00
renovate[bot]
55f642c625 Update dependency @types/serviceworker to v0.0.165 (#14250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 11:29:26 +00:00
Henry Heino
dee9ec3495 Chore: Testing: Give the plugin panel test more time to pass (#14199) 2026-02-04 10:10:01 +00:00
Henry Heino
97bf020150 Chore: Mobile: Migrate folder screen to TypeScript (#14200) 2026-02-04 10:09:35 +00:00
Henry Heino
2cb2680a5a All: Sync: Make resource processing in read-only shares more reliable (#14204) 2026-02-04 10:08:29 +00:00
Henry Heino
242c6ec3b8 Chore: Sync fuzzer: Don't attempt to publish read-only notes (#14205) 2026-02-04 10:08:14 +00:00
Henry Heino
ed0b1ae390 Desktop: Fixes #14216: Fix undo/redo menu items in the Rich Text and Markdown editors (#14218) 2026-02-04 10:07:34 +00:00
Henry Heino
de29e4ff92 Docs: Update SECURITY.md to suggest using GitHub private vulnerability reporting (#14221) 2026-02-04 10:03:36 +00:00
Henry Heino
9d96e31b83 Desktop: Fixes #14210: OneNote importer: Skip importing ink when ID lookup fails (#14230) 2026-02-04 10:03:28 +00:00
Henry Heino
aad460e9a1 Chore: Fixes #14222: Retry failing editor test in CI (#14231) 2026-02-04 10:03:11 +00:00
Henry Heino
00248a9177 Mobile: Upgrade to React Native 0.81 (#14232) 2026-02-04 10:03:02 +00:00
Henry Heino
af2926b634 Desktop: OneNote import: Fix onepkg import stops after the first section fails to import (#14246)
Co-authored-by: Himanshu <h-jangra@users.noreply.github.com>
2026-02-04 09:59:17 +00:00
Henry Heino
916ed9bbfb Chore: Desktop: Fix warning logged when opening the note viewer (#14247) 2026-02-04 09:59:01 +00:00
Laurent Cozic
b32015864e All: Add support for FrontMatter block rendering in notes (#14256) 2026-02-04 09:58:12 +00:00
Henry Heino
8939ef1c19 Desktop,Mobile: Resolves #9745: Markdown: Allow specifying the start/end of audio, videos, and PDFs (#14257) 2026-02-04 09:56:05 +00:00
Henry Heino
31bba39ae9 Chore: Fix HTML to Markdown conversion fails for certain rendered code block HTML (#14259) 2026-02-04 09:55:50 +00:00
Henry Heino
9dc49f0c24 Chore: Sync fuzzer: Fix "moveFolderToToplevel" command (#14261) 2026-02-04 09:55:03 +00:00
Henry Heino
02dfef11aa Desktop: Fixes #13540: Improve context menu handling in secondary windows (#14262) 2026-02-04 09:53:09 +00:00
renovate[bot]
c278b45c78 Update dependency nodejs to v24.8.0 (#14229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 23:54:22 +00:00
renovate[bot]
0dafd21db0 Update dependency electron-updater to v6.6.8 (#14239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 23:53:58 +00:00
Sebastian
490d35919c All: Translation: Update de_DE.po (#14242) 2026-02-01 06:24:59 -05:00
Nick
4c1ca5480d All: Translation: Update sv.po (#14241) 2026-02-01 06:20:55 -05:00
Joplin Bot
d414c6354a Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-02-01 02:36:23 +00:00
rnbastos
7651d8e3c4 All: Translation: Update pt_BR.po (#14238) 2026-01-31 17:23:25 -05:00
renovate[bot]
d5c72c13cb Update dependency @types/serviceworker to v0.0.164 (#14237)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-31 09:49:32 +00:00
renovate[bot]
4377634e7b Update dependency esbuild to v0.25.12 (#14236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-31 04:59:05 +00:00
renovate[bot]
69ec5c7f86 Update dependency @types/serviceworker to v0.0.163 (#14234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-30 17:50:22 +00:00
renovate[bot]
f02b0f48d8 Update dependency react-refresh to v0.18.0 (#14233)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-30 14:33:32 +01:00
renovate[bot]
4d77c1385f Update dependency sass to v1.93.3 (#14228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-29 16:51:56 +00:00
renovate[bot]
c83f9ddeac Update dependency dayjs to v1.11.19 (#14227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-29 14:49:50 +00:00
renovate[bot]
1b9c11df7b Update dependency @types/serviceworker to v0.0.162 (#14225)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-29 09:28:36 +00:00
Nick
333a8723e8 All: Translation: Update sv.po (#14220) 2026-01-28 18:14:28 -05:00
Laurent Cozic
e030c8271d Chore: Try to fix app-desktop tests on local 2026-01-28 12:55:08 +00:00
renovate[bot]
560bc31445 Update dependency gettext-extractor to v4.0.1 (#14217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-28 01:45:26 +00:00
renovate[bot]
c71aeb74b2 Update dependency gettext-extractor to v4 (#14213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 23:36:41 +00:00
renovate[bot]
ffaf2acb66 Update dependency @rollup/plugin-replace to v6.0.3 (#14212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 18:16:02 +00:00
renovate[bot]
f442f1fb23 Update dependency @types/serviceworker to v0.0.161 (#14206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 11:12:24 +00:00
renovate[bot]
81a1451820 Update dependency react-native-safe-area-context to v5.6.2 (#14202)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 02:16:37 +00:00
renovate[bot]
b3a3d71461 Update dependency @react-native-community/datetimepicker to v8.4.7 (#14191)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 22:47:00 +00:00
bwat47
1db38c3232 Desktop, Mobile: Fixes #13933: Markdown editor: Scroll jumps in notes with many inline images (#13955) 2026-01-26 15:21:08 +00:00
Fardin96
42e645eb70 Mobile: Fixes #13243: Align tag search-input-clear behavior across input methods (#14042) 2026-01-26 15:12:06 +00:00
mrjo118
3860f44d06 Mobile: Fixes #14153: Prevent the back button sometimes disappearing when switching between editors (#14164) 2026-01-26 15:09:37 +00:00
Henry Heino
4df0f8668d Desktop,Mobile: Resolves #14158: Markdown Editor: Make code block highlighting closer to the viewer (#14168) 2026-01-26 15:06:37 +00:00
Henry Heino
306d0fddd8 Desktop: OneNote import: Import invalid attachments as empty attachments (#14177) 2026-01-26 15:06:24 +00:00
Henry Heino
56d12b28f2 All: Unlinked resource deletion: Fix resources attached only via reference links are auto-deleted (#14178) 2026-01-26 15:06:15 +00:00
Henry Heino
6c5ea4872a Desktop,Mobile: Markdown editor: Fix error logged in "hide markdown" mode for certain markup (#14179) 2026-01-26 15:06:06 +00:00
Henry Heino
9856e8ae93 Chore: Sync fuzzer: Test adding, removing resources from notes (#14185) 2026-01-26 15:05:50 +00:00
Henry Heino
5712da4c0f Desktop,Mobile: Fixes #14009: Markdown editor: Upgrade most CodeMirror dependencies (#14186) 2026-01-26 15:04:35 +00:00
Henry Heino
4f7ee56444 Desktop: Fixes #13793: Make conflicts caused by resource duplication less likely (#14188) 2026-01-26 15:04:26 +00:00
Laurent Cozic
8e2b6ca296 Chore: Improved performance log consistency, and log to console on Android production too 2026-01-26 14:47:19 +00:00
Laurent Cozic
0172bb0ad8 Chore: Fix error in release-android script when the main apk is not built 2026-01-26 14:46:33 +00:00
Laurent Cozic
1d38e443ba Chore: Do not create GitHub release when there is no APK to publish 2026-01-26 13:58:57 +00:00
Laurent Cozic
5ad19b7261 Chore: Make Android app profileable 2026-01-26 13:46:27 +00:00
Arda Kılıçdağı
70293478a2 All: Translation: Update tr_TR.po (#14193) 2026-01-25 18:14:39 -05:00
custiq
3aaa20254f All: Translation: Update fi_FI.po (#14189) 2026-01-24 23:46:32 -05:00
renovate[bot]
42c248f7ca Update dependency @types/serviceworker to v0.0.160 (#14190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 09:11:54 +00:00
Joplin Bot
ac1e94a8df Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-23 06:47:06 +00:00
renovate[bot]
daff4496cf Update dependency turndown to v7.2.2 (#14181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 02:15:02 +00:00
Joplin Bot
1e00078228 Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-23 01:56:25 +00:00
renovate[bot]
03a1de9370 Update dependency @rollup/plugin-commonjs to v28.0.9 (#14175)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 17:46:44 +00:00
renovate[bot]
55ef256c65 Update dependency rate-limiter-flexible to v7.4.0 (#14174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 17:46:38 +00:00
renovate[bot]
6d115db16f Update dependency @types/yargs to v17.0.34 (#14173)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 16:25:50 +00:00
renovate[bot]
5853031fde Update dependency @types/serviceworker to v0.0.159 (#14172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 09:05:01 +00:00
renovate[bot]
47db2ae962 Update dependency @types/nodemailer to v6.4.21 (#14171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 09:02:54 +00:00
Laurent Cozic
b960a2a8b0 Doc: Updated JSB contact link 2026-01-21 09:27:55 +00:00
renovate[bot]
fcaa7d2a98 Update dependency lint-staged to v16.2.6 (#14165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-20 18:18:59 +00:00
renovate[bot]
99284ae135 Update dependency lint-staged to v16.2.0 (#14162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-20 13:20:03 +00:00
Joplin Bot
66ae58c81b Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-19 18:43:09 +00:00
Laurent Cozic
484d6a866d Doc: Remove "(Pre-release)" marker from Android changelog since all versions are pre-releases 2026-01-19 18:03:05 +00:00
Laurent Cozic
b45fd09e38 Merge branch 'release-3.5' into dev 2026-01-19 16:44:41 +00:00
Laurent Cozic
903a369c13 Android 3.5.9 2026-01-19 16:43:41 +00:00
Laurent Cozic
1fb79315e4 Chore: lock files 2026-01-19 16:13:04 +00:00
Henry Heino
4dc021b523 Android: Remove unnecessary READ_PHONE_STATE permission (#14157) 2026-01-19 16:04:56 +00:00
Joplin Bot
bbb4b46dd9 Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-19 02:01:31 +00:00
renovate[bot]
063dc46f50 Update dependency dotenv to v17.2.3 (#14155)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 00:02:36 +00:00
renovate[bot]
aa400b52be Update dependency short-uuid to v5 (#14156)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 00:02:26 +00:00
renovate[bot]
be7de2f08a Update dependency dotenv to v17.2.2 (#14145)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-18 22:01:23 +00:00
renovate[bot]
f8a129e4dc Update dependency npm-package-json-lint to v9 (#14146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-18 22:01:11 +00:00
Laurent Cozic
c5d9646908 Desktop release v3.6.2 2026-01-18 11:33:16 +00:00
Henry Heino
876ec80911 Desktop: Fixes #14084: .onepkg import: Fix Unicode issues, support Linux and MacOS (#14094)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:31:48 +00:00
mrjo118
4051f88ce7 Chore: Fix intermittent Synchronizer.revisions test failure (#14096)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:31:42 +00:00
Laurent Cozic
f194c111e4 All: Fixes #14144: Application crashes when profile database has been analyzed 2026-01-18 11:30:05 +00:00
Henry Heino
e386246bc9 Chore: Sync fuzzer: Improve error logging (#14108)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:29:32 +00:00
Henry Heino
292b269f1d Desktop: Resolves #14086: Accessibility: Include accessibility information in exported PDFs (#14111)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:29:25 +00:00
renovate[bot]
b2fc43da2b Update dependency short-uuid to v4.2.2 (#14114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:29:17 +00:00
Henry Heino
4a23a1ed3e Desktop: Fixes #14092: Built-in plugins: Upgrade Freehand Drawing to v4.3.0 (#14123)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:29:07 +00:00
Henry Heino
c8878a18bf Desktop, Mobile: Editor: Inline rendering: Render inline HTML (colorized text, superscript, subscript, strikethrough) (#14133) 2026-01-18 11:28:15 +00:00
Henry Heino
340fba7af5 Server: Fixes #14107: Fix warning when unsharing folder (#14134) 2026-01-18 11:25:52 +00:00
Henry Heino
271c4f4a2a Server: Fixes #14131: Allow changing the password for the admin account when SAML is enabled (#14135) 2026-01-18 11:25:38 +00:00
Henry Heino
c9dba20f59 Chore: Sync fuzzer: Allow specifying a set of initial actions (#14136)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-18 11:25:07 +00:00
renovate[bot]
b474cc206a Update dependency dotenv to v17 (#14138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-18 11:24:46 +00:00
Milo Ivir
9d4df8cc6e All: Translation: Update hr_HR.po (#14140) 2026-01-17 20:57:39 -05:00
Joplin Bot
a4ddfe1f58 Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-17 18:38:35 +00:00
renovate[bot]
7d15215e66 Update dependency react-native-device-info to v14.1.1 (#14132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 14:23:02 +00:00
Laurent Cozic
449555c8e9 Desktop release v3.5.12 2026-01-17 11:21:02 +00:00
Laurent Cozic
5b74e206ed Desktop release v3.6.1 2026-01-17 11:19:55 +00:00
Laurent Cozic
9873d02b0b Chore: Setup new release 3.6 2026-01-17 11:19:43 +00:00
Laurent Cozic
57b7d98d8a Merge branch 'release-3.5' into dev 2026-01-17 11:18:39 +00:00
Laurent Cozic
f075b561a2 All: Add more error information when the profile is corrupted 2026-01-17 11:17:43 +00:00
renovate[bot]
483d051de0 Update dependency rate-limiter-flexible to v7.3.2 (#14130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 15:59:43 +00:00
renovate[bot]
106cd2778f Update dependency rate-limiter-flexible to v7.3.1 (#14128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 13:57:49 +00:00
Eric Duarte
c3aea2db80 All: Translation: Update ca.po (#14129) 2026-01-16 08:10:45 -05:00
Liffindra Angga Zaaldian
3f067b0f77 All: Translation: Update id_ID.po (#14127)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-16 08:08:49 -05:00
Laurent Cozic
15cf025bc2 All: Resolves #14106: Improve Fountain notes exported as PDF (#14120) 2026-01-16 11:30:54 +00:00
Henry Heino
4677586e3b Desktop: Rich Text Editor: Fix cut, copy, paste, and select all menu items (#14125) 2026-01-16 11:30:39 +00:00
Laurent Cozic
b8c5b7a153 Doc: Added Contribution Scope Policy 2026-01-16 10:19:32 +00:00
renovate[bot]
e46e634c2e Update dependency style-to-js to v1.1.18 (#14118)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-16 09:44:18 +00:00
Henry Heino
b3cf4e5a35 Chore: Fix CI (#14124) 2026-01-16 09:43:30 +00:00
Laurent Cozic
8589e10d6e Chore: Trying to fix CI 2026-01-15 14:23:00 +00:00
renovate[bot]
18942f0d6a Update dependency babel-plugin-react-native-web to v0.21.2 (#14104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 13:25:14 +00:00
Eric Duarte
3be354cdcb All: Translation: Update es_ES.po (#14117) 2026-01-15 08:22:26 -05:00
renovate[bot]
0575f1aa3e Update dependency react-native-web to v0.21.2 (#14113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 05:12:54 +00:00
renovate[bot]
caa9baa460 Update dependency react-native-localize to v3.5.4 (#14112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 02:04:45 +00:00
renovate[bot]
b5284804d8 Update dependency qrcode to v1.5.4 (#14109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 00:07:22 +00:00
renovate[bot]
6053b4296c Update dependency esbuild to v0.25.11 (#14101)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 13:56:24 +00:00
renovate[bot]
615fec1d2c Update dependency @rollup/plugin-node-resolve to v16.0.3 (#14100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 13:54:35 +00:00
Laurent Cozic
0bbcd9a59b All: Add support for external embeds, eg. YouTube videos (#14012) 2026-01-14 13:52:17 +00:00
renovate[bot]
6931b32f17 Update dependency @rollup/plugin-commonjs to v28.0.8 (#14099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 12:09:18 +00:00
renovate[bot]
17ac501ddb Update dependency @types/serviceworker to v0.0.158 (#14060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-14 09:32:41 +00:00
renovate[bot]
94161c5f93 Update dependency @types/react to v18.3.26 (#14050)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2026-01-14 09:32:34 +00:00
Jozef Gaal
196255e960 All: Translation: Update sk_SK.po (#14095) 2026-01-13 19:55:13 -05:00
Self Not Found
f936390ee4 All: Translation: Update zh_CN.po (#14091) 2026-01-13 16:48:20 -05:00
Laurent Cozic
5638c4b812 Chore: Fixed various typo and grammar mistakes 2026-01-13 16:28:24 +00:00
Linkosred
4222caa423 Docs : Add video tutorial link for several pages of the documentation (#14068) 2026-01-13 16:20:52 +00:00
Henry Heino
bc705acc5c Windows: Fixes #13430: Experimental auto-updater: Fix application crash on update failure (#14083) 2026-01-13 16:19:51 +00:00
Laurent Cozic
f1c968c19a Chore: Remove usage of watchman when running Jest tests (#14087) 2026-01-13 15:40:53 +00:00
Laurent Cozic
26c5a6181e Chore: Retry Apple Silicon test build when it fails (#14088) 2026-01-13 15:40:36 +00:00
Laurent Cozic
a3bf0cfdeb Server: Add support for MFA (#14081) 2026-01-13 14:14:46 +00:00
Joplin Bot
606b397326 Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-13 01:53:11 +00:00
krevad
fbd157283d All: Translation: Update sv.po (#14082) 2026-01-12 19:38:43 -05:00
Laurent Cozic
2e879f65fc Chore: Fixed Markdown filename 2026-01-12 19:32:54 +00:00
Joplin Bot
c727156a46 Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-12 18:43:05 +00:00
Laurent Cozic
4e31f1918d Doc: Added instructions on how to create a custom profile for deployments 2026-01-12 16:18:17 +00:00
Laurent Cozic
a1cdf67779 Chore: Also release pkg files for macOS 2026-01-12 16:08:56 +00:00
Laurent Cozic
5cb1db197f Doc: Add release notes 3.5 2026-01-12 15:12:18 +00:00
Joplin Bot
05c3065c72 Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-10 12:49:51 +00:00
Laurent Cozic
25a5be09bf Merge branch 'release-3.5' into dev 2026-01-10 10:16:56 +00:00
Laurent Cozic
f0a3f73ddb iOS 13.5.3 2026-01-10 10:09:40 +00:00
Laurent Cozic
1bb5d9ade5 Android 3.5.8 2026-01-10 10:09:18 +00:00
Laurent Cozic
e75875c1b0 Desktop release v3.5.11 2026-01-10 10:01:41 +00:00
mrjo118
cce4b76e3f Mobile: Fixes #13544: Fixed keyboard input issue in note title (#14070) 2026-01-10 09:53:21 +00:00
Henry Heino
b310bfd0c2 iOS: Fixes #14063: Fix icon rendering (#14071) 2026-01-10 09:44:23 +00:00
Henry Heino
e19e1ac040 Desktop: OneNote importer: Simplify error report (#14074) 2026-01-10 09:44:09 +00:00
ERYpTION
3bba2f6b2a All: Translation: Update da_DK.po (#14073) 2026-01-09 17:22:08 -05:00
summoner
ca9addcda0 ALL: Translation: Update hu-HU.po (#14069) 2026-01-09 16:16:33 -05:00
Nick
c42a49c1cf All: Translation: Update sv.po (#14064) 2026-01-09 15:58:20 -05:00
Laurent Cozic
a1e056670d Chore: Ignore .watchman-cookie- files 2026-01-09 17:45:27 +00:00
Laurent Cozic
6d7a70c21a Chore: Update translations 2026-01-09 10:06:42 +00:00
Henry Heino
14fd3c66c1 Chore: Fix CI (#14061) 2026-01-09 09:26:17 +00:00
Joplin Bot
376f44a0ce Doc: Auto-update documentation
Auto-updated using release-website.sh
2026-01-09 01:56:03 +00:00
Laurent Cozic
4d81ee4c7f Chore: Exclude katex and mermaid from Renovate 2026-01-08 19:44:50 +00:00
1040 changed files with 51094 additions and 34732 deletions

View File

@@ -92,6 +92,7 @@ readme/
packages/react-native-vosk/lib/
packages/lib/countable/Countable.js
packages/onenote-converter/renderer/pkg/*
packages/whisper-voice-typing/lib/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
packages/app-cli/app/LinkSelector.js
@@ -468,6 +469,8 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/duplicateNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/editAlarm.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/globalRedo.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/globalUndo.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/importFrom.js
@@ -511,6 +514,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNotesSortOrderR
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/togglePerFolderSortOrder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleSideBar.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleVisiblePanes.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/utils/canUseNativeUndo.js
packages/app-desktop/gui/WindowCommandsAndDialogs/types.js
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/appDialogs.js
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/showFolderPicker.js
@@ -691,6 +695,7 @@ packages/app-mobile/components/FeedbackBanner.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/KeyboardAvoidingView.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.js
@@ -869,6 +874,7 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.test.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/folder.js
packages/app-mobile/components/screens/status.js
packages/app-mobile/components/screens/tags.js
packages/app-mobile/components/side-menu-content.js
@@ -969,6 +975,7 @@ packages/app-mobile/utils/hooks/useSafeAreaPadding.js
packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initReact.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
@@ -1045,6 +1052,8 @@ packages/editor/CodeMirror/extensions/links/utils/getUrlAtPosition.js
packages/editor/CodeMirror/extensions/links/utils/openLink.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.test.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.js
packages/editor/CodeMirror/extensions/markdownFrontMatterExtension.test.js
packages/editor/CodeMirror/extensions/markdownFrontMatterExtension.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.test.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.js
packages/editor/CodeMirror/extensions/markdownMathExtension.test.js
@@ -1061,6 +1070,8 @@ packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
packages/editor/CodeMirror/extensions/rendering/replaceFormatCharacters.js
packages/editor/CodeMirror/extensions/rendering/replaceInlineHtml.test.js
packages/editor/CodeMirror/extensions/rendering/replaceInlineHtml.js
packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
@@ -1101,6 +1112,7 @@ packages/editor/CodeMirror/utils/getSearchState.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handleLinkEditRequests.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/htmlNodeInfo.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
@@ -1704,6 +1716,7 @@ packages/lib/testing/share/mockShareService.js
packages/lib/testing/syncTargetUtils.js
packages/lib/testing/test-utils-synchronizer.js
packages/lib/testing/test-utils.js
packages/lib/testing/waitFor.js
packages/lib/theme.js
packages/lib/themes/aritimDark.js
packages/lib/themes/dark.js
@@ -1805,8 +1818,11 @@ packages/renderer/MdToHtml/renderMedia.js
packages/renderer/MdToHtml/rules/abc.js
packages/renderer/MdToHtml/rules/checkbox.js
packages/renderer/MdToHtml/rules/code_inline.js
packages/renderer/MdToHtml/rules/externalEmbed.js
packages/renderer/MdToHtml/rules/fence.js
packages/renderer/MdToHtml/rules/fountain.js
packages/renderer/MdToHtml/rules/frontmatter.test.js
packages/renderer/MdToHtml/rules/frontmatter.js
packages/renderer/MdToHtml/rules/highlight_keywords.js
packages/renderer/MdToHtml/rules/html_image.js
packages/renderer/MdToHtml/rules/image.js
@@ -1840,22 +1856,29 @@ packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionRunner.js
packages/tools/fuzzer/ActionTracker.js
packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/doRandomAction.js
packages/tools/fuzzer/model/FolderRecord.js
packages/tools/fuzzer/model/ResourceRecord.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/ProgressBar.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/diffSortedStringArrays.test.js
packages/tools/fuzzer/utils/diffSortedStringArrays.js
packages/tools/fuzzer/utils/extractResourceIds.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/hangingIndent.js
packages/tools/fuzzer/utils/logDiffDebug.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/randomId.test.js
packages/tools/fuzzer/utils/randomId.js
packages/tools/fuzzer/utils/randomString.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
@@ -1927,4 +1950,6 @@ packages/tools/website/utils/pressCarousel.js
packages/tools/website/utils/processTranslations.js
packages/tools/website/utils/render.js
packages/tools/website/utils/types.js
packages/whisper-voice-typing/src/index.js
packages/whisper-voice-typing/src/specs/Whisper.nitro.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@@ -28,7 +28,7 @@ jobs:
# See github-action-main.yml for explanation
- uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.14'
- name: Set Publish Flag
run: |
@@ -48,6 +48,7 @@ jobs:
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
PUBLISH_ENABLED: ${{ env.PUBLISH_ENABLED }}
@@ -57,25 +58,38 @@ jobs:
yarn install
cd packages/app-desktop
npm pkg set 'build.mac.artifactName'='${productName}-${version}-${arch}.${ext}'
npm pkg delete 'build.mac.target'
npm pkg set 'build.mac.target[0].target'='dmg'
npm pkg set 'build.mac.target[0].arch[0]'='arm64'
npm pkg set 'build.mac.target[1].target'='zip'
npm pkg set 'build.mac.target[1].arch[0]'='arm64'
if [[ "$PUBLISH_ENABLED" == "true" ]]; then
echo "Building and publishing desktop application..."
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn dist --mac --arm64
# Only enable pkg build in the main repository CI. As of 01/15/2026, pkg
# build fails when running on external pull requests.
if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then
npm pkg set 'build.mac.target[2].target'='pkg'
npm pkg set 'build.mac.target[2].arch[0]'='arm64'
fi
yarn modifyReleaseAssets --repo="$GH_REPO" --tag="$GIT_TAG_NAME" --token="$GITHUB_TOKEN"
else
echo "Building but *not* publishing desktop application..."
build_dist() {
if [[ "$PUBLISH_ENABLED" == "true" ]]; then
echo "Building and publishing desktop application..."
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn dist --mac --arm64
# We also want to disable signing the app in this case, because
# it doesn't work and we don't need it.
# https://www.electron.build/code-signing#how-to-disable-code-signing-during-the-build-process-on-macos
yarn modifyReleaseAssets --repo="$GH_REPO" --tag="$GIT_TAG_NAME" --token="$GITHUB_TOKEN"
else
echo "Building but *not* publishing desktop application..."
export CSC_IDENTITY_AUTO_DISCOVERY=false
npm pkg set 'build.mac.identity'=null --json
# We also want to disable signing the app in this case, because
# it doesn't work and we don't need it.
# https://www.electron.build/code-signing#how-to-disable-code-signing-during-the-build-process-on-macos
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn dist --mac --arm64 --publish=never
fi
export CSC_IDENTITY_AUTO_DISCOVERY=false
npm pkg set 'build.mac.identity'=null --json
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn dist --mac --arm64 --publish=never
fi
}
build_dist || build_dist

View File

@@ -72,4 +72,4 @@ runs:
# Ref: https://github.com/nodejs/node-gyp/issues/2869
- uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.14'

27
.gitignore vendored
View File

@@ -53,6 +53,7 @@ lerna-debug.log
docs/**/*.mustache
.idea
/readme/i18n
.watchman-cookie-*
# Yarn stuff
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
@@ -441,6 +442,8 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/duplicateNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/editAlarm.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/globalRedo.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/globalUndo.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/importFrom.js
@@ -484,6 +487,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNotesSortOrderR
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/togglePerFolderSortOrder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleSideBar.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleVisiblePanes.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/utils/canUseNativeUndo.js
packages/app-desktop/gui/WindowCommandsAndDialogs/types.js
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/appDialogs.js
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/showFolderPicker.js
@@ -664,6 +668,7 @@ packages/app-mobile/components/FeedbackBanner.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/KeyboardAvoidingView.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.js
@@ -842,6 +847,7 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.test.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/folder.js
packages/app-mobile/components/screens/status.js
packages/app-mobile/components/screens/tags.js
packages/app-mobile/components/side-menu-content.js
@@ -942,6 +948,7 @@ packages/app-mobile/utils/hooks/useSafeAreaPadding.js
packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initReact.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
@@ -1018,6 +1025,8 @@ packages/editor/CodeMirror/extensions/links/utils/getUrlAtPosition.js
packages/editor/CodeMirror/extensions/links/utils/openLink.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.test.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.js
packages/editor/CodeMirror/extensions/markdownFrontMatterExtension.test.js
packages/editor/CodeMirror/extensions/markdownFrontMatterExtension.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.test.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.js
packages/editor/CodeMirror/extensions/markdownMathExtension.test.js
@@ -1034,6 +1043,8 @@ packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
packages/editor/CodeMirror/extensions/rendering/replaceFormatCharacters.js
packages/editor/CodeMirror/extensions/rendering/replaceInlineHtml.test.js
packages/editor/CodeMirror/extensions/rendering/replaceInlineHtml.js
packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
@@ -1074,6 +1085,7 @@ packages/editor/CodeMirror/utils/getSearchState.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handleLinkEditRequests.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/htmlNodeInfo.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
@@ -1677,6 +1689,7 @@ packages/lib/testing/share/mockShareService.js
packages/lib/testing/syncTargetUtils.js
packages/lib/testing/test-utils-synchronizer.js
packages/lib/testing/test-utils.js
packages/lib/testing/waitFor.js
packages/lib/theme.js
packages/lib/themes/aritimDark.js
packages/lib/themes/dark.js
@@ -1778,8 +1791,11 @@ packages/renderer/MdToHtml/renderMedia.js
packages/renderer/MdToHtml/rules/abc.js
packages/renderer/MdToHtml/rules/checkbox.js
packages/renderer/MdToHtml/rules/code_inline.js
packages/renderer/MdToHtml/rules/externalEmbed.js
packages/renderer/MdToHtml/rules/fence.js
packages/renderer/MdToHtml/rules/fountain.js
packages/renderer/MdToHtml/rules/frontmatter.test.js
packages/renderer/MdToHtml/rules/frontmatter.js
packages/renderer/MdToHtml/rules/highlight_keywords.js
packages/renderer/MdToHtml/rules/html_image.js
packages/renderer/MdToHtml/rules/image.js
@@ -1813,22 +1829,29 @@ packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionRunner.js
packages/tools/fuzzer/ActionTracker.js
packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/doRandomAction.js
packages/tools/fuzzer/model/FolderRecord.js
packages/tools/fuzzer/model/ResourceRecord.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/ProgressBar.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/diffSortedStringArrays.test.js
packages/tools/fuzzer/utils/diffSortedStringArrays.js
packages/tools/fuzzer/utils/extractResourceIds.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/hangingIndent.js
packages/tools/fuzzer/utils/logDiffDebug.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/randomId.test.js
packages/tools/fuzzer/utils/randomId.js
packages/tools/fuzzer/utils/randomString.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
@@ -1900,5 +1923,7 @@ packages/tools/website/utils/pressCarousel.js
packages/tools/website/utils/processTranslations.js
packages/tools/website/utils/render.js
packages/tools/website/utils/types.js
packages/whisper-voice-typing/src/index.js
packages/whisper-voice-typing/src/specs/Whisper.nitro.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@@ -5,6 +5,7 @@
"exceptions": [
"@joplin/editor",
"@joplin/fork-htmlparser2",
"@joplin/whisper-voice-typing",
"@joplin/fork-sax",
"@joplin/fork-uslug",
"@joplin/htmlpack",

View File

@@ -0,0 +1,21 @@
# Add a minSdkVersion to prevent the dangerous READ_PHONE_STATE
# permission from being added.
# See:
# - Upstream issue report: https://github.com/oblador/react-native-vector-icons/issues/1861
# - About the permission: https://developer.android.com/reference/android/Manifest.permission#READ_PHONE_STATE
# - StackOverflow post with discussion and alternate workarounds: https://stackoverflow.com/questions/39668549/why-has-the-read-phone-state-permission-been-added
diff --git a/android/build.gradle b/android/build.gradle
index a16b4ad6d1871cf5cf73ef7ebeaf8bd4d662b134..9871afb5fbf8e687370e08f54d884ecd7dde7e7c 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -37,6 +37,10 @@ android {
}
compileSdkVersion safeExtGet('compileSdkVersion', 31)
+
+ defaultConfig {
+ minSdkVersion safeExtGet('minSdkVersion', 24)
+ }
}
dependencies {

View File

@@ -0,0 +1,21 @@
# Add a minSdkVersion to prevent the dangerous READ_PHONE_STATE
# permission from being added.
# See:
# - Upstream issue report: https://github.com/oblador/react-native-vector-icons/issues/1861
# - About the permission: https://developer.android.com/reference/android/Manifest.permission#READ_PHONE_STATE
# - StackOverflow post with discussion and alternate workarounds: https://stackoverflow.com/questions/39668549/why-has-the-read-phone-state-permission-been-added
diff --git a/android/build.gradle b/android/build.gradle
index d42bd23123644cc324051e9c7ec4635de286315a..640996df60fe7769f69b30b35f771eb9cf0b75d4 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -37,6 +37,10 @@ android {
}
compileSdkVersion safeExtGet('compileSdkVersion', 31)
+
+ defaultConfig {
+ minSdkVersion safeExtGet('minSdkVersion', 24)
+ }
}
dependencies {

View File

@@ -0,0 +1,21 @@
# Add a minSdkVersion to prevent the dangerous READ_PHONE_STATE
# permission from being added.
# See:
# - Upstream issue report: https://github.com/oblador/react-native-vector-icons/issues/1861
# - About the permission: https://developer.android.com/reference/android/Manifest.permission#READ_PHONE_STATE
# - StackOverflow post with discussion and alternate workarounds: https://stackoverflow.com/questions/39668549/why-has-the-read-phone-state-permission-been-added
diff --git a/android/build.gradle b/android/build.gradle
index 170ec0ff9befe0f9155aaf5e1b84133cfd87be99..e6a0ab4a019ee67c5af7761ae8bb35f18b05c590 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -37,6 +37,10 @@ android {
}
compileSdkVersion safeExtGet('compileSdkVersion', 31)
+
+ defaultConfig {
+ minSdkVersion safeExtGet('minSdkVersion', 24)
+ }
}
dependencies {

View File

@@ -0,0 +1,21 @@
# Add a minSdkVersion to prevent the dangerous READ_PHONE_STATE
# permission from being added.
# See:
# - Upstream issue report: https://github.com/oblador/react-native-vector-icons/issues/1861
# - About the permission: https://developer.android.com/reference/android/Manifest.permission#READ_PHONE_STATE
# - StackOverflow post with discussion and alternate workarounds: https://stackoverflow.com/questions/39668549/why-has-the-read-phone-state-permission-been-added
diff --git a/android/build.gradle b/android/build.gradle
index 3b22f9de66795ee01dbaa29655727ee7ddba3cc8..325daa88d33f066b3826e5031ce281793710af2d 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -37,6 +37,10 @@ android {
}
compileSdkVersion safeExtGet('compileSdkVersion', 31)
+
+ defaultConfig {
+ minSdkVersion safeExtGet('minSdkVersion', 24)
+ }
}
dependencies {

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 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>Mon, 22 Sep 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.4]]></title><description><![CDATA[<p>Joplin 3.4 includes many bug fixes and improvements, with a focus on the mobile app.</p>
<?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>Sun, 11 Jan 2026 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.5]]></title><description><![CDATA[<h2>Improvements across desktop and mobile<a name="improvements-across-desktop-and-mobile" href="#improvements-across-desktop-and-mobile" class="heading-anchor">🔗</a></h2>
<h3>More stable and consistent Markdown editing<a name="more-stable-and-consistent-markdown-editing" href="#more-stable-and-consistent-markdown-editing" class="heading-anchor">🔗</a></h3>
<p>The Markdown editor has been refined to feel more stable and closer to the final rendered view. Headings in the editor now more closely match how they appear when viewing a note, reducing the visual jump between editing and reading. Layout issues have also been addressed so elements like rendered checkboxes and images no longer cause the editor to shift unexpectedly while typing.</p>
<p>The ABC music notation plugin appeared to be popular but had some limitations. With this new version, ABC is now part of the app, which means it can now work from published notes, and from the Rich Text editor!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-abc.png" alt="ABC music notation rendered directly in Joplin, showing a short musical phrase displayed from plain-text ABC syntax"></p>
<h3>Smoother switching between notes<a name="smoother-switching-between-notes" href="#smoother-switching-between-notes" class="heading-anchor">🔗</a></h3>
<p>Switching between notes is now less disruptive. Joplin restores cursor position and scroll location more reliably, making it easier to move back and forth between notes—especially when working with longer documents or comparing content—without losing your place.</p>
<h3>Case insensitive tags<a name="case-insensitive-tags" href="#case-insensitive-tags" class="heading-anchor">🔗</a></h3>
<p>Tags are now treated in a case-insensitive way, which helps prevent duplicate tags caused by differences in capitalisation, while still allowing mixed-case tag names. All this time we were hoping that @dpoulton <a href="https://discourse.joplinapp.org/t/tags-lower-case-only/4220/106">would just get used to lowercase tags</a>, but 5 years later it looks like it's not happening ;) So thank you @mrjo118 for implementing it!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-lowercase-tags.png" alt="Joplin tag list demonstrating case-insensitive tags, with mixed-case tag names merged into a single tag."></p>
<h3>More reliable syncing and sharing<a name="more-reliable-syncing-and-sharing" href="#more-reliable-syncing-and-sharing" class="heading-anchor">🔗</a></h3>
<p>Syncing and sharing have been made more robust in everyday use. Joplin now handles repeated syncs more efficiently, avoids unnecessary data usage, and is better at detecting and syncing all changes, particularly when using WebDAV and S3 sync targets.</p>
<p>Moreover filesystem synchronisation is now more reliable, in particular when used alongside tools like SyncThing on both mobile and desktop.</p>
<h3>Accessibility and readability improvements<a name="accessibility-and-readability-improvements" href="#accessibility-and-readability-improvements" class="heading-anchor">🔗</a></h3>
<p>Accessibility has seen further refinements in this release. Dark mode readability has been improved, common editor elements are clearer, and animations are reduced or disabled when system “reduce motion” settings are enabled, making the app more comfortable to use for a wider range of users. Keyboard navigation has also been improved on the desktop application.</p>
<h2>Desktop-specific improvements<a name="desktop-specific-improvements" href="#desktop-specific-improvements" class="heading-anchor">🔗</a></h2>
<h3>Easier profile management<a name="easier-profile-management" href="#easier-profile-management" class="heading-anchor">🔗</a></h3>
<p>Managing multiple profiles on desktop is now simpler thanks to a new, more user-friendly profile management interface. This removes the need to manually edit configuration files and makes switching between different setups easier and safer.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-profiles.png" alt="Desktop profile management screen in Joplin showing multiple profiles with options to rename or delete them."></p>
<h3>Significantly improved OneNote import<a name="significantly-improved-onenote-import" href="#significantly-improved-onenote-import" class="heading-anchor">🔗</a></h3>
<p>Importing content from OneNote is now more reliable and accurate. Support has been expanded to cover more OneNote file formats, and many edge cases have been addressed so imported notes more closely match their original structure and content. This makes migrating from OneNote to Joplin smoother and more trustworthy.</p>
<h3>Better tools for organising large note collections<a name="better-tools-for-organising-large-note-collections" href="#better-tools-for-organising-large-note-collections" class="heading-anchor">🔗</a></h3>
<p>Desktop users can now select multiple notebooks at once, making it easier to reorganise notebook structures, move groups of notes, or clean up larger collections without working notebook by notebook.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-multi-select.png" alt="Joplin desktop sidebar with several notebooks selected at the same time for bulk organisation."></p>
<h3>Polished editing experience on desktop<a name="polished-editing-experience-on-desktop" href="#polished-editing-experience-on-desktop" class="heading-anchor">🔗</a></h3>
<p>Both the Markdown and Rich Text editors have been further refined. Cursor behaviour is more predictable, visual consistency between editing and viewing has improved, and several layout and rendering issues have been fixed to reduce interruptions while writing.</p>
<h3>More reliable search and navigation<a name="more-reliable-search-and-navigation" href="#more-reliable-search-and-navigation" class="heading-anchor">🔗</a></h3>
<p>Search and navigation on desktop have been improved with fixes that ensure search results behave consistently and remain visible when moving between windows or views.</p>
<h3>Improved math support in WebClipper<a name="improved-math-support-in-webclipper" href="#improved-math-support-in-webclipper" class="heading-anchor">🔗</a></h3>
<p>The WebClipper is not forgotten in this release - clipping certain math formulas, in particular from Wikipedia but also other websites, has been improved. Additionally, certain scientific articles are now also better handled by the WebClipper.</p>
<h2>Mobile-specific improvements<a name="mobile-specific-improvements" href="#mobile-specific-improvements" class="heading-anchor">🔗</a></h2>
<h3>A more powerful Rich Text Editor on mobile<a name="a-more-powerful-rich-text-editor-on-mobile" href="#a-more-powerful-rich-text-editor-on-mobile" class="heading-anchor">🔗</a></h3>
<p>The mobile Rich Text Editor continues to improve, with new and expanded support for tables, code blocks, and other structured content. These changes make it easier to create and edit more complex notes directly on mobile devices.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-rte1.png" alt="Joplin mobile Rich Text Editor showing table editing controls and an embedded code block inside a note."></p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-rte2.png" alt="Mobile code block editor in Joplin with a Python code snippet displayed in an editable dialog."></p>
<h3>Easier tag management on mobile<a name="easier-tag-management-on-mobile" href="#easier-tag-management-on-mobile" class="heading-anchor">🔗</a></h3>
<p>Managing tags on mobile is now more practical. You can rename and delete tags directly from the app, and searching through tags is easier, helping keep large tag lists organised over time.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20260111-mobile-tags.png" alt="Joplin mobile tag management screen showing a tag options menu with rename and delete actions."></p>
<h3>Improved stability and usability on mobile devices<a name="improved-stability-and-usability-on-mobile-devices" href="#improved-stability-and-usability-on-mobile-devices" class="heading-anchor">🔗</a></h3>
<p>Several fixes improve overall stability and usability on mobile, particularly on smaller screens. Issues causing UI elements to appear off-screen have been addressed, and the app behaves more consistently in situations that previously caused hangs or visual glitches.</p>
<h2>Bug fixes and security fixes across platforms<a name="bug-fixes-and-security-fixes-across-platforms" href="#bug-fixes-and-security-fixes-across-platforms" class="heading-anchor">🔗</a></h2>
<h3>A large number of stability, correctness and security fixes<a name="a-large-number-of-stability-correctness-and-security-fixes" href="#a-large-number-of-stability-correctness-and-security-fixes" class="heading-anchor">🔗</a></h3>
<p>Joplin 3.5 includes about 114 bug fixes across desktop and mobile, addressing issues in editing, syncing, importing, rendering, and general stability. Many fixes target edge cases that could lead to crashes, inconsistent behaviour, or rare data loss scenarios. Moreover, this version includes several vulnerability fixes to make the applications more secure.</p>
]]></description><link>https://joplinapp.org/news/20260111-release-3-5</link><guid isPermaLink="false">20260111-release-3-5</guid><pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[What's new in Joplin 3.4]]></title><description><![CDATA[<p>Joplin 3.4 includes many bug fixes and improvements, with a focus on the mobile app.</p>
<h2>Mobile<a name="mobile" href="#mobile" class="heading-anchor">🔗</a></h2>
<h3>Rich Text Editor<a name="rich-text-editor" href="#rich-text-editor" class="heading-anchor">🔗</a></h3>
<p>The mobile app now includes a beta <a href="https://joplinapp.org/help/apps/rich_text_editor">Rich Text Editor</a>! The new editor renders formatting/math/images within the editor:</p>
@@ -481,42 +524,4 @@ sys 0m38.013s</p>
<p>This is a bit of an extra constraint but it is hard to avoid. Contributor License Agreements are very common for GPL or AGPL projects. For example Apache, Canonical or Python all require their contributors to sign a CLA.</p>
<h2>Questions?<a name="questions" href="#questions" class="heading-anchor">🔗</a></h2>
<p>If you have any questions please let us know. Overall we believe this is a positive improvements for Joplin as it means any work derives from it will also benefit the project.</p>
]]></description><link>https://joplinapp.org/news/20221221-agpl</link><guid isPermaLink="false">20221221-agpl</guid><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate><twitter-text>Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0)</twitter-text></item><item><title><![CDATA[What's new in Joplin 2.9]]></title><description><![CDATA[<h2>Proxy support<a name="proxy-support" href="#proxy-support" class="heading-anchor">🔗</a></h2>
<p>Both the desktop and mobile application now support proxies thanks to the work of Jason Williams. This will allow you to use the apps in particular when you are behind a company proxy.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20221216-proxy-support.png" alt=""></p>
<h2>New PDF viewer<a name="new-pdf-viewer" href="#new-pdf-viewer" class="heading-anchor">🔗</a></h2>
<p>The desktop application now features a new PDF viewer thanks to the work of Asrient during GSoC.</p>
<p>The main advantage for now is that this viewer preserves the last PDF page that was read. In the next version, the viewer will also include a way to annotate PDF files.</p>
<h2>Multi-language spell checking<a name="multi-language-spell-checking" href="#multi-language-spell-checking" class="heading-anchor">🔗</a></h2>
<p>The desktop app include a multi-language spell checking features, which allows you, for example, to spell-check notes in your native language and in English.</p>
<h2>New mobile text editor<a name="new-mobile-text-editor" href="#new-mobile-text-editor" class="heading-anchor">🔗</a></h2>
<p>Writing formatted notes on mobile has always been cumbersome due to the need to enter special format characters like <code>*</code> or <code>[</code>, etc.</p>
<p>Thanks to the work of Henry Heino during GSoC, writing notes on the go is now easier thanks to an improved Markdown editor.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20221216-mobile-beta-editor.png" alt=""></p>
<p>The most visible feature is the addition of a toolbar, which helps input those special characters, like on desktop.</p>
<p>Moreover Henry made a lot of subtle but useful improvements to the editor, for example to improve the note appearance, to improve list continuation, etc. Search within a note is now also supported as well as spell-checking.</p>
<p>At a more technical level, Henry also added many test units to ensure that the editor remains robust and reliable.</p>
<p>To enable the feature, go to the configuration screen and selected &quot;Opt-in to the editor beta&quot;. It is already very stable so we will probably promote it to be the main editor from the next version.</p>
<h2>Improved alignment of notebook icons<a name="improved-alignment-of-notebook-icons" href="#improved-alignment-of-notebook-icons" class="heading-anchor">🔗</a></h2>
<p>Previously, when you would assign an icon to a notebook, it would shift the title to the right, but notebook without an icon would not. It means that notebooks with and without an icon would not be vertically aligned.</p>
<p>To tidy things up, this new version adds a default icons to notebooks without an explicitly assigned icon. This result in the notebook titles being correctly vertically aligned.</p>
<p>Note that this feature is only enabled if you use custom icons - otherwise it will simply display the notebook titles without any default icons, as before.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20221216-notebook-icons.png" alt=""></p>
<h2>Improved handling of file attachments<a name="improved-handling-of-file-attachments" href="#improved-handling-of-file-attachments" class="heading-anchor">🔗</a></h2>
<p>Self Not Found made a number of small but useful improvements to attachment handling, including increasing the maximum size to 200MB, adding support for attaching multiple files, and fixing issues with synchronising attachments via proxy.</p>
<h2>Fixed filesystem sync on mobile<a name="fixed-filesystem-sync-on-mobile" href="#fixed-filesystem-sync-on-mobile" class="heading-anchor">🔗</a></h2>
<p>This was a long and complex change due to the need to support new Android APIs but hopefully that should now be working again, thanks to the work of jd1378.</p>
<p>So you can now sync again your notes with Syncthing and other file-based synchronisation systems.</p>
<h2>And more...<a name="and-more" href="#and-more" class="heading-anchor">🔗</a></h2>
<p>In total this new desktop version includes 36 improvements, bug fixes, and security fixes.</p>
<p>As always, a lot of work went into the Android and iOS app too, which include 37 improvements, bug fixes, and security fixes.</p>
<p>See here for the changelogs:</p>
<ul>
<li><a href="https://joplinapp.org/help/about/changelog/desktop">Desktop app changelog</a></li>
<li><a href="https://joplinapp.org/help/about/changelog/android/">Android app changelog</a></li>
</ul>
<h2>About the Android version<a name="about-the-android-version" href="#about-the-android-version" class="heading-anchor">🔗</a></h2>
<p>Unfortunately we cannot publish the Android version because it is based on a framework version that Google does not accept. To upgrade the app a lot of changes are needed and another round of pre-releases, and therefore there will not be a 2.9 version for Google Play. You may however download the official APK directly from there: <a href="https://github.com/laurent22/joplin-android/releases/tag/android-v2.9.8">Android 2.9 Official Release</a></p>
<p>This is the reality of app stores in general - small developers being imposed never ending new requirements by all-powerful companies, and by the time a version is finally ready we can't even publish it because yet more requirements are in place.</p>
<p>For the record the current 2.9 app works perfectly fine. It targets Android 11, which is only 2 years old and is still supported (and installed on millions of phones). Google requires us to target Android 12 which only came out last year.</p>
]]></description><link>https://joplinapp.org/news/20221216-release-2-9</link><guid isPermaLink="false">20221216-release-2-9</guid><pubDate>Fri, 16 Dec 2022 00:00:00 GMT</pubDate><twitter-text>What&apos;s new in Joplin 2.9</twitter-text></item></channel></rss>
]]></description><link>https://joplinapp.org/news/20221221-agpl</link><guid isPermaLink="false">20221221-agpl</guid><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate><twitter-text>Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0)</twitter-text></item></channel></rss>

View File

@@ -6,7 +6,7 @@ Only the latest version is supported with security updates.
## Reporting a Vulnerability
Please [contact support](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/AdresseSupport.png) **with a proof of concept** that shows the security vulnerability. Please do not contact us without this proof of concept, as we cannot fix anything without this.
Please report vulnerabilities [through private vulnerability reporting](https://github.com/laurent22/joplin/security/advisories/new) **with a proof of concept** that shows the security vulnerability. Please do not contact us without this proof of concept, as we cannot fix anything without this.
For general opinions on what makes an app more or less secure, please use the forum.

View File

@@ -33,7 +33,7 @@
"/packages/app-desktop/build/",
"/packages/app-desktop/utils/checkForUpdatesUtilsTestData.ts",
"/packages/app-desktop/vendor/",
"/packages/app-mobile/android/vendor/",
"/packages/whisper-voice-typing/vendor/",
"/packages/app-mobile/ios/Pods/",
"/packages/app-mobile/lib/rnInjectedJs",
"/packages/app-mobile/pluginAssets",

View File

@@ -9,7 +9,7 @@
"vips.dev": {
"platforms": ["aarch64-darwin"],
},
"nodejs": "24.5.0",
"nodejs": "24.8.0",
"pkg-config": "latest",
"python": "3.13.3",
"bat": "latest",

6
jest.config.base.js Normal file
View File

@@ -0,0 +1,6 @@
// This is the base Jest configuration - all
// jest.config.js files should inherit from it.
module.exports = {
watchman: false,
};

View File

@@ -16,6 +16,7 @@
"./packages/app-cli/**/*.mo": true,
"./packages/app-cli/**/build/": true,
"./packages/app-cli/**/config.json": true,
"**/.watchman-cookie-*": true,
"./packages/app-cli/**/linkToLocal.sh": true,
"./packages/app-cli/**/node_modules/": true,
"./packages/app-cli/**/out.txt": true,

View File

@@ -86,9 +86,9 @@
"gulp": "4.0.2",
"husky": "9.1.7",
"lerna": "3.22.1",
"lint-staged": "16.1.6",
"lint-staged": "16.2.6",
"madge": "8.0.0",
"npm-package-json-lint": "8.0.0",
"npm-package-json-lint": "9.0.0",
"typescript": "5.8.3"
},
"dependencies": {

View File

@@ -21,7 +21,8 @@ class Command extends BaseCommand {
public override async action(_args: Args) {
const keymaps = await app().loadKeymaps();
this.stdout(_('Configured keyboard shortcuts:\n'));
this.stdout(_('Configured keyboard shortcuts:'));
this.stdout('\n');
const rows = [];
const padding = ' ';
@@ -31,7 +32,7 @@ class Command extends BaseCommand {
for (const item of keymaps) {
const formattedKeys = item.keys
.map((k: string) => (k === ' ' ? `(${'SPACE'})` : k))
.map((k: string) => (k === ' ' ? `(${_('SPACE')})` : k))
.join(', ');
rows.push([padding + formattedKeys, item.type, item.command]);
}

View File

@@ -1,6 +1,7 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import mockShareService from '@joplin/lib/testing/share/mockShareService';
import { createFolderTree, setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
import { createFolderTree, setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import waitFor from '@joplin/lib/testing/waitFor';
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';

View File

@@ -1,6 +1,7 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import mockShareService from '@joplin/lib/testing/share/mockShareService';
import { setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import waitFor from '@joplin/lib/testing/waitFor';
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';

View File

@@ -24,7 +24,10 @@
// 4. Remove tests one by one to narrow it down to the one with the async
// call that's causing problem.
const baseConfig = require('../../jest.config.base.js');
module.exports = {
...baseConfig,
testMatch: [
'**/tests/HtmlToHtml.js',
'**/tests/HtmlToMd.js',

View File

@@ -35,15 +35,15 @@
],
"owner": "Laurent Cozic"
},
"version": "3.5.1",
"version": "3.6.0",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "~3.5",
"@joplin/renderer": "~3.5",
"@joplin/utils": "~3.5",
"@joplin/lib": "~3.6",
"@joplin/renderer": "~3.6",
"@joplin/utils": "~3.6",
"aws-sdk": "2.1340.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",
@@ -57,7 +57,7 @@
"proper-lockfile": "4.1.2",
"redux": "4.2.1",
"server-destroy": "1.0.1",
"sharp": "0.34.4",
"sharp": "0.34.5",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
@@ -70,7 +70,7 @@
"yargs-parser": "21.1.1"
},
"devDependencies": {
"@joplin/tools": "~3.5",
"@joplin/tools": "~3.6",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.14",
"@types/node": "18.19.130",

View File

@@ -12,6 +12,7 @@ function newTestMdToHtml(options: any = null) {
ResourceModel: {
isResourceUrl: isResourceUrl,
urlToId: resourceUrlToId,
fullPath: () => '/some/path/here',
},
fsDriver: shim.fsDriver(),
...options,
@@ -56,6 +57,21 @@ describe('MdToHtml', () => {
mdToHtmlOptions.mapsToLine = true;
} else if (mdFilename.startsWith('resource_')) {
mdToHtmlOptions.resources = {};
} else if (mdFilename.startsWith('pdf_')) {
mdToHtmlOptions.resources = {
'00000000000000000000000000000001': {
item: { mime: 'application/pdf' },
localState: { },
},
};
mdToHtmlOptions.pdfViewerEnabled = true;
} else if (mdFilename.startsWith('video_')) {
mdToHtmlOptions.resources = {
'00000000000000000000000000000001': {
item: { mime: 'video/mp4' },
localState: { },
},
};
}
const markdown = await shim.fsDriver().readFile(mdFilePath);
@@ -86,7 +102,7 @@ describe('MdToHtml', () => {
// eslint-disable-next-line no-console
console.info(msg.join('\n'));
expect(false).toBe(true);
expect(actualHtml).toBe(expectedHtml);
// return;
} else {
expect(true).toBe(true);

View File

@@ -0,0 +1,11 @@
<div class="joplin-editable">
<!-- Regression test: Historically, text nodes before the first "joplin-source" block caused
conversion to fail. -->
A text node!
<pre class="joplin-source" data-joplin-language="test" data-joplin-source-open="```&#10;" data-joplin-source-close="&#10;```">
Test!
</pre>
<div class="joplin-rendered">
<p>Test content</p>
</div>
</div>

View File

@@ -0,0 +1,4 @@
```
Test!
```

View File

@@ -0,0 +1,8 @@
<div class="joplin-editable">
<span class="joplin-source" data-joplin-source-open="" data-joplin-source-close="">https://www.youtube.com/watch?v=iJqe9pC-z-Y</span>
<div class="joplin-youtube-player-rendered">
<iframe src="https://www.youtube-nocookie.com/embed/iJqe9pC-z-Y" title="YouTube video player" frameborder="0" allowfullscreen></iframe>
</div>
</div>

View File

@@ -0,0 +1 @@
https://www.youtube.com/watch?v=iJqe9pC-z-Y

View File

@@ -0,0 +1,4 @@
<p>Embed without starting page:</p>
<p><a data-from-md data-resource-id='00000000000000000000000000000001' type='application/pdf' href='#' onclick='postMessage(&quot;joplin://00000000000000000000000000000001&quot;, { resourceId: &quot;00000000000000000000000000000001&quot; }); return false;'><span class="resource-icon fa-file-pdf"></span>pdf</a><object data="file:///some/path/here" class="media-player media-pdf" type="application/pdf"></object></p>
<p>Embed with starting page:</p>
<p><a data-from-md data-resource-id='00000000000000000000000000000001' type='application/pdf' href='#' onclick='postMessage(&quot;joplin://00000000000000000000000000000001#page=1&quot;, { resourceId: &quot;00000000000000000000000000000001&quot; }); return false;'><span class="resource-icon fa-file-pdf"></span>pdf</a><object data="file:///some/path/here#page=1" class="media-player media-pdf" type="application/pdf"></object></p>

View File

@@ -0,0 +1,8 @@
Embed without starting page:
[pdf](:/00000000000000000000000000000001)
Embed with starting page:
[pdf](:/00000000000000000000000000000001#page=1)

View File

@@ -0,0 +1,10 @@
<p><a data-from-md data-resource-id='00000000000000000000000000000001' type='video/mp4' href='#' onclick='postMessage(&quot;joplin://00000000000000000000000000000001#t=1,2&quot;, { resourceId: &quot;00000000000000000000000000000001&quot; }); return false;'><span class="resource-icon fa-file-video"></span>video, with start/end time</a>
<video class="media-player media-video" controls>
<source src="file:///some/path/here#t=1,2" type="video/mp4">
</video>
</p>
<p><a data-from-md data-resource-id='00000000000000000000000000000001' type='video/mp4' href='#' onclick='postMessage(&quot;joplin://00000000000000000000000000000001&quot;, { resourceId: &quot;00000000000000000000000000000001&quot; }); return false;'><span class="resource-icon fa-file-video"></span>video, without start/end time</a>
<video class="media-player media-video" controls>
<source src="file:///some/path/here" type="video/mp4">
</video>
</p>

View File

@@ -0,0 +1,4 @@
[video, with start/end time](:/00000000000000000000000000000001#t=1,2)
[video, without start/end time](:/00000000000000000000000000000001)

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Joplin Web Clipper [DEV]",
"version": "3.5.0",
"version": "3.6.0",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": {

View File

@@ -260,6 +260,15 @@ export default class ElectronAppWrapper {
require('@electron/remote/main').enable(this.win_.webContents);
// Add Referer header for YouTube embeds to fix Error 153
this.win_.webContents.session.webRequest.onBeforeSendHeaders(
{ urls: ['*://*.youtube.com/*', '*://*.youtube-nocookie.com/*'] },
(details, callback) => {
details.requestHeaders['Referer'] = 'https://joplinapp.org/';
callback({ requestHeaders: details.requestHeaders });
},
);
if (!screen.getDisplayMatching(this.win_.getBounds())) {
const { width: windowWidth, height: windowHeight } = this.win_.getBounds();
const { width: primaryDisplayWidth, height: primaryDisplayHeight } = screen.getPrimaryDisplay().workArea;

View File

@@ -95,6 +95,9 @@ export default class InteropServiceHelper {
// Allows users to override the CSS page size.
// See https://github.com/laurent22/joplin/issues/13096
preferCSSPageSize: true,
// Include accessibility information in the output:
generateTaggedPDF: true,
});
resolve(data);
} catch (error) {

View File

@@ -693,17 +693,8 @@ function useMenu(props: Props) {
menuItemDic.pasteAsText,
menuItemDic.textSelectAll,
separator(),
// Using the generic "undo"/"redo" roles mean the menu
// item will work in every text fields, whether it's the
// editor or a regular text field.
{
role: 'undo',
label: _('Undo'),
},
{
role: 'redo',
label: _('Redo'),
},
menuItemDic.globalUndo,
menuItemDic.globalRedo,
separator(),
menuItemDic.textBold,
menuItemDic.textItalic,

View File

@@ -1,6 +1,6 @@
import { ContextMenuParams, Event } from 'electron';
import { useEffect, RefObject } from 'react';
import { useEffect, RefObject, useContext } from 'react';
import { _ } from '@joplin/lib/locale';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { EditContextMenuFilterObject, MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
@@ -11,6 +11,7 @@ import type CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl'
import eventManager from '@joplin/lib/eventManager';
import bridge from '../../../../../services/bridge';
import Setting from '@joplin/lib/models/Setting';
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -29,6 +30,7 @@ interface ContextMenuProps {
const useContextMenu = (props: ContextMenuProps) => {
const editorRef = props.editorRef;
const windowId = useContext(WindowIdContext);
// The below code adds support for spellchecking when it is enabled
// It might be buggy, refer to the below issue
@@ -156,7 +158,7 @@ const useContextMenu = (props: ContextMenuProps) => {
// Prepend the event listener so that it gets called before
// the listener that shows the default menu.
const targetWindow = bridge().activeWindow();
const targetWindow = bridge().windowById(windowId);
targetWindow.webContents.prependListener('context-menu', onContextMenu);
return () => {
@@ -167,6 +169,7 @@ const useContextMenu = (props: ContextMenuProps) => {
}, [
props.plugins, props.editorClassName, editorRef, props.containerRef,
props.editorCutText, props.editorCopyText, props.editorPaste,
windowId,
]);
};

View File

@@ -294,6 +294,13 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
window.requestAnimationFrame(() => editor.undoManager.add());
},
pasteAsText: () => editor.fire(TinyMceEditorEvents.PasteAsText),
'editor.undo': () => {
editor.undoManager.undo();
},
'editor.redo': () => {
editor.undoManager.redo();
},
};
if (additionalCommands[cmd.name]) {
@@ -742,7 +749,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
'media-src \'self\' blob: data: *', // Audio and video players
// Disallow certain unused features
'child-src \'none\'', // Should not contain sub-frames
'child-src https://*.youtube.com https://*.youtube-nocookie.com', // Allow YouTube embeds
'object-src \'none\'', // Objects can be used for script injection
'form-action \'none\'', // No submitting forms

View File

@@ -22,4 +22,8 @@ export const joplinCommandToTinyMceCommands: JoplinCommandToTinyMceCommands = {
'search': { name: 'SearchReplace' },
'attachFile': { name: 'joplinAttach' },
'insertDateTime': true,
'textCopy': true,
'textCut': true,
'textPaste': true,
'textSelectAll': true,
};

View File

@@ -1,7 +1,7 @@
import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
import { useEffect } from 'react';
import { useContext, useEffect } from 'react';
import bridge from '../../../../../services/bridge';
import { ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenuUtils';
import { menuItems } from '../../../utils/contextMenu';
@@ -18,6 +18,7 @@ import { Dispatch } from 'redux';
import { _ } from '@joplin/lib/locale';
import type { MenuItem as MenuItemType } from 'electron';
import isItemId from '@joplin/lib/models/utils/isItemId';
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -30,11 +31,12 @@ interface ContextMenuActionOptions {
const contextMenuActionOptions: ContextMenuActionOptions = { current: null };
export default function(editor: Editor, plugins: PluginStates, dispatch: Dispatch, htmlToMd: HtmlToMarkdownHandler, mdToHtml: MarkupToHtmlHandler, editDialog: EditDialogControl) {
const windowId = useContext(WindowIdContext);
useEffect(() => {
if (!editor) return () => {};
const contextMenuItems = menuItems(dispatch);
const targetWindow = bridge().activeWindow();
const targetWindow = bridge().windowById(windowId);
const makeMainMenuItems = (element: Element) => {
let itemType: ContextMenuItemType = ContextMenuItemType.None;
@@ -175,5 +177,5 @@ export default function(editor: Editor, plugins: PluginStates, dispatch: Dispatc
targetWindow.webContents.off('context-menu', onElectronContextMenu);
}
};
}, [editor, plugins, dispatch, htmlToMd, mdToHtml, editDialog]);
}, [editor, plugins, dispatch, htmlToMd, mdToHtml, editDialog, windowId]);
}

View File

@@ -51,6 +51,15 @@ function newBlockSource(language = '', content = '', previousSource: SourceInfo
} else {
fence = '$$';
}
} else if (language === 'frontmatter') {
// Frontmatter uses --- delimiters instead of code fences
return {
openCharacters: '---\n',
closeCharacters: '\n---\n',
content: content,
node: null,
language: language,
};
}
const fenceLanguage = language === 'katex' ? '' : language;

View File

@@ -17,19 +17,19 @@ describe('editorCommandDeclarations', () => {
test.each([
[
{},
true,
{ textBold: true },
],
[
{
markdownEditorPaneVisible: false,
},
false,
{ textBold: false },
],
[
{
noteIsReadOnly: true,
},
false,
{ textBold: false },
],
[
// In the Markdown editor, but only the viewer is visible
@@ -37,7 +37,7 @@ describe('editorCommandDeclarations', () => {
markdownEditorPaneVisible: false,
richTextEditorVisible: false,
},
false,
{ textBold: false },
],
[
// In the Markdown editor, and the viewer is visible
@@ -45,7 +45,7 @@ describe('editorCommandDeclarations', () => {
markdownEditorPaneVisible: true,
richTextEditorVisible: false,
},
true,
{ textBold: true },
],
[
// In the RT editor
@@ -53,7 +53,7 @@ describe('editorCommandDeclarations', () => {
markdownEditorPaneVisible: false,
richTextEditorVisible: true,
},
true,
{ textBold: true },
],
[
// In the Markdown editor, and the command palette is visible
@@ -63,14 +63,57 @@ describe('editorCommandDeclarations', () => {
gotoAnythingVisible: true,
modalDialogVisible: true,
},
true,
{ textBold: true },
],
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
])('should create the enabledCondition', (context: Record<string, any>, expected: boolean) => {
const condition = enabledCondition('textBold');
const wc = new WhenClause(condition);
const actual = wc.evaluate({ ...baseContext, ...context });
expect(actual).toBe(expected);
[
// In the Markdown editor, and the command palette is visible
{
markdownEditorPaneVisible: true,
richTextEditorVisible: false,
gotoAnythingVisible: true,
modalDialogVisible: true,
},
{ textBold: true },
],
[
// Rich Text Editor, HTML note
{
markdownEditorPaneVisible: false,
richTextEditorVisible: true,
noteIsMarkdown: false,
},
{
textCopy: true,
textPaste: true,
textSelectAll: true,
},
],
[
// Rich Text Editor, read-only note
{
markdownEditorPaneVisible: false,
richTextEditorVisible: true,
noteIsReadOnly: true,
},
{
textBold: false,
textPaste: false,
// TODO: textCopy should be enabled in read-only notes:
// textCopy: false,
},
],
])('should correctly determine whether command is enabled (case %#)', (context, expectedStates) => {
const actualStates = [];
for (const commandName of Object.keys(expectedStates)) {
const condition = enabledCondition(commandName);
const wc = new WhenClause(condition);
const actual = wc.evaluate({ ...baseContext, ...context });
actualStates.push([commandName, actual]);
}
const expectedStatesArray = Object.entries(expectedStates);
expect(actualStates).toEqual(expectedStatesArray);
});
});

View File

@@ -4,6 +4,10 @@ import { joplinCommandToTinyMceCommands } from './NoteBody/TinyMCE/utils/joplinC
const workWithHtmlNotes = [
'attachFile',
'textCopy',
'textCut',
'textPaste',
'textSelectAll',
];
export const enabledCondition = (commandName: string) => {

View File

@@ -58,6 +58,17 @@ const usePluginMessageResponder = (webviewRef: RefObject<HTMLIFrameElement>) =>
}, [webviewRef, windowId]);
};
const useAllowAttribute = () => {
// Specifies what content in the note viewer can do. See
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/allow
// allow=fullscreen: Required to allow the user to fullscreen videos.
return [
'clipboard-write', 'fullscreen', 'autoplay', 'local-fonts', 'encrypted-media',
].map(
attr => `${attr} joplin-content://note-viewer/`,
).join('; ');
};
const NoteTextViewer = forwardRef((props: Props, ref: ForwardedRef<NoteViewerControl>) => {
const [webview, setWebview] = useState<HTMLIFrameElement|null>(null);
const webviewRef = useRef<HTMLIFrameElement|null>(null);
@@ -233,14 +244,13 @@ const NoteTextViewer = forwardRef((props: Props, ref: ForwardedRef<NoteViewerCon
return { border: 'none', ...props.viewerStyle };
}, [props.viewerStyle]);
// allow=fullscreen: Required to allow the user to fullscreen videos.
const allow = useAllowAttribute();
return (
<iframe
className="noteTextViewer"
ref={setWebview}
style={viewerStyle}
allow='clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=(self) encrypted-media=(self)'
allowFullScreen={true}
allow={allow}
aria-label={_('Note viewer')}
src={`joplin-content://note-viewer/${toForwardSlashes(getAssetPath('gui/note-viewer/index.html'))}`}
></iframe>

View File

@@ -0,0 +1,24 @@
import CommandService, { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { WindowControl } from '../utils/useWindowControl';
import bridge from '../../../services/bridge';
import canUseNativeUndo from './utils/canUseNativeUndo';
export const declaration: CommandDeclaration = {
name: 'globalRedo',
label: () => _('Redo'),
};
export const runtime = (control: WindowControl): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
if (canUseNativeUndo(control)) {
bridge().activeWindow().webContents.redo();
} else {
await CommandService.instance().execute('editor.redo');
}
},
enabledCondition: '',
};
};

View File

@@ -0,0 +1,27 @@
import CommandService, { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { WindowControl } from '../utils/useWindowControl';
import bridge from '../../../services/bridge';
import canUseNativeUndo from './utils/canUseNativeUndo';
export const declaration: CommandDeclaration = {
name: 'globalUndo',
label: () => _('Undo'),
};
export const runtime = (control: WindowControl): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
// As of January 2026, webContents.undo() doesn't work properly in more complex
// edit controls (e.g. CodeMirror or TinyMCE). Only use it when a more simple input
// has focus:
if (canUseNativeUndo(control)) {
bridge().activeWindow().webContents.undo();
} else {
await CommandService.instance().execute('editor.undo');
}
},
enabledCondition: '',
};
};

View File

@@ -124,8 +124,7 @@ export const runtime = (control: WindowControl): CommandRuntime => {
void CommandService.instance().execute('showModalMessage', `${modalMessage}\n\n${statusStrings.join('\n')}`);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onError: (error: any) => {
onError: (error: string|Error) => {
errors.push(error);
console.warn(error);
},

View File

@@ -5,6 +5,8 @@ import * as deleteFolder from './deleteFolder';
import * as duplicateNote from './duplicateNote';
import * as editAlarm from './editAlarm';
import * as exportPdf from './exportPdf';
import * as globalRedo from './globalRedo';
import * as globalUndo from './globalUndo';
import * as gotoAnything from './gotoAnything';
import * as hideModalMessage from './hideModalMessage';
import * as importFrom from './importFrom';
@@ -54,6 +56,8 @@ const index: any[] = [
duplicateNote,
editAlarm,
exportPdf,
globalRedo,
globalUndo,
gotoAnything,
hideModalMessage,
importFrom,

View File

@@ -0,0 +1,11 @@
import { WindowControl } from '../../utils/useWindowControl';
// CodeMirror and TinyMCE both have trouble with native Electron
// undo/redo.
// See https://github.com/laurent22/joplin/issues/14216
const canUseNativeUndo = (control: WindowControl) => {
const dom = control.getFocusedDocument();
return !dom.activeElement.closest('.CodeMirror, div.joplin-tinymce');
};
export default canUseNativeUndo;

View File

@@ -21,7 +21,7 @@ const useWindowCommands = ({ documentRef, customCss, plugins, editorNoteStatuses
editorNoteStatuses: editorNoteStatuses,
plugins: plugins,
});
const windowControl = useWindowControl(setDialogState, onPrintCallback);
const windowControl = useWindowControl(setDialogState, onPrintCallback, documentRef);
// This effect needs to run as soon as possible. Certain components may fail to load if window
// commands are not registered on their first render.

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useMemo, useRef } from 'react';
import { RefObject, useMemo, useRef } from 'react';
import { DialogState } from '../types';
import { PrintCallback } from './usePrintToCallback';
import { _ } from '@joplin/lib/locale';
@@ -23,10 +23,11 @@ export interface WindowControl {
showPrompt: <T>(options: PromptOptions<T>)=> Promise<T>;
printTo: PrintCallback;
announcePanelVisibility(panelName: string, visible: boolean): void;
getFocusedDocument(): Document;
}
export type OnSetDialogState = React.Dispatch<React.SetStateAction<DialogState>>;
const useWindowControl = (setDialogState: OnSetDialogState, onPrint: PrintCallback) => {
const useWindowControl = (setDialogState: OnSetDialogState, onPrint: PrintCallback, windowDomRef: RefObject<Document>) => {
// Use refs to avoid reloading the output where possible -- reloading the window control
// may mean reloading all main window commands.
const onPrintRef = useRef(onPrint);
@@ -67,9 +68,12 @@ const useWindowControl = (setDialogState: OnSetDialogState, onPrint: PrintCallba
});
});
},
getFocusedDocument: () => {
return windowDomRef.current;
},
};
return control;
}, [setDialogState]);
}, [setDialogState, windowDomRef]);
};
export default useWindowControl;

View File

@@ -50,13 +50,16 @@ export default function() {
'editor.duplicateLine',
'openSecondaryAppInstance',
'openPrimaryAppInstance',
// We cannot put the undo/redo commands in the menu because they are
// editor-specific commands. If we put them there it will break the
// undo/redo in regular text fields.
// https://github.com/laurent22/joplin/issues/6214
// 'editor.undo',
// 'editor.redo',
// We cannot put the editor.undo/editor.redo commands in the menu because they are
// editor-specific commands. If we put them there it will break the undo/redo in
// regular text fields (https://github.com/laurent22/joplin/issues/6214).
// However, the native Electron undo/redo doesn't work well in TinyMCE/CodeMirror.
// As a workaround, use these commands that switch between editor.undo and native Electron
// undo/redo depending on the type of selected editor:
'globalUndo',
'globalRedo',
'editor.indentLess',
'editor.indentMore',
'editor.toggleComment',

View File

@@ -8,7 +8,7 @@
default-src 'self' joplin-content://* ;
connect-src 'self' * http://* https://* joplin-content://* blob: ;
style-src 'unsafe-inline' 'self' blob: joplin-content://* https://* http://* ;
child-src 'self' joplin-content://* ;
child-src 'self' joplin-content://* https://*.youtube.com https://*.youtube-nocookie.com ;
script-src 'self' 'unsafe-inline' joplin-content://* ;
media-src 'self' * blob: data: https://* http://* joplin-content://* ;
img-src 'self' blob: data: http://* https://* joplin-content://* ;

View File

@@ -381,5 +381,24 @@ test.describe('markdownEditor', () => {
await goToAnything.runCommand(electronApp, 'textPaste');
await noteEditor.expectToHaveText(/^Test \(new content!\)[\n]+/);
});
test('the undo and redo menu items should work', async ({ mainWindow, electronApp }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
await mainScreen.waitFor();
await mainScreen.createNewNote('Test undo/redo');
const noteEditor = mainScreen.noteEditor;
await noteEditor.focusCodeMirrorEditor();
await mainWindow.keyboard.type('A');
await noteEditor.expectToHaveText('A');
await activateMainMenuItem(electronApp, 'Undo');
await noteEditor.expectToHaveText('\n');
await activateMainMenuItem(electronApp, 'Redo');
await noteEditor.expectToHaveText('A');
});
});

View File

@@ -23,7 +23,7 @@ const waitFor = async (condition) => {
setTimeout(() => resolve(), 100);
});
};
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 500; i++) {
if (await condition()) {
return;
}

View File

@@ -1,7 +1,10 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
const baseConfig = require('../../jest.config.base.js');
module.exports = {
...baseConfig,
// All imported modules in your tests should be mocked automatically
// automock: false,
@@ -128,7 +131,9 @@ module.exports = {
testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
testEnvironmentOptions: {
customExportConditions: ['node', 'require'],
},
// Adds a location field to test results
// testLocationInResults: false,

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.5.10",
"version": "3.6.2",
"description": "Joplin for Desktop",
"main": "main.bundle.js",
"private": true,
@@ -92,6 +92,12 @@
"x64"
]
},
{
"target": "pkg",
"arch": [
"x64"
]
},
{
"target": "zip",
"arch": [
@@ -139,19 +145,19 @@
"@electron/rebuild": "3.7.2",
"@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4",
"@joplin/default-plugins": "~3.5",
"@joplin/editor": "~3.5",
"@joplin/lib": "~3.5",
"@joplin/renderer": "~3.5",
"@joplin/tools": "~3.5",
"@joplin/utils": "~3.5",
"@joplin/default-plugins": "~3.6",
"@joplin/editor": "~3.6",
"@joplin/lib": "~3.6",
"@joplin/renderer": "~3.6",
"@joplin/tools": "~3.6",
"@joplin/utils": "~3.6",
"@playwright/test": "1.55.1",
"@sentry/electron": "4.24.0",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.14",
"@types/mustache": "4.2.6",
"@types/node": "18.19.130",
"@types/react": "18.3.25",
"@types/react": "18.3.26",
"@types/react-dom": "18.3.7",
"@types/react-redux": "7.1.33",
"@types/styled-components": "5.1.32",
@@ -163,7 +169,7 @@
"debounce": "1.2.1",
"electron": "39.2.3",
"electron-builder": "24.13.3",
"electron-updater": "6.6.2",
"electron-updater": "6.6.8",
"electron-window-state": "5.0.3",
"esbuild": "^0.25.3",
"formatcoords": "1.1.3",
@@ -179,7 +185,7 @@
"md5": "2.3.0",
"moment": "2.30.1",
"mustache": "4.2.0",
"nan": "2.23.0",
"nan": "2.23.1",
"node-notifier": "10.0.1",
"node-rsa": "1.1.1",
"pdfjs-dist": "3.11.174",
@@ -208,7 +214,7 @@
},
"dependencies": {
"@electron/remote": "2.1.3",
"@joplin/onenote-converter": "~3.5",
"@joplin/onenote-converter": "~3.6",
"fs-extra": "11.3.2",
"keytar": "7.9.0",
"node-fetch": "2.6.7",

View File

@@ -140,7 +140,10 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
// electron's autoUpdater appends automatically the platform's yml file to the link so we should remove it
assetUrl = assetUrl.substring(0, assetUrl.lastIndexOf('/'));
autoUpdater.setFeedURL({ provider: 'generic', url: assetUrl });
await autoUpdater.checkForUpdates();
const result = await autoUpdater.checkForUpdates();
// Wait for the installation to finish. By default, .checkForUpdates runs in the background
await result.downloadPromise;
} catch (error) {
this.logger_.error(`Update download url failed: ${error.message}`);
this.isUpdateInProgress = false;

View File

@@ -0,0 +1,90 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.default = notarizeFile;
const fs_1 = require('fs');
const notarize_1 = require('@electron/notarize');
const execCommand = require('./execCommand');
const child_process_1 = require('child_process');
const util_1 = require('util');
const execAsync = (0, util_1.promisify)(child_process_1.exec);
// Same appId in electron-builder.
const appId = 'net.cozic.joplin-desktop';
function isDesktopAppTag(tagName) {
if (!tagName) { return false; }
return tagName[0] === 'v';
}
async function notarizeFile(filePath) {
if (process.platform !== 'darwin') { return; }
console.info(`Checking if notarization should be done on: ${filePath}`);
if (!process.env.IS_CONTINUOUS_INTEGRATION || !isDesktopAppTag(process.env.GIT_TAG_NAME)) {
console.info(`Either not running in CI or not processing a desktop app tag - skipping notarization. process.env.IS_CONTINUOUS_INTEGRATION = ${process.env.IS_CONTINUOUS_INTEGRATION}; process.env.GIT_TAG_NAME = ${process.env.GIT_TAG_NAME}`);
return;
}
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
console.warn('Environment variables APPLE_ID and APPLE_ID_PASSWORD not found - notarization will NOT be done.');
return;
}
if (!(0, fs_1.existsSync)(filePath)) {
throw new Error(`Cannot find file at: ${filePath}`);
}
// Every x seconds we print something to stdout, otherwise CI may timeout
// the task after 10 minutes, and Apple notarization can take more time.
const waitingIntervalId = setInterval(() => {
console.info('.');
}, 60000);
const isPkg = filePath.endsWith('.pkg');
console.info(`Notarizing ${filePath}`);
try {
if (isPkg) {
await execAsync(`xcrun notarytool submit "${filePath}" ` +
`--apple-id "${process.env.APPLE_ID}" ` +
`--password "${process.env.APPLE_ID_PASSWORD}" ` +
`--team-id "${process.env.APPLE_ASC_PROVIDER}" ` +
'--wait', { maxBuffer: 1024 * 1024 });
} else {
await (0, notarize_1.notarize)({
appBundleId: appId,
appPath: filePath,
// Apple Developer email address
appleId: process.env.APPLE_ID,
// App-specific password: https://support.apple.com/en-us/HT204397
appleIdPassword: process.env.APPLE_ID_PASSWORD,
// When Apple ID is attached to multiple providers (eg if the
// account has been used to build multiple apps for different
// companies), in that case the provider "Team Short Name" (also
// known as "ProviderShortname") must be provided.
//
// Use this to get it:
//
// xcrun altool --list-providers -u APPLE_ID -p APPLE_ID_PASSWORD
// ascProvider: process.env.APPLE_ASC_PROVIDER,
// In our case, the team ID is the same as the legacy ASC_PROVIDER
teamId: process.env.APPLE_ASC_PROVIDER,
tool: 'notarytool',
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
});
}
} catch (error) {
console.error(error);
process.exit(1);
}
clearInterval(waitingIntervalId);
// It appears that electron-notarize doesn't staple the app, but without
// this we were still getting the malware warning when launching the app.
// Stapling the app means attaching the notarization ticket to it, so that
// if the user is offline, macOS can still check if the app was notarized.
// So it seems to be more or less optional, but at least in our case it
// wasn't.
console.info('Stapling notarization ticket to the file...');
const staplerCmd = `xcrun stapler staple "${filePath}"`;
console.info(`> ${staplerCmd}`);
console.info(await execCommand(staplerCmd));
console.info(`Validating stapled file: ${filePath}`);
try {
await execAsync(`spctl -a -vv -t install "${filePath}"`);
} catch (error) {
console.error(`Failed validating stapled file: ${filePath}:`, error);
}
console.info(`Done notarizing ${filePath}`);
}
// # sourceMappingURL=notarizeFile.js.map

View File

@@ -2,11 +2,24 @@ apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
// (Disabled) Use Expo CLI to bundle the app, this ensures the Metro config
// works correctly with Expo projects.
// cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
// bundleCommand = "export:embed"
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../..")
@@ -55,31 +68,12 @@ react {
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
android {
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
@@ -89,21 +83,17 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097787
versionName "3.5.7"
versionCode 2097800
versionName "3.6.12"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
multiDexEnabled true
externalNativeBuild {
cmake {
cppFlags '-DCMAKE_BUILD_TYPE=Release'
// For 16 KB pages. This should be removable after upgrading to NDK r28
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
}
signingConfigs {
debug {
@@ -129,19 +119,25 @@ android {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
minifyEnabled enableMinifyInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
profileable {
// Release-like build that allows profiling with Android Studio Profiler
initWith release
signingConfig signingConfigs.debug
// Required for Android Studio Profiler to attach
debuggable false
// Keeps symbols for better stack traces in profiler
minifyEnabled false
// Use release variants of dependencies that don't have profileable
matchingFallbacks = ['release']
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
implementation("com.facebook.react:hermes-android")
}

View File

@@ -8,3 +8,7 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# Keep classes referenced by JNI
# (see https://developer.android.com/topic/performance/app-optimization/add-keep-rules)
-keep class com.margelo.nitro.whispervoicetyping.AudioRecorder

View File

@@ -44,8 +44,12 @@
android:requestLegacyExternalStorage="true"
android:resizeableActivity="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="${usesCleartextTraffic}"
android:supportsRtl="true">
<!-- Enable profiling in release builds (Android 10+) -->
<profileable android:shell="true" />
<!--
2018-12-16: Changed android:launchMode from "singleInstance" to "singleTop" for Firebase notification
Previously singleInstance was necessary to prevent multiple instance of the RN app from running at the same time, but maybe no longer needed.

View File

@@ -1,65 +0,0 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("joplin")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
whisperWrapper.cpp
utils/WhisperSession.cpp
utils/findLongestSilence.cpp
utils/findLongestSilence_test.cpp
)
set(WHISPER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../../../vendor/whisper.cpp)
# Based on the Whisper.cpp Android example:
set(SHARED_FLAGS "-O3 ")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SHARED_FLAGS} ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SHARED_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections")
# Whisper: See https://stackoverflow.com/a/76290722
add_subdirectory(${WHISPER_LIB_DIR} ./whisper)
# Directories for header files
target_include_directories(
${CMAKE_PROJECT_NAME}
PUBLIC
${PROJECT_BASE_DIR}/shared
${WHISPER_LIB_DIR}/include
)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
whisper
# List libraries link to the target library
android
log
)

View File

@@ -1,151 +0,0 @@
// Write C++ code here.
//
// Do not forget to dynamically load the C++ library into your application.
//
// For instance,
//
// In MainActivity.java:
// static {
// System.loadLibrary("joplin");
// }
//
// Or, in MainActivity.kt:
// companion object {
// init {
// System.loadLibrary("joplin")
// }
// }
#include <jni.h>
#include <memory>
#include <string>
#include <sstream>
#include <android/log.h>
#include "whisper.h"
#include "utils/WhisperSession.h"
#include "utils/androidUtil.h"
#include "utils/findLongestSilence_test.h"
void log_android(enum ggml_log_level level, const char* message, void* user_data) {
android_LogPriority priority = level == 4 ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO;
__android_log_print(priority, "Whisper::JNI::cpp", "%s", message);
}
jstring stringToJava(JNIEnv *env, const std::string& source) {
return env->NewStringUTF(source.c_str());
}
std::string stringToCXX(JNIEnv *env, jstring jString) {
const char *jStringChars = env->GetStringUTFChars(jString, nullptr);
std::string result { jStringChars };
env->ReleaseStringUTFChars(jString, jStringChars);
return result;
}
void throwException(JNIEnv *env, const std::string& message) {
jclass errorClass = env->FindClass("java/lang/Exception");
env->ThrowNew(errorClass, message.c_str());
}
extern "C"
JNIEXPORT jlong JNICALL
Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_init(
JNIEnv *env,
jobject thiz,
jstring modelPath,
jstring language,
jstring prompt,
jboolean useShortAudioContext
) {
whisper_log_set(log_android, nullptr);
try {
auto *pSession = new WhisperSession(
stringToCXX(env, modelPath), stringToCXX(env, language), stringToCXX(env, prompt), useShortAudioContext
);
return (jlong) pSession;
} catch (const std::exception& exception) {
LOGW("Failed to init whisper: %s", exception.what());
throwException(env, exception.what());
return 0;
}
}
extern "C"
JNIEXPORT void JNICALL
Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_free(JNIEnv *env, jobject thiz,
jlong pointer) {
delete reinterpret_cast<WhisperSession *>(pointer);
}
extern "C"
JNIEXPORT void JNICALL
Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_addAudio(JNIEnv *env,
jobject thiz,
jlong pointer,
jfloatArray audio_data) {
auto *pSession = reinterpret_cast<WhisperSession *> (pointer);
jfloat *pAudioData = env->GetFloatArrayElements(audio_data, nullptr);
jsize lenAudioData = env->GetArrayLength(audio_data);
std::string result;
try {
pSession->addAudio(pAudioData, lenAudioData);
} catch (const std::exception& exception) {
LOGW("Failed to add to audio buffer: %s", exception.what());
throwException(env, exception.what());
}
// JNI_ABORT: "free the buffer without copying back the possible changes", pass 0 to copy
// changes (there should be no changes)
env->ReleaseFloatArrayElements(audio_data, pAudioData, JNI_ABORT);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_transcribeNextChunk(JNIEnv *env,
jobject thiz,
jlong pointer) {
auto *pSession = reinterpret_cast<WhisperSession *> (pointer);
std::string result;
try {
result = pSession->transcribeNextChunk();
} catch (const std::exception& exception) {
LOGW("Failed to run whisper: %s", exception.what());
throwException(env, exception.what());
return nullptr;
}
return stringToJava(env, result);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_transcribeRemaining(JNIEnv *env,
jobject thiz,
jlong pointer) {
auto *pSession = reinterpret_cast<WhisperSession *> (pointer);
std::string result;
try {
result = pSession->transcribeAll();
} catch (const std::exception& exception) {
LOGW("Failed to run whisper: %s", exception.what());
throwException(env, exception.what());
return nullptr;
}
return stringToJava(env, result);
}
extern "C"
JNIEXPORT void JNICALL
Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_runTests(JNIEnv *env, jobject thiz) {
try {
findLongestSilence_test();
} catch (const std::exception& exception) {
LOGW("Failed to run tests: %s", exception.what());
throwException(env, exception.what());
}
}

View File

@@ -6,37 +6,36 @@ import expo.modules.ReactNativeHostWrapper
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.common.ReleaseLevel
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import net.cozic.joplin.audio.SpeechToTextPackage
import net.cozic.joplin.versioninfo.SystemVersionInformationPackage
import net.cozic.joplin.share.SharePackage
import net.cozic.joplin.ssl.SslPackage
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
this,
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
add(SharePackage())
add(SslPackage())
add(SystemVersionInformationPackage())
add(SpeechToTextPackage())
}
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
})
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
})
override val reactHost: ReactHost
get() = ReactNativeHostWrapper.createReactHost(this.applicationContext, reactNativeHost)
@@ -44,16 +43,17 @@ class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
try {
DefaultNewArchitectureEntryPoint.releaseLevel = ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
} catch (e: IllegalArgumentException) {
DefaultNewArchitectureEntryPoint.releaseLevel = ReleaseLevel.STABLE
}
ApplicationLifecycleDispatcher.onApplicationCreate(this)
}
loadReactNative(this)
ApplicationLifecycleDispatcher.onApplicationCreate(this)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
}
}

View File

@@ -1,5 +0,0 @@
package net.cozic.joplin.audio
class InvalidSessionIdException(id: Int) : IllegalArgumentException("Invalid session ID $id") {
}

View File

@@ -1,64 +0,0 @@
package net.cozic.joplin.audio
import java.io.Closeable
class NativeWhisperLib(
modelPath: String,
languageCode: String,
prompt: String,
shortAudioContext: Boolean,
) : Closeable {
companion object {
init {
System.loadLibrary("joplin")
}
external fun runTests(): Unit;
// TODO: The example whisper.cpp project transfers pointers as Longs to the Kotlin code.
// This seems unsafe. Try changing how this is managed.
private external fun init(modelPath: String, languageCode: String, prompt: String, shortAudioContext: Boolean): Long;
private external fun free(pointer: Long): Unit;
private external fun addAudio(pointer: Long, audioData: FloatArray): Unit;
private external fun transcribeNextChunk(pointer: Long): String;
private external fun transcribeRemaining(pointer: Long): String;
}
private var closed = false
private val pointer: Long = init(modelPath, languageCode, prompt, shortAudioContext)
fun addAudio(audioData: FloatArray) {
if (closed) {
throw Exception("Cannot add audio data to a closed session")
}
Companion.addAudio(pointer, audioData)
}
fun transcribeNextChunk(): String {
if (closed) {
throw Exception("Cannot transcribe using a closed session")
}
return Companion.transcribeNextChunk(pointer)
}
fun transcribeRemaining(): String {
if (closed) {
throw Exception("Cannot transcribeAll using a closed session")
}
return Companion.transcribeRemaining(pointer)
}
override fun close() {
if (closed) {
throw Exception("Cannot close a whisper session twice")
}
closed = true
free(pointer)
}
}

View File

@@ -1,62 +0,0 @@
package net.cozic.joplin.audio
import android.content.Context
import android.util.Log
import java.io.Closeable
class SpeechToTextConverter(
modelPath: String,
locale: String,
prompt: String,
useShortAudioCtx: Boolean,
recorderFactory: AudioRecorderFactory,
context: Context,
) : Closeable {
private val recorder = recorderFactory(context)
private val languageCode = Regex("_.*").replace(locale, "")
private var whisper = NativeWhisperLib(
modelPath,
languageCode,
prompt,
useShortAudioCtx,
)
fun start() {
recorder.start()
}
private fun convert(data: FloatArray): String {
Log.d("Whisper", "Pre-transcribe data of size ${data.size}")
whisper.addAudio(data)
val result = whisper.transcribeNextChunk()
Log.d("Whisper", "Post transcribe. Got $result")
return result;
}
fun dropFirstSeconds(seconds: Double) {
Log.i("Whisper", "Drop first seconds $seconds")
recorder.dropFirstSeconds(seconds)
}
val bufferLengthSeconds: Double get() = recorder.bufferLengthSeconds
fun convertNext(seconds: Double): String {
val buffer = recorder.pullNextSeconds(seconds)
val result = convert(buffer)
dropFirstSeconds(seconds)
return result
}
// Converts as many seconds of buffered data as possible, without waiting
fun convertRemaining(): String {
val buffer = recorder.pullAvailable()
whisper.addAudio(buffer)
return whisper.transcribeRemaining()
}
override fun close() {
Log.d("Whisper", "Close")
recorder.close()
whisper.close()
}
}

View File

@@ -1,87 +0,0 @@
package net.cozic.joplin.audio
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.uimanager.ViewManager
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class SpeechToTextPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf<NativeModule>(SpeechToTextModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
class SpeechToTextModule(
private var context: ReactApplicationContext,
) : ReactContextBaseJavaModule(context), LifecycleEventListener {
private val executorService: ExecutorService = Executors.newFixedThreadPool(1)
private val sessionManager = SpeechToTextSessionManager(executorService)
override fun getName() = "SpeechToTextModule"
override fun onHostResume() { }
override fun onHostPause() { }
override fun onHostDestroy() { }
@ReactMethod
fun runTests(promise: Promise) {
try {
NativeWhisperLib.runTests()
promise.resolve(true)
} catch (exception: Throwable) {
promise.reject(exception)
}
}
@ReactMethod
fun openSession(modelPath: String, locale: String, prompt: String, useShortAudioCtx: Boolean, promise: Promise) {
val appContext = context.applicationContext
try {
val sessionId = sessionManager.openSession(modelPath, locale, prompt, useShortAudioCtx, appContext)
promise.resolve(sessionId)
} catch (exception: Throwable) {
promise.reject(exception)
}
}
@ReactMethod
fun startRecording(sessionId: Int, promise: Promise) {
sessionManager.startRecording(sessionId, promise)
}
@ReactMethod
fun getBufferLengthSeconds(sessionId: Int, promise: Promise) {
sessionManager.getBufferLengthSeconds(sessionId, promise)
}
@ReactMethod
fun dropFirstSeconds(sessionId: Int, duration: Double, promise: Promise) {
sessionManager.dropFirstSeconds(sessionId, duration, promise)
}
@ReactMethod
fun convertNext(sessionId: Int, duration: Double, promise: Promise) {
sessionManager.convertNext(sessionId, duration, promise)
}
@ReactMethod
fun convertAvailable(sessionId: Int, promise: Promise) {
sessionManager.convertAvailable(sessionId, promise)
}
@ReactMethod
fun closeSession(sessionId: Int, promise: Promise) {
sessionManager.closeSession(sessionId, promise)
}
}
}

View File

@@ -1,111 +0,0 @@
package net.cozic.joplin.audio
import android.content.Context
import com.facebook.react.bridge.Promise
import java.util.concurrent.Executor
import java.util.concurrent.locks.ReentrantLock
class SpeechToTextSession (
val converter: SpeechToTextConverter
) {
val mutex = ReentrantLock()
}
class SpeechToTextSessionManager(
private var executor: Executor,
) {
private val sessions: MutableMap<Int, SpeechToTextSession> = mutableMapOf()
private var nextSessionId: Int = 0
fun openSession(
modelPath: String,
locale: String,
prompt: String,
useShortAudioCtx: Boolean,
context: Context,
): Int {
val sessionId = nextSessionId++
sessions[sessionId] = SpeechToTextSession(
SpeechToTextConverter(
modelPath, locale, prompt, useShortAudioCtx, recorderFactory = AudioRecorder.factory, context,
)
)
return sessionId
}
private fun getSession(id: Int): SpeechToTextSession {
return sessions[id] ?: throw InvalidSessionIdException(id)
}
private fun concurrentWithSession(
id: Int,
callback: (session: SpeechToTextSession)->Unit,
) {
executor.execute {
val session = getSession(id)
session.mutex.lock()
try {
callback(session)
} finally {
session.mutex.unlock()
}
}
}
private fun concurrentWithSession(
id: Int,
onError: (error: Throwable)->Unit,
callback: (session: SpeechToTextSession)->Unit,
) {
return concurrentWithSession(id) { session ->
try {
callback(session)
} catch (error: Throwable) {
onError(error)
}
}
}
fun startRecording(sessionId: Int, promise: Promise) {
this.concurrentWithSession(sessionId, promise::reject) { session ->
session.converter.start()
promise.resolve(null)
}
}
// Left-shifts the recording buffer by [duration] seconds
fun dropFirstSeconds(sessionId: Int, duration: Double, promise: Promise) {
this.concurrentWithSession(sessionId, promise::reject) { session ->
session.converter.dropFirstSeconds(duration)
promise.resolve(sessionId)
}
}
fun getBufferLengthSeconds(sessionId: Int, promise: Promise) {
this.concurrentWithSession(sessionId, promise::reject) { session ->
promise.resolve(session.converter.bufferLengthSeconds)
}
}
// Waits for the next [duration] seconds to become available, then converts
fun convertNext(sessionId: Int, duration: Double, promise: Promise) {
this.concurrentWithSession(sessionId, promise::reject) { session ->
val result = session.converter.convertNext(duration)
promise.resolve(result)
}
}
// Converts all available recorded data
fun convertAvailable(sessionId: Int, promise: Promise) {
this.concurrentWithSession(sessionId, promise::reject) { session ->
val result = session.converter.convertRemaining()
promise.resolve(result)
}
}
fun closeSession(sessionId: Int, promise: Promise) {
this.concurrentWithSession(sessionId) { session ->
session.converter.close()
promise.resolve(null)
}
}
}

View File

@@ -2,14 +2,14 @@
buildscript {
ext {
buildToolsVersion = "35.0.0"
buildToolsVersion = "36.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
kotlinVersion = "2.1.20"
}
repositories {
google()

View File

@@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryEr
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
@@ -34,12 +34,17 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
# Use this property to enable edge-to-edge display support.
# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=true
# To fix this error:
#
# > Failed to transform bcprov-jdk15on-1.68.jar

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

View File

@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,9 +0,0 @@
whisper.cpp/.gitmodules
whisper.cpp/scripts/
whisper.cpp/samples/
whisper.cpp/tests/
whisper.cpp/models/
whisper.cpp/examples/
whisper.cpp/.*/
whisper.cpp/bindings/
whisper.cpp/**/*.Dockerfile

View File

@@ -1,4 +1,7 @@
{
"name": "Joplin",
"displayName": "Joplin"
}
"displayName": "Joplin",
"plugins": [
"@react-native-community/datetimepicker"
]
}

View File

@@ -44,7 +44,7 @@ const useStyles = (theme: ThemeStyle) => {
},
contentContainer: {
padding: 20,
paddingBottom: 14,
paddingBottom: 14 + safeAreaPadding.paddingBottom,
gap: 8,
flexDirection: 'row',
flexWrap: 'wrap',

View File

@@ -438,9 +438,8 @@ const useInputEventHandlers = ({
const onSubmit = useCallback(() => {
if (selectedResult) {
onItemSelected(selectedResult, selectedIndex);
setSearch('');
}
}, [onItemSelected, selectedResult, selectedIndex, setSearch]);
}, [onItemSelected, selectedResult, selectedIndex]);
// For now, onKeyPress only works on web.
// See https://github.com/react-native-community/discussions-and-proposals/issues/249

View File

@@ -13,6 +13,7 @@ import shim from '@joplin/lib/shim';
import Logger from '@joplin/utils/Logger';
import { Props, WebViewControl } from './types';
import useCss from './utils/useCss';
import { Platform } from 'react-native';
const logger = Logger.create('ExtendedWebView');
@@ -141,7 +142,8 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
onLoadEnd={props.onLoadEnd}
onContentProcessDidTerminate={refreshWebViewAfterCrash}
onRenderProcessGone={refreshWebViewAfterCrash}
decelerationRate='normal'
// See https://github.com/react-native-webview/react-native-webview/issues/3814
decelerationRate={Platform.OS === 'ios' ? 'normal' : undefined}
/>
);
};

View File

@@ -2,7 +2,8 @@ import * as React from 'react';
import { Store } from 'redux';
import { AppState } from '../utils/types';
import TestProviderStack from './testing/TestProviderStack';
import { switchClient, setupDatabase, mockMobilePlatform, mockFetch, waitFor } from '@joplin/lib/testing/test-utils';
import { switchClient, setupDatabase, mockMobilePlatform, mockFetch } from '@joplin/lib/testing/test-utils';
import waitFor from '@joplin/lib/testing/waitFor';
import createMockReduxStore from '../utils/testing/createMockReduxStore';
import setupGlobalStore from '../utils/testing/setupGlobalStore';
import { act, fireEvent, render, screen } from '@testing-library/react-native';

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { KeyboardAvoidingViewProps, KeyboardAvoidingView as NativeKeyboardAvoidingView } from 'react-native';
import useKeyboardState from '../utils/hooks/useKeyboardState';
interface Props extends KeyboardAvoidingViewProps {}
const KeyboardAvoidingView: React.FC<Props> = ({ enabled, children, ...forwardedProps }) => {
const keyboardState = useKeyboardState();
enabled &&= (
// When the floating keyboard is enabled, the KeyboardAvoidingView can have a very small
// height. Don't use the KeyboardAvoidingView when the floating keyboard is enabled.
// See https://github.com/facebook/react-native/issues/29473
!keyboardState.isFloatingKeyboard
);
return <NativeKeyboardAvoidingView
behavior='padding'
{...forwardedProps}
enabled={enabled}
>
{children}
</NativeKeyboardAvoidingView>;
};
export default KeyboardAvoidingView;

View File

@@ -1,12 +1,13 @@
import * as React from 'react';
import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
import { GestureResponderEvent, KeyboardAvoidingView, Modal, ModalProps, Platform, Pressable, ScrollView, ScrollViewProps, StyleSheet, View, ViewStyle } from 'react-native';
import { GestureResponderEvent, Modal, ModalProps, Platform, Pressable, ScrollView, ScrollViewProps, StyleSheet, View, ViewStyle } from 'react-native';
import FocusControl from './accessibility/FocusControl/FocusControl';
import { msleep, Second } from '@joplin/utils/time';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import { ModalState } from './accessibility/FocusControl/types';
import useSafeAreaPadding from '../utils/hooks/useSafeAreaPadding';
import { _ } from '@joplin/lib/locale';
import KeyboardAvoidingView from './KeyboardAvoidingView';
export interface ModalElementProps extends ModalProps {
children: React.ReactNode;
@@ -175,7 +176,7 @@ const ModalElement: React.FC<ModalElementProps> = ({
{...modalProps}
>
{scrollOverflow ? (
<KeyboardAvoidingView behavior='padding' style={styles.keyboardAvoidingView}>
<KeyboardAvoidingView style={styles.keyboardAvoidingView} enabled={true}>
<ScrollView
{...extraScrollViewProps}
style={[styles.modalScrollView, extraScrollViewProps.style]}

View File

@@ -61,7 +61,7 @@ const ProfileListItem: React.FC<ProfileItemProps> = ({ profile, profileConfig, s
}
};
const switchProfileMessage = _('To switch the profile, the app is going to close and you will need to restart it.');
const switchProfileMessage = _('To switch the profile, the app is going to restart.');
if (shim.mobilePlatform() === 'web') {
if (confirm(switchProfileMessage)) {
void doIt();

View File

@@ -688,16 +688,23 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
const menuComp =
!menuOptions.length || !showContextMenuButton ? null : (
<Menu themeId={this.props.themeId} options={menuOptions}>
<View style={contextMenuStyle} accessibilityLabel={_('Actions')}>
<Icon name="ionicon ellipsis-vertical" style={this.styles().contextMenuTrigger} accessibilityLabel={null}/>
<View style={contextMenuStyle}>
<Icon name="ionicon ellipsis-vertical" style={this.styles().contextMenuTrigger} accessibilityLabel={_('Actions')}/>
</View>
</Menu>
);
// Updating the state of this component can result in the left most element becoming hidden, so add a dummy as the first element to prevent this
// See https://github.com/laurent22/joplin/issues/14153
const zeroWidthSpacer = (
<View style={{ width: 0 }} pointerEvents="none"/>
);
return (
<View style={this.styles().outerContainer}>
<View style={this.styles().aboveHeader}/>
<View style={this.styles().innerContainer}>
{zeroWidthSpacer}
{sideMenuComp}
{backButtonComp}
{renderUndoButton()}

View File

@@ -7,6 +7,7 @@ import AccessibleView from './accessibility/AccessibleView';
import { _ } from '@joplin/lib/locale';
import useReduceMotionEnabled from '../utils/hooks/useReduceMotionEnabled';
import { themeStyle } from './global-style';
import useSafeAreaPadding from '../utils/hooks/useSafeAreaPadding';
export enum SideMenuPosition {
Left = 'left',
@@ -40,6 +41,8 @@ interface UseStylesProps {
const useStyles = ({ themeId, isLeftMenu, menuWidth, menuOpenFraction }: UseStylesProps) => {
const { height: windowHeight, width: windowWidth } = useWindowDimensions();
const safeAreaInsets = useSafeAreaPadding();
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
@@ -53,7 +56,7 @@ const useStyles = ({ themeId, isLeftMenu, menuWidth, menuOpenFraction }: UseStyl
contentOuterWrapper: {
flexGrow: 1,
flexShrink: 1,
width: windowWidth,
width: '100%',
height: windowHeight,
transform: [{
translateX: menuOpenFraction.interpolate({
@@ -71,11 +74,18 @@ const useStyles = ({ themeId, isLeftMenu, menuWidth, menuOpenFraction }: UseStyl
flexShrink: 1,
},
menuWrapper: {
backgroundColor: theme.backgroundColor,
position: 'absolute',
top: 0,
bottom: 0,
width: menuWidth,
paddingLeft: isLeftMenu ? safeAreaInsets.paddingLeft : 0,
paddingRight: isLeftMenu ? 0 : safeAreaInsets.paddingRight,
paddingTop: safeAreaInsets.paddingTop,
paddingBottom: safeAreaInsets.paddingBottom,
// In React Native, RTL replaces `left` with `right` and `right` with `left`.
// As such, we need to reverse the normal direction in RTL mode.
...(isLeftMenu === !I18nManager.isRTL ? {
@@ -107,7 +117,7 @@ const useStyles = ({ themeId, isLeftMenu, menuWidth, menuOpenFraction }: UseStyl
width: windowWidth,
},
});
}, [themeId, isLeftMenu, windowWidth, windowHeight, menuWidth, menuOpenFraction]);
}, [themeId, isLeftMenu, windowWidth, windowHeight, menuWidth, menuOpenFraction, safeAreaInsets]);
};
interface UseAnimationsProps {

View File

@@ -2,13 +2,16 @@ import * as React from 'react';
import { connect } from 'react-redux';
import NotesScreen from './screens/Notes/Notes';
import SearchScreen from './screens/SearchScreen';
import { KeyboardAvoidingView, Platform, View } from 'react-native';
import { Platform, View, StyleSheet } from 'react-native';
import { AppState } from '../utils/types';
import { themeStyle } from './global-style';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import useKeyboardState from '../utils/hooks/useKeyboardState';
import usePrevious from '@joplin/lib/hooks/usePrevious';
import FeedbackBanner from './FeedbackBanner';
import { Theme } from '@joplin/lib/themes/type';
import { useMemo } from 'react';
import KeyboardAvoidingView from './KeyboardAvoidingView';
interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -20,6 +23,15 @@ interface Props {
themeId: number;
}
const useStyles = (theme: Theme) => {
return useMemo(() => {
return StyleSheet.create({
keyboardAvoidingView: { flex: 1, backgroundColor: theme.backgroundColor },
});
}, [theme]);
};
const AppNavComponent: React.FC<Props> = (props) => {
const keyboardState = useKeyboardState();
const safeAreaPadding = useSafeAreaInsets();
@@ -50,20 +62,18 @@ const AppNavComponent: React.FC<Props> = (props) => {
const searchScreenLoaded = searchScreenVisible || (previousRouteName === 'Search' && route.routeName === 'Note');
const theme = themeStyle(props.themeId);
const style = { flex: 1, backgroundColor: theme.backgroundColor };
// When the floating keyboard is enabled, the KeyboardAvoidingView can have a very small
// height. Don't use the KeyboardAvoidingView when the floating keyboard is enabled.
// See https://github.com/facebook/react-native/issues/29473
const keyboardAvoidingViewEnabled = !keyboardState.isFloatingKeyboard;
const autocompletionBarPadding = Platform.OS === 'ios' && keyboardState.keyboardVisible ? safeAreaPadding.top : 0;
const styles = useStyles(theme);
const autocompletionBarPadding = keyboardState.keyboardVisible ? safeAreaPadding.top : 0;
return (
<KeyboardAvoidingView
enabled={keyboardAvoidingViewEnabled}
behavior={Platform.OS === 'ios' ? 'padding' : null}
style={style}
style={styles.keyboardAvoidingView}
enabled={
// Workaround: On Android 15 and 16, the main app content seems to auto-resize when the keyboard is shown.
// On earlier Android versions (and in modals), this does not seem to be the case.
(Platform.OS === 'android' && Platform.Version < 35)
|| Platform.OS === 'ios'
}
>
<NotesScreen visible={notesScreenVisible} />
{searchScreenLoaded && <SearchScreen visible={searchScreenVisible} />}

View File

@@ -16,6 +16,7 @@ import usePrevious from '@joplin/lib/hooks/usePrevious';
import PlatformImplementation from '../../services/plugins/PlatformImplementation';
import AccessibleView from '../accessibility/AccessibleView';
import useOnDevPluginsUpdated from './utils/useOnDevPluginsUpdated';
import { ViewStyle } from 'react-native';
const logger = Logger.create('PluginRunnerWebView');
@@ -98,6 +99,17 @@ interface Props {
themeId: number;
}
// The WebView needs to have a non-zero size to be rendered by
// newer React Native versions. This style makes it visually hidden.
const hiddenStyle: ViewStyle = {
width: 1,
height: 1,
opacity: 0,
position: 'absolute',
top: 0,
zIndex: -1,
};
const PluginRunnerWebViewComponent: React.FC<Props> = props => {
const webviewRef = useRef<WebViewControl>(null);
@@ -189,7 +201,7 @@ const PluginRunnerWebViewComponent: React.FC<Props> = props => {
};
return (
<AccessibleView style={{ display: 'none' }} inert={true}>
<AccessibleView style={hiddenStyle} inert={true}>
{renderWebView()}
</AccessibleView>
);

View File

@@ -5,7 +5,7 @@ import { act, fireEvent, render, screen, userEvent, waitFor } from '../../../uti
import NoteScreen from './Note';
import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, supportDir, synchronizerStart, resourceFetcher, runWithFakeTimers } from '@joplin/lib/testing/test-utils';
import { waitFor as waitForWithRealTimers } from '@joplin/lib/testing/test-utils';
import waitForWithRealTimers from '@joplin/lib/testing/waitFor';
import Note from '@joplin/lib/models/Note';
import { AppState } from '../../../utils/types';
import { Store } from 'redux';

View File

@@ -1,30 +1,43 @@
const React = require('react');
import * as React from 'react';
const { View, StyleSheet } = require('react-native');
const { connect } = require('react-redux');
const Folder = require('@joplin/lib/models/Folder').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const { ScreenHeader } = require('../ScreenHeader');
const { BaseScreenComponent } = require('../base-screen');
const shim = require('@joplin/lib/shim').default;
const { _ } = require('@joplin/lib/locale');
const { default: FolderPicker } = require('../FolderPicker');
const TextInput = require('../TextInput').default;
import { View, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import Folder from '@joplin/lib/models/Folder';
import BaseModel from '@joplin/lib/BaseModel';
import { ScreenHeader } from '../ScreenHeader';
import { BaseScreenComponent } from '../base-screen';
import shim from '@joplin/lib/shim';
import { _ } from '@joplin/lib/locale';
import FolderPicker from '../FolderPicker';
import TextInput from '../TextInput';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { AppState } from '../../utils/types';
import { Dispatch } from 'redux';
class FolderScreenComponent extends BaseScreenComponent {
static navigationOptions() {
return { header: null };
}
interface Props {
folderId: string;
selectedFolderId: string;
themeId: number;
folders: FolderEntity[];
dispatch: Dispatch;
}
constructor() {
super();
interface State {
folder: FolderEntity;
lastSavedFolder: FolderEntity|null;
}
class FolderScreenComponent extends BaseScreenComponent<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
folder: Folder.new(),
lastSavedFolder: null,
};
}
UNSAFE_componentWillMount() {
public override UNSAFE_componentWillMount() {
if (!this.props.folderId) {
const folder = Folder.new();
this.setState({
@@ -33,7 +46,7 @@ class FolderScreenComponent extends BaseScreenComponent {
});
} else {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
Folder.load(this.props.folderId).then(folder => {
void Folder.load(this.props.folderId).then(folder => {
this.setState({
folder: folder,
lastSavedFolder: { ...folder },
@@ -42,38 +55,40 @@ class FolderScreenComponent extends BaseScreenComponent {
}
}
isModified() {
private isModified() {
if (!this.state.folder || !this.state.lastSavedFolder) return false;
const diff = BaseModel.diffObjects(this.state.folder, this.state.lastSavedFolder);
delete diff.type_;
return !!Object.getOwnPropertyNames(diff).length;
}
folderComponent_change(propName, propValue) {
private folderComponent_change(propName: keyof FolderEntity, propValue: string) {
this.setState((prevState) => {
const folder = { ...prevState.folder };
folder[propName] = propValue;
const folder = {
...prevState.folder,
[propName]: propValue,
};
return { folder: folder };
});
}
title_changeText(text) {
private title_changeText(text: string) {
this.folderComponent_change('title', text);
}
parent_changeValue(parent) {
private parent_changeValue(parent: string) {
this.folderComponent_change('parent_id', parent);
}
async saveFolderButton_press() {
private async saveFolderButton_press() {
let folder = { ...this.state.folder };
try {
if (folder.id && !(await Folder.canNestUnder(folder.id, folder.parent_id))) throw new Error(_('Cannot move notebook to this location'));
folder = await Folder.save(folder, { userSideValidation: true });
} catch (error) {
shim.showErrorDialog(_('The notebook could not be saved: %s', error.message));
void shim.showErrorDialog(_('The notebook could not be saved: %s', error.message));
return;
}
@@ -89,7 +104,7 @@ class FolderScreenComponent extends BaseScreenComponent {
});
}
render() {
public override render() {
const saveButtonDisabled = !this.isModified() || !this.state.folder.title;
return (
@@ -101,7 +116,7 @@ class FolderScreenComponent extends BaseScreenComponent {
autoFocus={true}
value={this.state.folder.title}
onChangeText={text => this.title_changeText(text)}
disabled={this.state.folder.encryption_applied}
editable={!this.state.folder.encryption_applied}
/>
<View style={styles.folderPickerContainer}>
<FolderPicker
@@ -120,7 +135,7 @@ class FolderScreenComponent extends BaseScreenComponent {
}
}
const FolderScreen = connect(state => {
export default connect((state: AppState) => {
return {
folderId: state.selectedFolderId,
themeId: state.settings.theme,
@@ -138,4 +153,3 @@ const styles = StyleSheet.create({
},
});
module.exports = { FolderScreen };

View File

@@ -6,16 +6,14 @@
// So there's basically still a one way flux: React => SQLite => Redux => React
import './utils/initReact';
import './utils/polyfills';
import Root from './root';
import { LogBox } from 'react-native';
import { registerRootComponent } from 'expo';
// Allows loading image assets. See https://github.com/expo/expo/issues/31240
import 'expo-asset';
import shim from '@joplin/lib/shim';
shim.setReact(require('react'));
const Root = require('./root').default;
// Seems JavaScript developers love adding warnings everywhere, even when these warnings can't be fixed
// or don't really matter. Because we want important warnings to actually be fixed, we disable

View File

@@ -1,4 +1,5 @@
import './utils/polyfills';
import './utils/initReact';
import { AppRegistry } from 'react-native';
import Root from './root';
import Setting from '@joplin/lib/models/Setting';

View File

@@ -35,6 +35,8 @@
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>

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