1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-29 23:48:19 +02:00

Compare commits

...

330 Commits

Author SHA1 Message Date
Laurent Cozic
c44aad544e update 2025-11-06 17:54:15 +01:00
Laurent Cozic
996a0894ae Chore: Fixed Postgres tool path for new Homebrew version 2025-11-06 17:50:59 +01:00
Laurent Cozic
66fa3fc808 Server: Remove query optimisation that now seems to be slower with newer versions of Postgres 2025-11-06 17:12:45 +01:00
renovate[bot]
dab55daf95 Update dependency prosemirror-model to v1.25.3 (#13623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 02:16:07 +00:00
summoner
7f1c31e03f All: Translation: Update hu_HU.po (#13620) 2025-11-04 15:42:36 -05:00
cedecode
0a8255f091 All: Translation: Update de_DE.po (#13618) 2025-11-04 15:41:04 -05:00
Helmut K. C. Tessarek
9f3e6650a9 Update translations 2025-11-03 17:23:28 -05:00
mrjo118
4a17da3df5 All: Fixes #13531: When creating a conflict, ensure the latest note contents are used to create the conflict (#13552) 2025-11-03 20:21:05 +01:00
Henry Heino
2c4f0d4d8c Desktop: Fixes #13574: Fix crash when opening the legacy Markdown editor (#13576) 2025-11-03 20:12:39 +01:00
Henry Heino
9c1c2fb0d4 Chore: Desktop: Enable source maps for error reporting by default (#13577) 2025-11-03 20:12:24 +01:00
Henry Heino
2332e4bf62 Desktop: Fixes #13579: Rich Text Editor: Make cursor jump during editing less likely (#13581) 2025-11-03 20:11:45 +01:00
Henry Heino
a488ac1b27 Desktop: Fixes #13177: Location: Remove geoplugin.net from location providers (#13583) 2025-11-03 20:11:37 +01:00
Henry Heino
6daa41ca66 All: Fixes #13291: Improve performance of item deserialization (#13585) 2025-11-03 20:11:21 +01:00
Henry Heino
cc9517f1a2 Desktop: Resolves #13586: Preserve scroll when switching between Markdown and Rich Text Editors (#13587) 2025-11-03 20:11:12 +01:00
Henry Heino
200a471e55 Chore: OneNote importer: Remove unused dependency (#13590) 2025-11-03 12:21:03 +01:00
renovate[bot]
c21d37bd91 Update dependency @types/serviceworker to v0.0.149 (#13604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 12:20:50 +01:00
renovate[bot]
e36cd0e60b Update dependency mermaid to v11.8.1 (#13607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 21:16:04 +00:00
VortexP
871f55bf11 All: Translation: Update fi_FI.po (#13605) 2025-11-02 16:13:27 -05:00
renovate[bot]
22c9fed663 Update dependency mermaid to v11.8.0 (#13589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 20:14:11 +01:00
renovate[bot]
ea362d7a82 Update dependency @electron/remote to v2.1.3 (#13594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 20:13:54 +01:00
renovate[bot]
9ae9347f89 Update eslint (#13597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 20:28:29 +00:00
Joplin Bot
ae8bb902f9 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-11-01 01:50:23 +00:00
renovate[bot]
90eeec23de Update eslint (#13595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 01:49:05 +00:00
Henry Heino
474fd094c4 Chore: Update licenses.md (#13582) 2025-10-31 10:28:04 +01:00
renovate[bot]
937d8fa4f7 Update dependency react-native-share to v12.1.2 (#13570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 12:10:11 +01:00
renovate[bot]
45c9844616 Update dependency @types/serviceworker to v0.0.148 (#13568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 10:49:02 +01:00
Joplin Bot
12b8ef5a54 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-29 18:41:30 +00:00
mrjo118
18f72c224e Mobile: Fixes #13151: Reset the state of undo and redo buttons when switching editor (#13505) 2025-10-29 18:22:56 +01:00
mrjo118
7ca3aaa83f Web: Fixes #13241: Find and replace toolbar in the note editor is not sized correctly (#13559) 2025-10-29 18:21:30 +01:00
mrjo118
04b1443e5a Mobile: Make title field work with very long text (#13566) 2025-10-29 18:20:56 +01:00
mrjo118
c461741778 All: Fixes #13319: Treat unclosed quotes as fully quoted search terms, to prevent malformed match expression error (#13564) 2025-10-29 18:19:38 +01:00
renovate[bot]
2865b0a803 Update dependency follow-redirects to v1.15.11 (#13565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 17:58:37 +01:00
Laurent Cozic
21e49be22f Doc: Fixed order of tags in spellcheck document 2025-10-29 17:56:40 +01:00
Laurent Cozic
fef761cbab Doc: Added documentation to setup Joplin Server with Keycloak to test SAML auth 2025-10-29 17:55:07 +01:00
renovate[bot]
c15a353dc2 Update dependency react-native-safe-area-context to v5.5.2 (#13496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-29 14:00:36 +01:00
Laurent Cozic
ffb32766c1 Desktop release v3.5.6 2025-10-29 13:45:27 +01:00
Henry Heino
038908550e Chore: Desktop: Share folder dialog: Remove duplicate "refreshShares" call (#13535) 2025-10-29 13:43:56 +01:00
Henry Heino
42f59134ae Desktop: Fixes #13549: OneNote importer: Task lists: Fix checkbox sizes and accessibility (#13558) 2025-10-29 13:43:48 +01:00
Laurent Cozic
fc0014c0b5 All: Open the connection screen when a SAML session has expired 2025-10-29 13:42:11 +01:00
Laurent Cozic
42d8df3036 Desktop, Cli, Mobile: Ensure that sync process ends up properly when Joplin Server shares cannot be accessed 2025-10-29 13:42:11 +01:00
Frank Fesevur
1fad9ca1cc All: Translation: Update nl_NL.po (#13556) 2025-10-28 17:37:04 -04:00
Laurent Cozic
ae289be77a Server: Add support for DELETE_EXPIRED_SESSIONS_SCHEDULE to prevent auto-logout when using SAML login 2025-10-28 17:37:38 +01:00
Laurent Cozic
7f6bfe9c6e Doc: Clarifies that SAML does not support the API_BASE_URL 2025-10-28 17:21:57 +01:00
Laurent Cozic
ead4001b7a Revert "Server: Fix SAML routes to prevent cookie issues on redirect (#13557)"
This reverts commit a4556bf598.
2025-10-28 17:05:27 +01:00
Laurent Cozic
7b95ef72a0 Server: Fixes #13368: Cannot login with SAML when already logged in on the browser 2025-10-28 16:59:42 +01:00
Laurent Cozic
a4556bf598 Server: Fix SAML routes to prevent cookie issues on redirect (#13557) 2025-10-28 16:58:11 +01:00
mrjo118
8d6268dc92 Chore: Fix intermittent revision test failure (#13458) 2025-10-28 11:35:07 +01:00
Henry Heino
7ffcbdf60a Server: Fixes #13490: Make server less likely to generate non-unique SSO codes (#13501) 2025-10-28 11:34:22 +01:00
mrjo118
76989ddc45 Mobile: Fixes #13120: Fix truncated buttons on tag association screen (#13502) 2025-10-28 11:33:52 +01:00
mrjo118
1db1254617 Mobile: Fixes #12957: Avoid dismissing the keyboard when tapping markdown toolbar buttons with the title in focus (#13504) 2025-10-28 11:33:36 +01:00
mrjo118
9810bffddc Mobile: Fixes #11468: Ensure note list is re-ordered after updating a note opened via a search (#13506) 2025-10-28 11:28:59 +01:00
Henry Heino
b25e18107b Desktop,Mobile,Cli: Fixes #13522: Fix "cannot add an item as a child of a read-only item" error when updating share IDs (#13523) 2025-10-28 11:28:37 +01:00
Henry Heino
edc5fe5d1b Desktop: Allow adding and removing users from a share while a sync is in progress (#13529) 2025-10-28 11:26:46 +01:00
Henry Heino
7ffb44b3a4 Desktop: Fixes #13537: Fix adding a new user to a share creates an unused E2EE key (#13538) 2025-10-28 11:23:02 +01:00
Henry Heino
32f4c33140 Desktop: Disallow unsharing a folder while sharing is in progress (#13551) 2025-10-28 11:22:13 +01:00
renovate[bot]
1a7b09c91c Update dependency koa to v2.16.2 (#13554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 09:40:08 +00:00
renovate[bot]
e5bf8e0e58 Update dependency @types/node-fetch to v2.6.13 (#13553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 09:38:10 +00:00
renovate[bot]
94725c533c Update dependency @react-native-community/datetimepicker to v8.4.3 (#13547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 14:46:31 +00:00
Jozef Gaal
359c92b64f All: Translation: Update sk_SK.po (#13542) 2025-10-25 16:28:26 -04:00
renovate[bot]
8f8b8ad943 Update dependency dotenv to v16.6.1 (#13543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 14:36:57 +00:00
renovate[bot]
dd2f329fd5 Update dependency @types/serviceworker to v0.0.147 (#13541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 14:34:38 +00:00
mrjo118
813f594cb4 Mobile: Increase height of tag association screen to cater for a larger tag list area (#13521) 2025-10-25 14:13:15 +02:00
mrjo118
0e0ce49867 Mobile: Fixes #13108: Markdown toolbar overlaps with the gesture bar (#13533) 2025-10-25 14:12:37 +02:00
Henry Heino
e485d318b7 Desktop: Accessibility: Improve dialog keyboard handling (#13536) 2025-10-25 14:09:10 +02:00
renovate[bot]
4e82d81df1 Update dependency dotenv to v16.6.0 (#13539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 14:04:29 +02:00
Frank Fesevur
d5dbda201b All: Translation: Update nl_NL.po (#13519) 2025-10-23 15:29:45 -04:00
Joplin Bot
831258506b Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-23 12:52:21 +00:00
renovate[bot]
67f3329ecb Update dependency rate-limiter-flexible to v7.1.1 (#13517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 14:32:35 +02:00
renovate[bot]
ed7e6751f0 Update dependency react-native-share to v12.1.1 (#13516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 14:32:03 +02:00
mrjo118
35e69486d3 Mobile: Fixes #13457: Prevent toggling of multiline mode from clearing the title field on iOS (#13515) 2025-10-23 11:40:50 +02:00
Henry Heino
918c8830e0 Mobile: Fixes #13193: Fix Markdown toolbar (#13514) 2025-10-23 11:40:28 +02:00
renovate[bot]
c3b4a4b955 Update dependency rate-limiter-flexible to v7 (#13513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 11:38:21 +02:00
Laurent Cozic
44a14fabbd Doc: Updated sponsors 2025-10-23 10:19:53 +02:00
Frank Fesevur
49399cd1fa All: Translation: Update nl_NL.po (#13510) 2025-10-22 14:39:24 -04:00
Bartolomeo
fc4cd2e942 Server: Resolves #13147: Add LOG_LEVEL env var to control logging verbosity (#13503) 2025-10-22 12:26:02 +02:00
Arman Saga
cd6e457dc5 All: Translation: Update ru_RU.po (#13507) 2025-10-22 00:31:23 -04:00
Eric Duarte
2e9bf3a4e5 All: Translation: Update ca.po and es_ES.po (#13499) 2025-10-21 18:27:12 -04:00
Eric Duarte
547ceea4b0 All: Translation: Update es_ES.po (#13498) 2025-10-21 18:11:53 -04:00
renovate[bot]
776ff5e7ea Update dependency @fortawesome/react-fontawesome to v0.2.3 (#13500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 23:22:49 +02:00
Joplin Bot
2b3bac0d43 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-21 18:39:58 +00:00
Henry Heino
e48efe2e8d Desktop: OneNote importer: Resolve possible import failure related to unsupported formatting (#13495) 2025-10-21 17:19:56 +02:00
Laurent Cozic
5f6382fbc0 Merge branch 'release-3.4' into dev 2025-10-21 16:36:53 +02:00
Laurent Cozic
3d5d82081a iOS 13.4.4 2025-10-21 16:17:15 +02:00
Laurent Cozic
cff96b1306 iOS: Removed donation link since Apple is blocking the release because of this 2025-10-21 16:08:02 +02:00
Henry Heino
98c5a9c096 Desktop: Fixes #13481: Accessibility: Prevent sidebar header text from moving: Don't change the header icon on hover (#13482) 2025-10-21 00:46:52 +02:00
Henry Heino
e92430b3ed Desktop: Accessibility: Fix global keyboard shortcuts are ignored when the sidebar has focus (#13485) 2025-10-21 00:46:36 +02:00
renovate[bot]
848d1bfe64 Update dependency react-native-safe-area-context to v5.5.0 (#13487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 00:45:52 +02:00
Henry Heino
a386283530 Docs: Update OneNote import workflow (#13494) 2025-10-21 00:45:33 +02:00
Greg Oledzki
6101031269 Chore: Replace if with it in one of the tests (#13489) 2025-10-20 21:18:07 +02:00
Henry Heino
2fc3431f46 Web: Accessibility: Fix focus indicator is invisible for sync wizard options (#13492) 2025-10-20 21:12:06 +02:00
renovate[bot]
361fa2c768 Update dependency @types/serviceworker to v0.0.146 (#13484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 16:07:56 +00:00
Helmut K. C. Tessarek
f4a0a2466b Update translations 2025-10-19 14:42:12 -04:00
summoner
dbf225d6ad All: Translation: Update hu_HU.po (#13486) 2025-10-19 14:36:40 -04:00
Helmut K. C. Tessarek
4773a3831c fix: remove \r escape sequence from hu_HU.po 2025-10-18 15:46:49 -04:00
Mihai Vasiliu
6a19690581 All: Translation: Update ro_RO.po and ro_MD.po (#13479) 2025-10-18 15:24:06 -04:00
Arda Kılıçdağı
b7a771d58d All: Translation: Update tr_TR.po (#13478) 2025-10-18 15:23:53 -04:00
Jozef Gaal
e3daefb81a All: Translation: Update sk_SK.po (#13477) 2025-10-18 15:23:41 -04:00
Joplin Bot
b4253dace8 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-18 12:46:07 +00:00
renovate[bot]
fcf3be1be1 Update dependency esbuild to v0.25.8 (#13473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 12:41:05 +01:00
renovate[bot]
99aebbad81 Update dependency mermaid to v11.7.0 (#13476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 12:40:51 +01:00
Laurent Cozic
81b695a2a9 Chore: Exclude translation updates from changelog 2025-10-18 11:30:24 +01:00
Laurent Cozic
2dbba27357 Plugin Repo CLI v3.5.3 2025-10-18 10:12:33 +01:00
Laurent Cozic
8713cd2fd8 CLI v3.5.1 2025-10-18 10:11:50 +01:00
Laurent Cozic
d0fc4ea21b Lock file 2025-10-18 10:11:18 +01:00
Laurent Cozic
8bd62800ef Releasing sub-packages 2025-10-18 10:10:42 +01:00
Laurent Cozic
00f9e932e6 Desktop release v3.5.5 2025-10-18 09:53:34 +01:00
Helmut K. C. Tessarek
b8b55e4a55 Chore: Fix generated file line ending (#13459) 2025-10-18 09:50:37 +01:00
renovate[bot]
ef5be2ded3 Update dependency @types/node to v18.19.130 (#13463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 09:49:19 +01:00
Henry Heino
00702dde00 Desktop: OneNote importer: Improve file header validation (#13467) 2025-10-18 09:48:51 +01:00
Henry Heino
2a6af9bed9 Desktop: Accessibility: Allow jumping to notebooks by typing the initial letter or Home/End (#13469) 2025-10-18 09:48:44 +01:00
Henry Heino
c26fe0960b Web: Show sync wizard on first start (#13470) 2025-10-18 09:48:13 +01:00
Henry Heino
ab9d36fc08 Chore: Windows: Tests: Fix Rust OneNote importer tests fail (#13471) 2025-10-18 09:48:03 +01:00
Henry Heino
28eb53bd9f Desktop: OneNote importer: Support directly importing .one files and, on Windows, .onepkg files (#13474)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-18 09:47:35 +01:00
Laurent Cozic
3097c3e589 Desktop, Cli: Correctly import Evernote resources that do not have the encoding specified 2025-10-18 09:44:35 +01:00
renovate[bot]
08371ef718 Update dependency @types/serviceworker to v0.0.144 (#13475)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 08:19:27 +00:00
Laurent Cozic
561716efea Desktop, Cli: Fixed importing certain Evernote images that have invalid dimension attributes (#13472) 2025-10-18 09:17:22 +01:00
renovate[bot]
0d457d1bde Update dependency esbuild to v0.25.7 (#13461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 09:18:57 +01:00
renovate[bot]
8c11f17c93 Update dependency @types/node to v18.19.120 (#13460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 09:18:50 +01:00
renovate[bot]
f7a90ee1d2 Update dependency @types/serviceworker to v0.0.143 (#13449)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-17 09:18:08 +01:00
Eric Duarte
8822409f7c All: Translation: Update ca.po (#13462) 2025-10-17 03:54:42 -04:00
Eric Duarte
cd3e7f485a All: Translation: Update es_ES.po (#13454) 2025-10-16 17:25:46 -04:00
summoner
8d42b01d4f All: Translation: Update hu_HU.po (#13451) 2025-10-16 17:25:34 -04:00
Henry Heino
2c37197641 Desktop: Resolves #520: Save and restore the cursor position when switching between notes (#13447) 2025-10-16 14:56:38 +01:00
Henry Heino
c2c37b3741 Desktop: Fixes #13411: Fix header links only work if the note viewer is visible (#13442) 2025-10-16 12:10:01 +01:00
Henry Heino
3e770300dc Chore: Desktop: Add tool for resolving stack traces based on source maps (#13427) 2025-10-16 12:09:02 +01:00
Henry Heino
683291d5df Chore: Transcribe: Make logic for starting transcription workers safer (#13425) 2025-10-16 12:08:52 +01:00
Henry Heino
d239035417 Mobile: Resolves #13067: Rich Text Editor: Improve table support (#13413)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-16 12:08:44 +01:00
Henry Heino
5ef37d9de0 Desktop: Support importing .one files from OneNote 2016 (#13391) 2025-10-16 12:08:35 +01:00
mrjo118
1111bde017 All: Fixes #6517: Prevent Joplin from missing changes when syncing with file system or WebDAV (#13054) 2025-10-16 12:06:48 +01:00
Laurent Cozic
468cf00d77 Chore: Clean-up exclusion list in buildTranslation 2025-10-16 11:20:05 +01:00
Laurent Cozic
3c5b41b992 Chore: Fixed CI 2025-10-16 11:19:44 +01:00
Helmut K. C. Tessarek
5f66c51dba All: Update translations 2025-10-15 23:34:40 -04:00
k-santos
bfeaa67ec4 All: Translation: Update pt_BR.po (#13448) 2025-10-15 23:30:16 -04:00
renovate[bot]
348fd0333f Update dependency react-native-share to v12.1.0 (#13446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 21:19:13 +01:00
Henry Heino
51c4d6d6ef Desktop: Upgrade to Electron v37.7.0 (#13445) 2025-10-15 20:39:26 +01:00
Laurent Cozic
09d77a65e8 Plugin Repo CLI v3.5.1 2025-10-15 20:03:29 +01:00
Laurent Cozic
d1aec4a9f7 Plugin Generator release v3.5.1 2025-10-15 20:02:14 +01:00
Henry Heino
cab1525589 Chore: Plugin repository script: Fix certain plugins are not being published (#13443) 2025-10-15 20:00:29 +01:00
Henry Heino
a52f3fea9e Mobile: Resolves: #12823: Disable auto-search for 1-2 character searches (#13444)
Co-authored-by: pedr <pedr@users.noreply.github.com>
2025-10-15 20:00:02 +01:00
renovate[bot]
dfbd5eb8ed Update dependency expo to v53.0.20 (#13441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 14:18:13 +01:00
Laurent Cozic
3131f36033 Chore: Trying to fix random CI failure 2025-10-15 12:55:40 +01:00
renovate[bot]
dc5b2cfa21 Update dependency form-data to v4.0.4 (#13439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 09:42:15 +00:00
renovate[bot]
cad0f35fcc Update dependency expo-camera to v16.1.11 (#13438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 02:43:03 +00:00
renovate[bot]
38ea92ff57 Update dependency axios to v1.10.0 (#13431)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 14:35:53 +01:00
renovate[bot]
830deada22 Update dependency @types/serviceworker to v0.0.142 (#13434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 18:47:47 +01:00
renovate[bot]
38cd4033ea Update dependency @types/node to v18.19.119 (#13435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 13:36:57 +00:00
Shania
02900752d9 Doc: Missing hashtag in rich_text_editor.md (#13418) 2025-10-11 12:56:26 +01:00
renovate[bot]
091e9813b5 Update dependency @react-native/babel-preset to v0.80.1 (#13426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-11 01:23:33 +01:00
renovate[bot]
e61e5ac32a Update dependency @react-native/babel-preset to v0.80.0 (#13423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 21:28:24 +01:00
Joplin Bot
414970c9a1 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-10 18:39:00 +00:00
Laurent Cozic
d4ed49ff23 Doc: Clarify how to disable spellchecking on Markdown files 2025-10-10 17:48:37 +01:00
Laurent Cozic
8751d5d152 Doc: Add documentation for LDAP and SAML support in Joplin Server 2025-10-10 17:47:28 +01:00
Laurent Cozic
2e846fe15d Desktop release v3.5.4 2025-10-10 15:48:13 +01:00
Laurent Cozic
e54b7696d9 Chore: Prevent sign tool from being added to the Windows app 2025-10-10 15:48:01 +01:00
Laurent Cozic
553c61d628 Desktop release v3.5.3 2025-10-10 12:12:32 +01:00
Laurent Cozic
6a15db3a36 Chore: Implement SSL eSigner for Windows app signing (#13397) 2025-10-10 11:18:43 +01:00
Laurent Cozic
6f1d0a4b90 Chore: Disable time drift check on Joplin Server tests (#13420) 2025-10-10 11:18:18 +01:00
renovate[bot]
33b995672c Update dependency @playwright/test to v1.53.2 (#13421)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 11:18:04 +01:00
mrjo118
8ee46bb4e7 All: Avoid excessive data usage when automatically triggering another sync (#13261) 2025-10-10 09:36:42 +01:00
renovate[bot]
b35d9a64cf Update dependency @playwright/test to v1.53.0 (#13410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 22:36:48 +01:00
renovate[bot]
64ef74dd01 Update dependency @types/node to v18.19.118 (#13412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 22:36:39 +01:00
mrjo118
53035839a5 Desktop, Mobile: Fix historic issue whereby the first revision created for a note does not contain the original contents (#12674)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-09 22:35:08 +01:00
Henry Heino
af5287de99 Desktop: OCR: Fully disable the handwriting transcription backend when disabled in settings (#13072) 2025-10-09 22:21:49 +01:00
mrjo118
45a7554774 All: Fixes #11902: Ensure notebook conflicts do not delete child notes and notebooks when resolved (#13167)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-09 22:21:14 +01:00
Henry Heino
b06ffe3d25 Mobile,Desktop: Resolves #12343: Markdown editor search: Auto-scroll to the next match when the search changes (#13242)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-09 22:20:34 +01:00
mrjo118
53ea51b758 All: Fixes 12810: Ensure the sync shows an error when the server is down, when using a local WebDAV server (#13301) 2025-10-09 21:59:58 +01:00
Tom Chedmail
820acdc1f0 All: Fixes #13328: Implement the config check for Joplin Server with SAML enabled (#13360) 2025-10-09 21:49:18 +01:00
Henry Heino
ef0a79666e Desktop: OneNote importer: Simplify reporting import issues to the forum (#13409) 2025-10-09 21:47:31 +01:00
Henry Heino
d096a90c0e Chore: shim.mobilePlatform: Use a stronger return type (#13415) 2025-10-09 21:46:04 +01:00
renovate[bot]
191775310e Update dependency react-select to v5.10.2 (#13417)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 20:42:35 +00:00
renovate[bot]
4fc351b861 Update dependency @react-native-documents/picker to v10.1.5 (#13416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 04:55:42 +00:00
renovate[bot]
396decd26c Update dependency sharp to v0.34.3 (#13404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 13:45:43 +01:00
yingli-lab
01f8fa7bef Desktop: Fixes #13267: Fixed image load failure when path contains '#' (13267) (#13375) 2025-10-08 09:34:52 +01:00
Henry Heino
c40856ac7e Docs: Mobile: Add documentation for the mobile document scanner (#13387) 2025-10-08 09:33:45 +01:00
Henry Heino
d869cce413 Mobile: Document scanner: Add "Recognise text" checkbox (#13398) 2025-10-08 09:33:05 +01:00
Henry Heino
a83e8311d8 Server: Fixes #13400: Fix password fields are always disabled (#13401) 2025-10-08 09:32:24 +01:00
renovate[bot]
aa884fcb39 Update dependency @react-native-documents/picker to v10.1.4 (#13403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 06:58:50 +00:00
Henry Heino
be2a4c3e24 Chore: Correct license information for packages/onenote-converter (#13392) 2025-10-07 10:01:52 +01:00
renovate[bot]
520eec555b Update dependency @types/node to v18.19.117 (#13395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 05:08:10 +00:00
renovate[bot]
1281fdb9d2 Update dependency @types/node to v18.19.116 (#13394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 00:55:38 +01:00
renovate[bot]
6029353fd1 Update dependency react-native-webview to v13.15.0 (#13388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 17:49:23 +01:00
Laurent Cozic
8d1d1be79e Doc: Resolves #13370: Add documentation for user profiles (#13377) 2025-10-06 09:45:22 +01:00
bwat47
fd180ae0b4 Desktop: Add write() method to Plugin Clipboard API (#13348) 2025-10-06 09:31:27 +01:00
Laurent Cozic
6fdfd6eae6 Desktop: Resolves #13371: Open the Joplin Plugin web page when clicking on a plugin name (#13376) 2025-10-06 09:30:04 +01:00
Laurent Cozic
cd5bb575c8 Server: Resolves #13369: SAML users cannot modify their own profile at all (#13378) 2025-10-06 09:29:53 +01:00
Laurent Cozic
2df56530ae All: Remove Beta mention for Joplin Server (#13367) 2025-10-06 09:28:24 +01:00
Laurent Cozic
7987137470 Chore: Trying to migrate to macOS 15 on CI (#13366) 2025-10-06 09:28:13 +01:00
Manu Erwin
a1dcd2fd8f Doc: Update trash.md (#13383) 2025-10-06 08:40:10 +01:00
renovate[bot]
7826dc064a Update dependency @types/serviceworker to v0.0.141 (#13385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 08:39:30 +01:00
renovate[bot]
eedf083bfd Update dependency esbuild to v0.25.6 (#13380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-05 20:53:13 +00:00
renovate[bot]
d4aa1f8f8d Update dependency pg-boss to v10.3.2 (#13353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-05 17:57:51 +01:00
Henry Heino
738e749d51 Desktop: Fixes #13346: Fix startup error when a non-English locale is selected (#13347) 2025-10-04 16:08:23 +01:00
renovate[bot]
8fe818c0b0 Update dependency samlify to v2.10.1 (#13362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-04 09:25:20 +01:00
maggie897
e603452fad Desktop: Fixes #13088: Hide 'Start application minimised' unless tray icon is enabled (#13340) 2025-10-03 14:42:45 +01:00
mrjo118
3827637b54 Mobile: Make the conflicts folder text use the error colour, like is done on desktop (#13343) 2025-10-03 14:41:55 +01:00
Henry Heino
1da7c54e5f Chore: Add test for joplinServerConnected condition (#13352) 2025-10-03 14:32:57 +01:00
Henry Heino
e24ebffba6 Desktop: Resolves #12803: Upgrade tesseract.js to v6 (#13345)
Co-authored-by: pedr <pedr@users.noreply.github.com>
2025-10-03 14:32:15 +01:00
Laurent Cozic
97fa85a3f7 Desktop release v3.4.13 2025-10-02 09:35:36 +01:00
Laurent Cozic
defe36bba1 Server: Enable publish and share notebook for SAML login 2025-10-02 09:34:51 +01:00
Laurent Cozic
e5bd77836a Desktop release v3.5.2 2025-10-02 09:14:44 +01:00
Henry Heino
8f5e628303 Chore: Fix CI (#13344) 2025-10-02 09:14:14 +01:00
Laurent Cozic
6850c8128b Desktop release v3.5.1 2025-10-02 09:13:48 +01:00
Laurent Cozic
8a797fdf23 Server: Enable publish and share notebook for SAML login 2025-10-02 09:13:29 +01:00
Laurent Cozic
1ae550c0aa Chore: Setup new release 3.5 2025-10-02 09:12:11 +01:00
maggie897
e7e0529f52 Desktop: Resolves #12292: Add hover + expanded arrow behavior for Notebook/Tags header (#13190)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-01 23:20:33 +01:00
Henry Heino
2381e44c7f Chore: OneNote importer: Upgrade to Rust 2024 (#13298)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-01 10:22:33 +01:00
renovate[bot]
a59e975f73 Update dependency sharp to v0.34.2 (#13336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 09:35:07 +01:00
Henry Heino
2d703b6292 Mobile: Resolves #13123: Add sync wizard (#13234) 2025-10-01 09:34:18 +01:00
renovate[bot]
b8db70f707 Update eslint (#13337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 04:17:48 +00:00
Filbert Wijaya
c91513b6b5 All: Translation: Update ja_JP.po (#13339) 2025-10-01 00:15:56 -04:00
Joplin Bot
a57ada97ef Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-01 01:51:34 +00:00
Laurent Cozic
d8677a70dd Transcribe: Resolves #12874: Downscale images before storing (#13333) 2025-09-30 23:17:01 +01:00
renovate[bot]
15839a19fd Update dependency rate-limiter-flexible to v6.2.1 (#13334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 23:10:14 +01:00
renovate[bot]
8f1d55c1fc Update dependency rate-limiter-flexible to v6 (#13325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-30 20:03:59 +01:00
JZou-Code
98c18711f7 Desktop: Fixes #12531: Fix the order of attached images (#12531) (#12868) 2025-09-30 17:44:38 +01:00
Henry Heino
24ff4612fb Mobile: Resolves #13104: Accessibility: Allow changing the ALT text of images from the mobile Rich Text Editor (#13169)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-30 17:34:03 +01:00
Henry Heino
f832eb38ff Mobile: Improve inline search performance in large documents (#13259) 2025-09-30 17:22:55 +01:00
Henry Heino
91dc23c23f Android: Plugins: Fix inspecting note editor WebViews (#13272) 2025-09-30 17:17:34 +01:00
Henry Heino
d1913493ab Desktop: Fix "insecure content security policy" warning (#13288)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-30 17:08:48 +01:00
trap000d
fd2b22ed68 Desktop: Resolves #12572: Click on systray icon will show/hide Joplin main window (#13299)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-30 17:05:44 +01:00
Henry Heino
14b56f19df Chore: OneNote converter: Refactor to allow debugging the import process, reduce use of "unsafe" (#13300) 2025-09-30 17:03:38 +01:00
renovate[bot]
0b082a985b Update dependency @react-native-clipboard/clipboard to v1.16.3 (#13304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-30 16:50:29 +01:00
mrjo118
53dcac22d0 Mobile: For notes over 100,000 characters, make the share note function share them as a file (#13305) 2025-09-30 16:49:54 +01:00
Henry Heino
2c721a76b7 Chore: Fix transcription server build (#13310) 2025-09-30 16:45:16 +01:00
yingli-lab
b68cfd6d9e Desktop: Fixes #13196: Fixed red close button not working on macOS 26 (#13311) 2025-09-30 16:43:58 +01:00
JZou-Code
affebedc4b Desktop: Fixes #12763: skip copy event in TinyMCE if no content is selected. (#13313) 2025-09-30 16:37:44 +01:00
JZou-Code
a714ef4807 Desktop: Fixes #13314: skip cut action in TinyMCE editor if no content is selected. (#13315) 2025-09-30 16:35:38 +01:00
renovate[bot]
596f99aad3 Update dependency @types/node to v18.19.115 (#13332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 16:32:47 +01:00
renovate[bot]
c530d35b36 Update dependency @react-native/metro-config to v0.79.5 (#13331)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 02:49:10 +00:00
renovate[bot]
5a5c734e2a Update dependency @react-native/babel-preset to v0.79.5 (#13329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 21:56:41 +00:00
renovate[bot]
f7eb483d9a Update dependency react-native-webview to v13.14.2 (#13327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 17:10:00 +01:00
Laurent Cozic
7f3c7e807c Desktop: Use plugin repository URL when homepage URL is not available in config screen (#13318) 2025-09-29 14:35:55 +01:00
renovate[bot]
a50fc02b32 Update dependency react-native-webview to v13.14.0 (#13324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 14:35:39 +01:00
renovate[bot]
63702e9e34 Update dependency @types/serviceworker to v0.0.140 (#13321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 02:48:55 +00:00
renovate[bot]
92c67aab4e Update dependency @types/node to v18.19.113 (#13320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 02:46:26 +00:00
renovate[bot]
91535870a2 Update dependency react-native-localize to v3.4.2 (#13289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-28 21:48:19 +01:00
renovate[bot]
d4bb277417 Update dependency pg to v8.16.3 (#13296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-28 21:48:06 +01:00
krevad
90f87d1496 All: Translation: Update sv.po (#13316) 2025-09-28 14:44:42 -04:00
Joplin Bot
b07752b3ab Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-28 12:47:37 +00:00
Laurent Cozic
98effef4c5 Server: Improve SAML login error handling and add doc regarding email and displayName attributes 2025-09-28 12:48:56 +01:00
Laurent Cozic
32a919eb81 Chore: Remove obsolete "version" parameter from docker-compose files 2025-09-28 12:44:00 +01:00
Laurent Cozic
e124fd5c9f Chore: Set URLs to absolute ones on release notes and tweak title 2025-09-28 10:23:56 +01:00
Joplin Bot
c5f9290402 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-27 10:41:21 +00:00
Henry Heino
c80cdadc99 Doc: Add release notes for v3.4 (#13273) 2025-09-27 10:20:31 +01:00
Laurent Cozic
d96dcef109 Chore: Trying to fix GitHub Actions size issue (#13290) 2025-09-25 23:33:02 +01:00
Henry Heino
33b889ca38 Chore: Fix APK alignment check in CI (#13294) 2025-09-25 20:48:56 +01:00
Laurent Cozic
fa78ea0173 Server v3.4.4 2025-09-25 14:19:26 +01:00
Laurent Cozic
6705712f80 Server: Use "lax" cookies when using external authentication like SAML or LDAP 2025-09-25 12:12:14 +01:00
renovate[bot]
2785b7f7d9 Update dependency @react-native-community/datetimepicker to v8.4.2 (#13285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 14:10:36 +01:00
Laurent Cozic
f04831406e Server: Trying to make logging more robust 2025-09-24 12:37:49 +01:00
renovate[bot]
fdffc81834 Update dependency @react-native-community/datetimepicker to v8.4.1 (#13279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 11:13:51 +01:00
Henry Heino
6f113df2d6 Chore: Sync fuzzer: Fix failures related to publishing/unpublishing notes (#13282) 2025-09-24 11:12:58 +01:00
Henry Heino
8b8b6fbe36 Desktop: Accessibility: Disable sync icon animation when reduce motion is enabled (#13283) 2025-09-24 11:12:38 +01:00
Laurent Cozic
1ef8fd529b Server: Provide more logging information for each request to help debugging issues 2025-09-24 10:46:24 +01:00
Marcus Kida
9547a459cb All: Translation: Update de_DE.po (#13275) 2025-09-23 11:11:34 -04:00
Joplin Bot
be1d092cab Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-23 01:32:34 +00:00
Laurent Cozic
517669ee27 Doc: Update sponsors 2025-09-22 22:29:54 +01:00
Laurent Cozic
72fc97116f Chore: Update renovate.json5 - ignore @react-native-community/cli-platform-ios package 2025-09-22 17:46:53 +01:00
Laurent Cozic
77ca6b3447 Chore: Update renovate.json5 - ignore @react-native-community/cli* packages 2025-09-22 17:46:21 +01:00
renovate[bot]
b227d337d0 Update dependency sass to v1.93.0 (#13255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 17:44:44 +01:00
Henry Heino
a6e671d45b Android: Improve location permission request (#13248) 2025-09-20 10:00:15 +01:00
Henry Heino
47c82a7e75 Chore: Mobile: Fix expo-related warnings in tests (#13260) 2025-09-20 01:25:49 +01:00
summoner
bafa1576f2 All: Translations: Update hu_HU.po (#13256) 2025-09-19 16:11:45 -04:00
Henry Heino
48956df439 Chore: Fix web app build (#13257) 2025-09-19 17:37:28 +01:00
renovate[bot]
4716065295 Update dependency pg to v8.16.2 (#13236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-19 09:24:41 +01:00
renovate[bot]
f801bbfb27 Update dependency sass to v1.89.2 (#13237)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-19 09:24:24 +01:00
Henry Heino
4a043f68ad Chore: Web: Fix build (#13250) 2025-09-19 09:22:22 +01:00
renovate[bot]
cac93e9f9c Update dependency react-native-paper to v5.14.5 (#13230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-18 14:25:08 +01:00
mrjo118
e1e5c9aeb0 Mobile: Remove expandable title field on Web client and fix icon alignment (#13240)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-18 14:24:49 +01:00
Henry Heino
382cb257ab Web: Fix multi-page document creation action fails for non-JPEG images (#13244)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-18 14:23:57 +01:00
Henry Heino
711d214741 Android: Fixes #13193: Fix Markdown toolbar buttons sometimes don't work (#13233) 2025-09-18 12:05:57 +01:00
Laurent Cozic
6f375be8b9 Server: Pin pm2-logrotate version to prevent supply chain attacks (#13235) 2025-09-18 12:03:57 +01:00
Laurent Cozic
a118615e06 Chore: Remove bitnami repository (#13239) 2025-09-18 10:34:01 +01:00
Joplin Bot
912bf7463f Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-17 18:38:33 +00:00
Laurent Cozic
cfc29832a2 Doc: Update sponsors 2025-09-17 18:07:34 +01:00
renovate[bot]
737fd132e3 Update dependency sass to v1.89.0 (#13231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 16:53:56 +01:00
renovate[bot]
9fc76f4e4c Update dependency pg to v8.16.0 (#13223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:27:13 +01:00
renovate[bot]
981f15d85c Update dependency node-mocks-http to v1.17.2 (#13221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:26:43 +01:00
renovate[bot]
a59594db3b Update dependency nodejs to v23.11.0 (#13222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:26:35 +01:00
JZou-Code
8c8190e2e9 Desktop: Fixes #12239: Prevent the default cut action handler to avoid double deletion (#13208) 2025-09-16 13:22:26 +01:00
Henry Heino
d7e7ff77e8 Chore: Mobile: Add additional logging to help debug toolbar issue (#13224)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-16 13:18:12 +01:00
Henry Heino
e33c142c5a Chore: Web: Skip secondary single-instance check in dev mode (#13225)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-16 12:52:43 +01:00
Laurent Cozic
97d3a8243d Chore: Fix CI (#13227) 2025-09-16 08:50:28 +01:00
renovate[bot]
f1716a3edb Update dependency @pmmmwh/react-refresh-webpack-plugin to ^0.6.0 (#13219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 18:39:20 +01:00
renovate[bot]
1436f5867d Update dependency androidx.documentfile:documentfile to v1.1.0 (#13220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 18:37:53 +01:00
renovate[bot]
d754b8fe0c Update dependency @rollup/plugin-commonjs to v28.0.6 (#13218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 17:05:58 +01:00
renovate[bot]
4f58055cc1 Update dependency @react-native/metro-config to v0.79.4 (#13217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 16:38:15 +01:00
renovate[bot]
98697e1db4 Update dependency style-to-js to v1.1.17 (#13209)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-15 16:30:10 +01:00
renovate[bot]
8ac65a08c1 Update dependency @react-native/babel-preset to v0.79.4 (#13213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 15:06:33 +01:00
renovate[bot]
2b86d83290 Update dependency @babel/plugin-transform-export-namespace-from to v7.27.1 (#13212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 13:18:34 +01:00
renovate[bot]
09cafe99d1 Update bitnami/postgresql Docker tag to v17.4.0 (#13211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 11:33:22 +01:00
renovate[bot]
6fce844cbf Update dependency webpack-dev-server to v5.2.2 (#13210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 09:01:07 +01:00
renovate[bot]
52de8c071f Update dependency glob to v11.0.3 (#13205)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-15 09:00:01 +01:00
renovate[bot]
537543cc8a Update dependency react-native-zip-archive to v7.0.2 (#13207)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 00:29:44 +00:00
renovate[bot]
ff16453299 Update dependency js-draw to v1.30.1 (#13206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 00:27:57 +00:00
renovate[bot]
210deec495 Update dependency form-data to v4.0.3 (#13203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 19:03:16 +01:00
renovate[bot]
e96baea005 Update dependency @types/tar-stream to v3.1.4 (#13202)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 19:03:05 +01:00
renovate[bot]
ae24b91f25 Update dependency @types/node to v18.19.112 (#13204)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 19:02:56 +01:00
renovate[bot]
f2e5118bf5 Update dependency @types/serviceworker to v0.0.139 (#13201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 12:36:40 +00:00
renovate[bot]
72698ec573 Update dependency @react-native/metro-config to v0.79.3 (#13198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-14 09:35:05 +01:00
renovate[bot]
68abc27c6a Update dependency @types/react to v18.3.23 (#13200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-14 09:34:56 +01:00
renovate[bot]
1acb3d0726 Update dependency @rollup/plugin-commonjs to v28.0.5 (#13199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-14 09:34:48 +01:00
Joplin Bot
5bf97dc3b8 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-14 01:38:57 +00:00
Laurent Cozic
e0e04fbc91 Chore: Fixed type error 2025-09-14 00:44:36 +01:00
Laurent Cozic
625cd1221c Doc: Update sponsors 2025-09-14 00:41:03 +01:00
Henry Heino
110d5bde2d Desktop: Fix error dialogs fail to appear in certain cases (#13179) 2025-09-13 14:21:38 +01:00
renovate[bot]
93a85b3207 Update dependency @types/node to v18.19.111 (#13165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-13 14:18:04 +01:00
renovate[bot]
ff305f42fd Update dependency @js-draw/material-icons to v1.30.1 (#13164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-13 14:17:50 +01:00
Henry Heino
99ba854ee1 Chore: Cli: Fix CLI app integration tests (#13089) 2025-09-13 14:17:40 +01:00
pedr
0795c67354 All: Fixes #12249: Change default content-type for Webdav connector to application/octet-stream (#13053) 2025-09-13 14:13:27 +01:00
mrjo118
38b368e997 Mobile: Resolves #12936: Allow expanding and collapsing the title field across multiple lines (#13016) 2025-09-13 14:08:31 +01:00
Henry Heino
f9ffe6c4e6 Desktop,Mobile,Cli: Fix notes are moved to the conflict folder when a folder is unshared (#12993)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-13 14:08:19 +01:00
pedr
5adc0170fc All: Resolves #8718: Delete all note revisions when the note is permanently deleted (#12609) 2025-09-13 14:06:56 +01:00
mrjo118
f54c364b4d Desktop, Mobile: Automatically retrigger the sync if there are more unsynced outgoing changes when sync completes (#12989) 2025-09-13 14:05:31 +01:00
mrjo118
9f541b9b9d Desktop, Mobile: Add support for mixed case tags (#12931)
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
2025-09-13 14:01:33 +01:00
Henry Heino
bd0af08c57 Docs: REST API: Add descriptions for "is_shared" and "share_id" (#13186) 2025-09-13 13:52:46 +01:00
Henry Heino
ac06c6750d Android: Fixes #13113: Fix compatibility with 16-KB-page-size devices: Remove Vosk (#13189) 2025-09-13 13:52:12 +01:00
renovate[bot]
23b07094b7 Update dependency @react-native/babel-preset to v0.79.3 (#13195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 13:48:57 +01:00
Joplin Bot
7eefc016de Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-13 12:45:33 +00:00
Laurent Cozic
c002be76cd Doc: Update sponsors 2025-09-13 12:00:32 +01:00
renovate[bot]
2cd29aaaea Update dependency react-native-image-picker to v8.2.1 (#13002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-12 22:48:44 +01:00
Laurent Cozic
4cb6b01c71 Server: Clean-up SAML login section 2025-09-12 15:14:22 +01:00
Joplin Bot
91c79b9488 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-10 12:42:24 +00:00
Henry Heino
fc516d05b3 Docs: Update the Rich Text Editor documentation (#13171) 2025-09-10 10:44:29 +01:00
Henry Heino
2769c9586c Mobile: Fixes #13138: Rich Text Editor: Fix image size lost on change (#13172) 2025-09-10 10:06:40 +01:00
Henry Heino
fd15d5a6d3 Mobile: Rich Text Editor: Accessibility: Fix font size setting not respected (#13174) 2025-09-10 10:05:51 +01:00
krevad
7237d7faa7 All: Translation: Update sv.po (#13170)
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
2025-09-09 19:37:19 -04:00
Henry Heino
3025d62568 Chore: Fix CI (#13168) 2025-09-09 22:31:55 +01:00
Laurent Cozic
e9a9f68568 Desktop release v3.4.12 2025-09-09 15:36:24 +01:00
renovate[bot]
5b5dcf34a1 Update dependency @axe-core/playwright to v4.10.2 (#13162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 09:54:36 +01:00
676 changed files with 115820 additions and 92780 deletions

View File

@@ -9,6 +9,7 @@ API_KEY=random-string
QUEUE_TTL=900000
QUEUE_RETRY_COUNT=2
QUEUE_MAINTENANCE_INTERVAL=30000
IMAGE_MAX_DIMENSION=400
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:latest
# Fullpath to images folder e.g.:

View File

@@ -90,12 +90,13 @@ plugin_types/
readme/
packages/react-native-vosk/lib/
packages/lib/countable/Countable.js
packages/onenote-converter/pkg/onenote_converter.js
packages/onenote-converter/renderer/pkg/*
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
packages/app-cli/app/LinkSelector.js
packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/cli-integration-tests.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
@@ -269,6 +270,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useKeymap.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVisiblePaneChange.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useSyncEditorValue.js
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
@@ -279,6 +281,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useCursorPositioning.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
@@ -320,6 +323,7 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
packages/app-desktop/gui/NoteEditor/utils/useInitialCursorLocation.js
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
@@ -603,6 +607,7 @@ packages/app-desktop/tools/generateLatestArm64Yml.js
packages/app-desktop/tools/githubReleasesUtils.js
packages/app-desktop/tools/modifyReleaseAssets.js
packages/app-desktop/tools/notarizeMacApp.js
packages/app-desktop/tools/resolveSourceMap.js
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
packages/app-desktop/utils/checkForUpdatesUtils.test.js
@@ -696,6 +701,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
@@ -724,6 +730,8 @@ packages/app-mobile/components/SearchInput.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/SideMenuContentNote.js
packages/app-mobile/components/SyncWizard/JoplinCloudIcon.js
packages/app-mobile/components/SyncWizard/SyncWizard.js
packages/app-mobile/components/TagEditor.test.js
packages/app-mobile/components/TagEditor.js
packages/app-mobile/components/TextInput.js
@@ -740,6 +748,7 @@ packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/buttons/CardButton.js
packages/app-mobile/components/buttons/FloatingActionButton.js
packages/app-mobile/components/buttons/LabelledIconButton.js
packages/app-mobile/components/buttons/MultiTouchableOpacity.js
@@ -840,6 +849,7 @@ packages/app-mobile/components/screens/NoteTagsDialog.js
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
packages/app-mobile/components/screens/Notes/NewNoteButton.js
packages/app-mobile/components/screens/Notes/Notes.js
packages/app-mobile/components/screens/SearchScreen/SearchResults.test.js
packages/app-mobile/components/screens/SearchScreen/SearchResults.js
packages/app-mobile/components/screens/SearchScreen/index.js
packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
@@ -909,8 +919,6 @@ packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/VoiceTyping.js
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
packages/app-mobile/services/voiceTyping/utils/unzip.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/services/voiceTyping/whisper.test.js
packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
@@ -1047,6 +1055,7 @@ packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
packages/editor/CodeMirror/extensions/searchExtension.test.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
@@ -1093,12 +1102,16 @@ packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/ProseMirror/commands.test.js
packages/editor/ProseMirror/commands.js
packages/editor/CodeMirror/vendor/announceSearchMatch.js
packages/editor/ProseMirror/commands/commands.test.js
packages/editor/ProseMirror/commands/commands.js
packages/editor/ProseMirror/commands/focusEditor.js
packages/editor/ProseMirror/createEditor.js
packages/editor/ProseMirror/index.js
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
packages/editor/ProseMirror/plugins/detailsPlugin.js
packages/editor/ProseMirror/plugins/imagePlugin.test.js
packages/editor/ProseMirror/plugins/imagePlugin.js
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
@@ -1110,12 +1123,16 @@ packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
packages/editor/ProseMirror/plugins/listPlugin.js
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
packages/editor/ProseMirror/plugins/searchPlugin.js
packages/editor/ProseMirror/plugins/tablePlugin.js
packages/editor/ProseMirror/plugins/utils/createExternalEditorPlugin.js
packages/editor/ProseMirror/plugins/utils/createFloatingButtonPlugin.js
packages/editor/ProseMirror/schema.js
packages/editor/ProseMirror/styles.js
packages/editor/ProseMirror/testing/createTestEditor.js
packages/editor/ProseMirror/testing/createTestEditorWithSerializer.js
packages/editor/ProseMirror/types.js
packages/editor/ProseMirror/utils/SelectableNodeView.js
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
@@ -1123,6 +1140,7 @@ packages/editor/ProseMirror/utils/dom/createButton.js
packages/editor/ProseMirror/utils/dom/createTextArea.js
packages/editor/ProseMirror/utils/dom/createTextNode.js
packages/editor/ProseMirror/utils/dom/createUniqueId.js
packages/editor/ProseMirror/utils/dom/showModal.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
packages/editor/ProseMirror/utils/forEachHeading.js
@@ -1133,8 +1151,15 @@ packages/editor/ProseMirror/utils/postprocessEditorOutput.js
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
packages/editor/ProseMirror/utils/preprocessEditorInput.js
packages/editor/ProseMirror/utils/sanitizeHtml.js
packages/editor/ProseMirror/utils/selectFirstInstanceOfNode.js
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
packages/editor/ProseMirror/vendor/changedDescendants.js
packages/editor/ProseMirror/vendor/icons/addColumnRight.js
packages/editor/ProseMirror/vendor/icons/addRowBelow.js
packages/editor/ProseMirror/vendor/icons/icon.js
packages/editor/ProseMirror/vendor/icons/removeColumn.js
packages/editor/ProseMirror/vendor/icons/removeRow.js
packages/editor/ProseMirror/vendor/icons/types.js
packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
@@ -1403,6 +1428,7 @@ packages/lib/services/database/migrations/45.js
packages/lib/services/database/migrations/46.js
packages/lib/services/database/migrations/47.js
packages/lib/services/database/migrations/48.js
packages/lib/services/database/migrations/49.js
packages/lib/services/database/migrations/index.js
packages/lib/services/database/sqlStringToLines.js
packages/lib/services/database/types.js
@@ -1526,6 +1552,7 @@ packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getActivePluginEditorView.js
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
packages/lib/services/plugins/utils/getPluginHelpUrl.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
@@ -1617,6 +1644,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
packages/lib/services/synchronizer/Synchronizer.tags.test.js
packages/lib/services/synchronizer/Synchronizer.tools.test.js
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
packages/lib/services/synchronizer/handleConflictAction.test.js
packages/lib/services/synchronizer/migrations/1.js
packages/lib/services/synchronizer/migrations/2.js
packages/lib/services/synchronizer/migrations/3.js
@@ -1730,6 +1758,7 @@ packages/plugin-repo-cli/lib/gitCompareUrl.test.js
packages/plugin-repo-cli/lib/gitCompareUrl.js
packages/plugin-repo-cli/lib/overrideUtils.test.js
packages/plugin-repo-cli/lib/overrideUtils.js
packages/plugin-repo-cli/lib/searchPlugins.js
packages/plugin-repo-cli/lib/types.js
packages/plugin-repo-cli/lib/updateReadme.test.js
packages/plugin-repo-cli/lib/updateReadme.js
@@ -1845,6 +1874,8 @@ packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.test.js
packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/parsePluralLocalizationForm.js
packages/tools/utils/parsePlurallLocalizationForm.test.js
packages/tools/utils/translation.js
packages/tools/validateFilenames.js
packages/tools/website/build.js

View File

@@ -59,10 +59,10 @@ jobs:
fi
# The build-tools/ directory contains different subdirectories
# for each build tools version. As a result, there may be multiple
# zipalign tools. Select one of them:
ZIPALIGN_PATH="$(find $BUILD_TOOLS_PATH -name "zipalign" -print | head -n1)"
# zipalign tools. Select the most recent (biggest two-digit version number):
ZIPALIGN_PATH="$(find $BUILD_TOOLS_PATH -name "zipalign" -print | sort | tail -n1)"
if test ! -x "$ZIPALIGN_PATH" ; then
echo "zipalign not found (searching in $BUILD_TOOLS_PATH, candidate: $ZIPALIGN_PATH)"
exit 1
fi
"$ZIPALIGN_PATH" -c -P 16 -v 4 "$APK_FILE"
"$ZIPALIGN_PATH" -c -P 16 -v 4 "$APK_FILE"

View File

@@ -9,7 +9,7 @@ jobs:
matrix:
# Do not use unbuntu-latest because it causes `The operation was canceled` failures:
# https://github.com/actions/runner-images/issues/6709
os: [macos-13, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
os: [macos-15-intel, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
steps:
- uses: actions/checkout@v4
@@ -31,6 +31,16 @@ jobs:
sudo apt-get update || true
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin
- name: Free disk space
if: runner.os == 'Linux'
run: |
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /opt/ghc || true
docker system prune -af || true
docker builder prune -af || true
sudo rm -rf /var/lib/docker/tmp/* || true
# Login to Docker only if we're on a server release tag. If we run this on
# a pull request it will fail because the PR doesn't have access to
# secrets
@@ -40,6 +50,22 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# - name: Test Windows app signing
# if: runner.os == 'Windows'
# env:
# GH_TOKEN: ${{ secrets.GH_TOKEN }}
# IS_CONTINUOUS_INTEGRATION: 1
# BUILD_SEQUENCIAL: 1
# SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }}
# SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }}
# SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }}
# SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }}
# SIGN_APPLICATION: 1
# # To ensure that the operations stop on failure, all commands
# # should be on one line with "&&" in between.
# run: |
# yarn install && cd packages/app-desktop && yarn dist
- name: Run tests, build and publish Linux and macOS apps
if: runner.os == 'Linux' || runner.os == 'macOs'
env:
@@ -61,11 +87,14 @@ jobs:
- name: Build and publish Windows app
if: runner.os == 'Windows' && startsWith(github.ref, 'refs/tags/v')
env:
CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }}
SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }}
SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }}
SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }}
SIGN_APPLICATION: 1
# To ensure that the operations stop on failure, all commands
# should be on one line with "&&" in between.
run: |
@@ -122,6 +151,16 @@ jobs:
with:
node-version: '18'
- name: Free disk space
if: runner.os == 'Linux'
run: |
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /opt/ghc || true
docker system prune -af || true
docker builder prune -af || true
sudo rm -rf /var/lib/docker/tmp/* || true
- name: Install Yarn
run: |
# https://yarnpkg.com/getting-started/install
@@ -149,7 +188,7 @@ jobs:
- name: Check HTTP request
run: |
# Need to pass environment variables:
docker run -p 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
docker run --env MAX_TIME_DRIFT=0 --publish 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
# Wait for server to start
sleep 120
@@ -175,5 +214,4 @@ jobs:
if [[ "$actual_body" != "$expected_body" ]]; then
echo 'Failed while checking the body response after request to /api/ping'
exit 1;
fi
fi

41
.gitignore vendored
View File

@@ -69,6 +69,7 @@ docs/**/*.mustache
packages/app-cli/app/LinkSelector.js
packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/cli-integration-tests.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
@@ -242,6 +243,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useKeymap.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVisiblePaneChange.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useSyncEditorValue.js
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
@@ -252,6 +254,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useCursorPositioning.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
@@ -293,6 +296,7 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
packages/app-desktop/gui/NoteEditor/utils/useInitialCursorLocation.js
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
@@ -576,6 +580,7 @@ packages/app-desktop/tools/generateLatestArm64Yml.js
packages/app-desktop/tools/githubReleasesUtils.js
packages/app-desktop/tools/modifyReleaseAssets.js
packages/app-desktop/tools/notarizeMacApp.js
packages/app-desktop/tools/resolveSourceMap.js
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
packages/app-desktop/utils/checkForUpdatesUtils.test.js
@@ -669,6 +674,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
@@ -697,6 +703,8 @@ packages/app-mobile/components/SearchInput.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/SideMenuContentNote.js
packages/app-mobile/components/SyncWizard/JoplinCloudIcon.js
packages/app-mobile/components/SyncWizard/SyncWizard.js
packages/app-mobile/components/TagEditor.test.js
packages/app-mobile/components/TagEditor.js
packages/app-mobile/components/TextInput.js
@@ -713,6 +721,7 @@ packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/buttons/CardButton.js
packages/app-mobile/components/buttons/FloatingActionButton.js
packages/app-mobile/components/buttons/LabelledIconButton.js
packages/app-mobile/components/buttons/MultiTouchableOpacity.js
@@ -813,6 +822,7 @@ packages/app-mobile/components/screens/NoteTagsDialog.js
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
packages/app-mobile/components/screens/Notes/NewNoteButton.js
packages/app-mobile/components/screens/Notes/Notes.js
packages/app-mobile/components/screens/SearchScreen/SearchResults.test.js
packages/app-mobile/components/screens/SearchScreen/SearchResults.js
packages/app-mobile/components/screens/SearchScreen/index.js
packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
@@ -882,8 +892,6 @@ packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/VoiceTyping.js
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
packages/app-mobile/services/voiceTyping/utils/unzip.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/services/voiceTyping/whisper.test.js
packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
@@ -1020,6 +1028,7 @@ packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
packages/editor/CodeMirror/extensions/searchExtension.test.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
@@ -1066,12 +1075,16 @@ packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/ProseMirror/commands.test.js
packages/editor/ProseMirror/commands.js
packages/editor/CodeMirror/vendor/announceSearchMatch.js
packages/editor/ProseMirror/commands/commands.test.js
packages/editor/ProseMirror/commands/commands.js
packages/editor/ProseMirror/commands/focusEditor.js
packages/editor/ProseMirror/createEditor.js
packages/editor/ProseMirror/index.js
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
packages/editor/ProseMirror/plugins/detailsPlugin.js
packages/editor/ProseMirror/plugins/imagePlugin.test.js
packages/editor/ProseMirror/plugins/imagePlugin.js
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
@@ -1083,12 +1096,16 @@ packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
packages/editor/ProseMirror/plugins/listPlugin.js
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
packages/editor/ProseMirror/plugins/searchPlugin.js
packages/editor/ProseMirror/plugins/tablePlugin.js
packages/editor/ProseMirror/plugins/utils/createExternalEditorPlugin.js
packages/editor/ProseMirror/plugins/utils/createFloatingButtonPlugin.js
packages/editor/ProseMirror/schema.js
packages/editor/ProseMirror/styles.js
packages/editor/ProseMirror/testing/createTestEditor.js
packages/editor/ProseMirror/testing/createTestEditorWithSerializer.js
packages/editor/ProseMirror/types.js
packages/editor/ProseMirror/utils/SelectableNodeView.js
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
@@ -1096,6 +1113,7 @@ packages/editor/ProseMirror/utils/dom/createButton.js
packages/editor/ProseMirror/utils/dom/createTextArea.js
packages/editor/ProseMirror/utils/dom/createTextNode.js
packages/editor/ProseMirror/utils/dom/createUniqueId.js
packages/editor/ProseMirror/utils/dom/showModal.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
packages/editor/ProseMirror/utils/forEachHeading.js
@@ -1106,8 +1124,15 @@ packages/editor/ProseMirror/utils/postprocessEditorOutput.js
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
packages/editor/ProseMirror/utils/preprocessEditorInput.js
packages/editor/ProseMirror/utils/sanitizeHtml.js
packages/editor/ProseMirror/utils/selectFirstInstanceOfNode.js
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
packages/editor/ProseMirror/vendor/changedDescendants.js
packages/editor/ProseMirror/vendor/icons/addColumnRight.js
packages/editor/ProseMirror/vendor/icons/addRowBelow.js
packages/editor/ProseMirror/vendor/icons/icon.js
packages/editor/ProseMirror/vendor/icons/removeColumn.js
packages/editor/ProseMirror/vendor/icons/removeRow.js
packages/editor/ProseMirror/vendor/icons/types.js
packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
@@ -1376,6 +1401,7 @@ packages/lib/services/database/migrations/45.js
packages/lib/services/database/migrations/46.js
packages/lib/services/database/migrations/47.js
packages/lib/services/database/migrations/48.js
packages/lib/services/database/migrations/49.js
packages/lib/services/database/migrations/index.js
packages/lib/services/database/sqlStringToLines.js
packages/lib/services/database/types.js
@@ -1499,6 +1525,7 @@ packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getActivePluginEditorView.js
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
packages/lib/services/plugins/utils/getPluginHelpUrl.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
@@ -1590,6 +1617,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
packages/lib/services/synchronizer/Synchronizer.tags.test.js
packages/lib/services/synchronizer/Synchronizer.tools.test.js
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
packages/lib/services/synchronizer/handleConflictAction.test.js
packages/lib/services/synchronizer/migrations/1.js
packages/lib/services/synchronizer/migrations/2.js
packages/lib/services/synchronizer/migrations/3.js
@@ -1703,6 +1731,7 @@ packages/plugin-repo-cli/lib/gitCompareUrl.test.js
packages/plugin-repo-cli/lib/gitCompareUrl.js
packages/plugin-repo-cli/lib/overrideUtils.test.js
packages/plugin-repo-cli/lib/overrideUtils.js
packages/plugin-repo-cli/lib/searchPlugins.js
packages/plugin-repo-cli/lib/types.js
packages/plugin-repo-cli/lib/updateReadme.test.js
packages/plugin-repo-cli/lib/updateReadme.js
@@ -1818,6 +1847,8 @@ packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.test.js
packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/parsePluralLocalizationForm.js
packages/tools/utils/parsePlurallLocalizationForm.test.js
packages/tools/utils/translation.js
packages/tools/validateFilenames.js
packages/tools/website/build.js

View File

@@ -0,0 +1,36 @@
# Patch to remove eval. This allows using depd in an environment with
# a strict Content-Security-Policy.
# Ref: https://github.com/dougwilson/nodejs-depd/pull/33
diff --git a/index.js b/index.js
index d758d3c8f58a60bf27ef377ad77639bf10ce7854..2bad40d4eeba553d3bcfb206873eac059067ae3b 100644
--- a/index.js
+++ b/index.js
@@ -399,19 +399,20 @@ function wrapfunction (fn, message) {
throw new TypeError('argument fn must be a function')
}
- var args = createArgumentsString(fn.length)
- var deprecate = this // eslint-disable-line no-unused-vars
var stack = getStack()
var site = callSiteLocation(stack[1])
site.name = fn.name
- // eslint-disable-next-line no-eval
- var deprecatedfn = eval('(function (' + args + ') {\n' +
- '"use strict"\n' +
- 'log.call(deprecate, message, site)\n' +
- 'return fn.apply(this, arguments)\n' +
- '})')
+ var deprecatedfn
+ var self = this
+ deprecatedfn = function () {
+ 'use strict'
+ log.call(self, message, site)
+ return fn.apply(this, arguments)
+ }
+ Object.defineProperty(deprecatedfn, 'length', { value: fn.length })
+ Object.defineProperty(deprecatedfn, 'name', { value: fn.name })
return deprecatedfn
}

View File

@@ -0,0 +1,35 @@
# Patch to remove eval. This allows using depd in an environment with
# a strict Content-Security-Policy.
# Ref: https://github.com/dougwilson/nodejs-depd/pull/33
diff --git a/index.js b/index.js
index 1bf2fcfdeffc984e5ad792eec08744c29d4a4590..1b24aa2414458bc651abfdded81b103c131efeaa 100644
--- a/index.js
+++ b/index.js
@@ -415,19 +415,19 @@ function wrapfunction (fn, message) {
throw new TypeError('argument fn must be a function')
}
- var args = createArgumentsString(fn.length)
var stack = getStack()
var site = callSiteLocation(stack[1])
site.name = fn.name
- // eslint-disable-next-line no-new-func
- var deprecatedfn = new Function('fn', 'log', 'deprecate', 'message', 'site',
- '"use strict"\n' +
- 'return function (' + args + ') {' +
- 'log.call(deprecate, message, site)\n' +
- 'return fn.apply(this, arguments)\n' +
- '}')(fn, log, this, message, site)
+ var self = this
+ var deprecatedfn = function () {
+ 'use strict'
+ log.call(self, message, site)
+ return fn.apply(this, arguments)
+ }
+ Object.defineProperty(deprecatedfn, 'length', { value: fn.length })
+ Object.defineProperty(deprecatedfn, 'name', { value: fn.name })
return deprecatedfn
}

View File

@@ -1,209 +0,0 @@
diff --git a/android/build.gradle b/android/build.gradle
index 6afcbbf0cc8ca2d69dd78077d61e59a90b2136bb..9f8d72b4ec5b2b3d290975d6a255917c95300854 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -67,19 +67,19 @@ repositories {
}
// Generate UUIDs for each models contained in android/src/main/assets/
-tasks.register('genUUID') {
- doLast {
- fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
- if (fileDetails.directory) {
- def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
- def ofile = file("$odir/uuid")
- mkdir odir
- ofile.text = UUID.randomUUID().toString()
- }
- }
- }
-}
-preBuild.dependsOn genUUID
+// tasks.register('genUUID') {
+// doLast {
+// fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
+// if (fileDetails.directory) {
+// def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
+// def ofile = file("$odir/uuid")
+// mkdir odir
+// ofile.text = UUID.randomUUID().toString()
+// }
+// }
+// }
+// }
+// preBuild.dependsOn genUUID
def kotlin_version = getExtOrDefault('kotlinVersion')
diff --git a/android/src/main/java/com/reactnativevosk/VoskModule.kt b/android/src/main/java/com/reactnativevosk/VoskModule.kt
index 0e2b6595b1b2cf1ee01c6c64239c4b0ea37fce19..5a8539b9cce8951967640dba755e29a4e3ff404a 100644
--- a/android/src/main/java/com/reactnativevosk/VoskModule.kt
+++ b/android/src/main/java/com/reactnativevosk/VoskModule.kt
@@ -19,13 +19,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
return "Vosk"
}
+ @ReactMethod
+ fun addListener(type: String?) {
+ // Keep: Required for RN built in Event Emitter Calls.
+ }
+
+ @ReactMethod
+ fun removeListeners(type: Int?) {
+ // Keep: Required for RN built in Event Emitter Calls.
+ }
+
override fun onResult(hypothesis: String) {
// Get text data from string object
val text = getHypothesisText(hypothesis)
// Stop recording if data found
if (text != null && text.isNotEmpty()) {
- cleanRecognizer();
+ // Don't auto-stop the recogniser - we want to do that when the user
+ // presses on "stop" only.
+ // cleanRecognizer();
sendEvent("onResult", text)
}
}
@@ -93,12 +105,11 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
@ReactMethod
fun loadModel(path: String, promise: Promise) {
cleanModel();
- StorageService.unpack(context, path, "models",
- { model: Model? ->
- this.model = model
- promise.resolve("Model successfully loaded")
- }
- ) { e: IOException ->
+
+ try {
+ this.model = Model(path);
+ promise.resolve("Model successfully loaded")
+ } catch (e: IOException) {
this.model = null
promise.reject(e)
}
@@ -153,6 +164,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
cleanRecognizer();
}
+ @ReactMethod
+ fun stopOnly() {
+ if (speechService != null) {
+ speechService!!.stop()
+ }
+ }
+
+ @ReactMethod
+ fun cleanup() {
+ if (speechService != null) {
+ speechService!!.shutdown();
+ speechService = null
+ }
+ if (recognizer != null) {
+ recognizer!!.close();
+ recognizer = null;
+ }
+ }
+
@ReactMethod
fun unload() {
cleanRecognizer();
diff --git a/lib/typescript/index.d.ts b/lib/typescript/index.d.ts
index 441e41cc402cca3a60b34978ef4fea976076259c..a173acebb4b314402550442ad471e0f7c706e3c4 100644
--- a/lib/typescript/index.d.ts
+++ b/lib/typescript/index.d.ts
@@ -10,6 +10,8 @@ export default class Vosk {
currentRegisteredEvents: EmitterSubscription[];
start: (grammar?: string[] | null) => Promise<String>;
stop: () => void;
+ stopOnly: () => void;
+ cleanup: () => void;
unload: () => void;
onResult: (onResult: (e: VoskEvent) => void) => EventSubscription;
onFinalResult: (onFinalResult: (e: VoskEvent) => void) => EventSubscription;
diff --git a/package.json b/package.json
index 707eddb8d68007f93071ac659c5b087c935c5f01..90ebe20f224eeec472c377df1fef9b15f2ff8200 100644
--- a/package.json
+++ b/package.json
@@ -11,12 +11,9 @@
"src",
"lib",
"android",
- "ios",
"cpp",
- "react-native-vosk.podspec",
"!lib/typescript/example",
"!android/build",
- "!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
diff --git a/react-native-vosk.podspec b/react-native-vosk.podspec
deleted file mode 100644
index e3d41b90c5eef890c7a5108aaf16ac07d34a698b..0000000000000000000000000000000000000000
--- a/react-native-vosk.podspec
+++ /dev/null
@@ -1,41 +0,0 @@
-require "json"
-
-package = JSON.parse(File.read(File.join(__dir__, "package.json")))
-folly_version = '2021.06.28.00-v2'
-folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
-
-Pod::Spec.new do |s|
- s.name = "react-native-vosk"
- s.version = package["version"]
- s.summary = package["description"]
- s.homepage = package["homepage"]
- s.license = package["license"]
- s.authors = package["author"]
-
- s.platforms = { :ios => "10.0" }
- s.source = { :git => "https://github.com/riderodd/react-native-vosk.git", :tag => "#{s.version}" }
-
- s.source_files = "ios/**/*.{h,m,mm,swift}"
- s.resource_bundles = { 'Vosk' => ['ios/Vosk/*'] }
-
- s.dependency "React-Core"
- s.frameworks = "Accelerate"
- s.library = "c++"
- s.vendored_frameworks = "ios/libvosk.xcframework"
- s.requires_arc = true
-
- # Don't install the dependencies when we run `pod install` in the old architecture.
- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
- s.pod_target_xcconfig = {
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
- }
-
- s.dependency "React-Codegen"
- s.dependency "RCT-Folly", folly_version
- s.dependency "RCTRequired"
- s.dependency "RCTTypeSafety"
- s.dependency "ReactCommon/turbomodule/core"
- end
-end
diff --git a/src/index.tsx b/src/index.tsx
index d9f90c921d89b1b4d85e145443ed3376546a368a..29e4068dbd7500828a73145bd25497a52c9bf638 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -69,6 +69,15 @@ export default class Vosk {
VoskModule.stop();
};
+ stopOnly = () => {
+ VoskModule.stopOnly();
+ };
+
+ cleanup = () => {
+ this.cleanListeners();
+ VoskModule.cleanup();
+ };
+
unload = () => {
this.cleanListeners();
VoskModule.unload();

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -1,4 +1,77 @@
<?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, 28 Apr 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 28 Apr 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.3]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 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>
<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>
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-mobile-rte.png" width="400" alt="screenshot: Mobile Rich Text Editor editing the welcome notes. Images, headings, etc are rendering."/>
<p>To try it, 1) open a note in the default Markdown editor 2) open the note actions menu (the three vertical dots) for the note and 3) click “Edit as Rich Text”.</p>
<p>Be aware that this editor is still in active development and <a href="https://github.com/laurent22/joplin/issues/12840">has a number of known limitations and issues</a>. The Rich Text editor is based on <a href="https://prosemirror.net/">ProseMirror</a> and will behave differently from the desktop Rich Text Editor in many cases.</p>
<h3>Support for publishing notes with Joplin Cloud and Server<a name="support-for-publishing-notes-with-joplin-cloud-and-server" href="#support-for-publishing-notes-with-joplin-cloud-and-server" class="heading-anchor">🔗</a></h3>
<p>It's now possible to <a href="https://joplinapp.org/help/apps/publish_note">publish notes</a> from the mobile app! To do so, open the “Properties” menu for a note, then click “Publish/unpublish”:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-publish-notes.png" alt="screenshot: A Publish/unpublish note action is shown in the &quot;Note properties&quot; sidebar, just below a &quot;Previous versions&quot; button"></p>
<p>Next, in the “publish note” dialog, click “Copy shareable link”. Notes can later be unpublished by clicking &quot;Unpublish&quot; in the publication dialog.</p>
<h3>Viewing note history<a name="viewing-note-history" href="#viewing-note-history" class="heading-anchor">🔗</a></h3>
<p>It is now possible to view and restore previous note versions from the mobile app. Like the &quot;publish note&quot; feature, previous note versions can be accessed from the note properties menu.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-note-history.png" alt="screenshot: The note history page"></p>
<p>As on desktop, the note history feature can be configured from the “Note History” tab in settings.</p>
<h3>Updated tag dialog<a name="updated-tag-dialog" href="#updated-tag-dialog" class="heading-anchor">🔗</a></h3>
<p>The tag dialog has been redesigned, with a new UI for adding, removing, and creating new tags:<br>
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-tag-editor.png" width="500" alt="screenshot: Tag dialog now consists of three sections: Added tags, Add new tags, Actions."/></p>
<h3>Android: Improved voice typing<a name="android-improved-voice-typing" href="#android-improved-voice-typing" class="heading-anchor">🔗</a></h3>
<p>The voice typing feature on Android has been updated with <a href="https://github.com/laurent22/joplin/pull/12404">improved silence detection</a> and a new “<a href="https://github.com/laurent22/joplin/pull/12370">custom glossary</a>” setting. Voice typing also now <a href="https://github.com/laurent22/joplin/pull/12352">defaults to a more accurate (but somewhat slower) model</a>.</p>
<h3>Quickly creating a note from multiple photos<a name="quickly-creating-a-note-from-multiple-photos" href="#quickly-creating-a-note-from-multiple-photos" class="heading-anchor">🔗</a></h3>
<p>A “scan notebook” action has been added to the “New note” menu:</p>
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-scan-notebook.png" width="500"/>
<p>This action allows quickly creating a new note with multiple pictures taken from the camera.</p>
<h2>Desktop<a name="desktop" href="#desktop" class="heading-anchor">🔗</a></h2>
<h3>More Markdown Editor settings<a name="more-markdown-editor-settings" href="#more-markdown-editor-settings" class="heading-anchor">🔗</a></h3>
<p>The &quot;Note&quot; tab in settings now includes new settings for the Markdown editor, including:</p>
<ul>
<li>An option to render headers, lists, and certain other formatting within the editor.</li>
<li>An option to render images in the editor.</li>
</ul>
<p>When enabled, these settings bring the Markdown editor closer to the Rich Text Editor, without <a href="https://joplinapp.org/help/apps/rich_text_editor">some of the Rich Text Editor's limitations</a>.</p>
<p>These settings are also available on mobile.</p>
<h3>Smaller application size and faster startup<a name="smaller-application-size-and-faster-startup" href="#smaller-application-size-and-faster-startup" class="heading-anchor">🔗</a></h3>
<p>We've made the desktop application roughly 33% smaller! In addition to faster application startup, this means that the desktop app should be faster to download take up less space.</p>
<table class="table">
<thead>
<tr>
<th>Joplin version</th>
<th>Previous size (v3.3.13)</th>
<th>New size (v3.4.12)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Joplin for MacOS (ARM)</td>
<td>211 MB</td>
<td>141 MB</td>
</tr>
<tr>
<td>Joplin for Windows (installer)</td>
<td>321 MB</td>
<td>219 MB</td>
</tr>
<tr>
<td>Joplin for Windows (portable)</td>
<td>320 MB</td>
<td>219 MB</td>
</tr>
<tr>
<td>Joplin for Linux (AppImage)</td>
<td>219 MB</td>
<td>147 MB</td>
</tr>
</tbody>
</table>
<h2>Terminal app<a name="terminal-app" href="#terminal-app" class="heading-anchor">🔗</a></h2>
<h3>Collapsible folders<a name="collapsible-folders" href="#collapsible-folders" class="heading-anchor">🔗</a></h3>
<p>The <a href="https://joplinapp.org/help/apps/terminal/">terminal application</a> now supports expanding and collapsing folders by pressing <kbd>z</kbd>. For additional information, see <a href="https://github.com/laurent22/joplin/pull/12718">the original pull request</a>.</p>
<h3>Managing shared notebooks and published notes<a name="managing-shared-notebooks-and-published-notes" href="#managing-shared-notebooks-and-published-notes" class="heading-anchor">🔗</a></h3>
<p>New commands have been added to the terminal app, including <code>publish</code>, <code>unpublish</code>, and <code>share</code>. This allows the terminal app to manage shared folders and published notes.</p>
<h2>Bug fixes<a name="bug-fixes" href="#bug-fixes" class="heading-anchor">🔗</a></h2>
<p>For the full list of changes, see <a href="https://joplinapp.org/help/about/changelog/desktop/">the desktop changelog</a> and <a href="https://joplinapp.org/help/about/changelog/android/">the mobile changelog</a>.</p>
]]></description><link>https://joplinapp.org/news/20250922-release-3-4</link><guid isPermaLink="false">20250922-release-3-4</guid><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[What's new in Joplin 3.3]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
<h3>Accessibility improvements<a name="accessibility-improvements" href="#accessibility-improvements" class="heading-anchor">🔗</a></h3>
<p>The Joplin 3.3 release introduces significant accessibility enhancements designed to make the application more inclusive and user-friendly. Users can now benefit from improved keyboard navigation, thanks to newly added shortcuts and clearer labels that streamline interaction across the interface. We've also added a &quot;go to viewer&quot; menu item that moves focus from the note editor to the note viewer. Focus is moved to the location in the viewer corresponding to the location of the cursor in the editor.</p>
<p>Screen reader support has been bolstered, ensuring elements like the note list and sidebar are easier to toggle and interact with. These updates make the application more usable for individuals relying on assistive technologies.</p>
@@ -446,10 +519,4 @@ sys 0m38.013s</p>
<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><item><title><![CDATA[Joplin is hiring!]]></title><description><![CDATA[<p>Joplin is an open source note-taking app. Capture your thoughts and securely access them from any device.</p>
<p>We are looking to hire two JavaScript software developers to work on the desktop, mobile, and server applications. All those are built using modern technologies, including React, React Native and Electron with a strong focus on test units.</p>
<p>You need to demonstrate some experience with at least some of these technologies, and willing to learn more and touch various different projects.</p>
<p>You will be part of a small team, so you will have an opportunity for a high-impact role, targeting hundreds of thousands of users.</p>
<p>If you're interested please contact us at job-AT-joplin.cloud</p>
<p>No agencies please.</p>
]]></description><link>https://joplinapp.org/news/20221209-job</link><guid isPermaLink="false">20221209-job</guid><pubDate>Fri, 09 Dec 2022 00:00:00 GMT</pubDate><twitter-text>Joplin is hiring!</twitter-text></item></channel></rss>
]]></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>

View File

@@ -73,8 +73,10 @@ USER $user
COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages
COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini
# Install pm2-logrotate and default settings as the runtime user
RUN pm2 install pm2-logrotate \
# We download a specific version of the plugin to prevent pm2 from fetching the latest, since it may
# not have been properly audited (that fact was used to spread malware at some point). Ref:
# https://github.com/laurent22/joplin/issues/12754
RUN pm2 install https://registry.npmjs.org/pm2-logrotate/-/pm2-logrotate-3.0.0.tgz \
&& pm2 set pm2-logrotate:max_size 100MB \
&& pm2 set pm2-logrotate:retain 5 \
&& pm2 set pm2-logrotate:compress true

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://damangameplay.in"><img title="Daman Game" width="256" src="https://joplinapp.org/images/sponsors/DamanGame.png" alt="Daman Game"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a> <a href="https://uk.notgamstop.com/bonuses/free-spins-no-deposit-no-gamstop/"><img title="free spins no deposit at NotGamstop" width="256" src="https://joplinapp.org/images/sponsors/NotGamStop.jpg" alt="free spins no deposit at NotGamstop"/></a> <a href="https://www.writemyessay.com/"><img title="writing service for students WriteMyEssay" width="256" src="https://joplinapp.org/images/sponsors/WriteMyEssay.png" alt="writing service for students WriteMyEssay"/></a> <a href="https://essayservice.com/"><img title="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -9,7 +9,7 @@
"vips.dev": {
"platforms": ["aarch64-darwin"],
},
"nodejs": "23.10.0",
"nodejs": "23.11.0",
"pkg-config": "latest",
"darwin.apple_sdk.frameworks.Foundation": { // satisfies missing CoreText/CoreText.h
// https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/darwin/apple-sdk/default.nix

View File

@@ -16,12 +16,10 @@
# SLAVE_POSTGRES_PORT=5433
# SLAVE_POSTGRES_HOST=localhost
version: '2'
services:
postgresql-master:
image: 'bitnami/postgresql:17.3.0'
image: 'bitnamilegacy/postgresql:17.4.0'
ports:
- '5432:5432'
environment:
@@ -38,7 +36,7 @@ services:
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
postgresql-slave:
image: 'bitnami/postgresql:17.3.0'
image: 'bitnamilegacy/postgresql:17.4.0'
ports:
- '5433:5432'
depends_on:

View File

@@ -1,8 +1,6 @@
# This compose file can be used in development to run both the database and app
# within Docker.
version: '3'
services:
app:
build:

View File

@@ -15,8 +15,6 @@
# - This would typically be mapped to port to 443 (TLS) with a reverse proxy.
# - If Joplin Server does not need to be accessible over the internet, the port can be mapped to 22300.
version: '3'
networks:
app-network:
transcribe-network:

View File

@@ -76,20 +76,20 @@
"cspell": "5.21.2",
"eslint": "8.57.1",
"eslint-interactive": "10.8.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jest": "27.9.0",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-react": "7.37.4",
"eslint-plugin-react": "7.37.5",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "11.0.2",
"glob": "11.0.3",
"gulp": "4.0.2",
"husky": "9.1.7",
"lerna": "3.22.1",
"lint-staged": "15.5.2",
"madge": "8.0.0",
"npm-package-json-lint": "8.0.0",
"typescript": "5.8.2"
"typescript": "5.8.3"
},
"dependencies": {
"@types/fs-extra": "11.0.4",
@@ -101,7 +101,6 @@
"packageManager": "yarn@4.9.2",
"resolutions": {
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
"eslint": "patch:eslint@8.57.1#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
@@ -118,6 +117,12 @@
"pdfjs-dist@*": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"pdfjs-dist@3.11.174": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"canvas@npm:^2.11.2": "link:./.yarn/joplin-empty-package/",
"node-gyp@npm:^9.0.0": "11.2.0"
"node-gyp@npm:^9.0.0": "11.2.0",
"depd@npm:^2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
"depd@npm:~2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
"depd@npm:~1.1.2": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
"depd@npm:2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
"depd@npm:^1.1.2": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
"depd@npm:^1.1.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch"
}
}

View File

@@ -2,33 +2,44 @@
/* eslint-disable no-console */
const fs = require('fs-extra');
const Logger = require('@joplin/utils/Logger').default;
const { dirname } = require('@joplin/lib/path-utils');
import * as fs from 'fs-extra';
import Logger, { TargetType } from '@joplin/utils/Logger';
import { dirname } from '@joplin/lib/path-utils';
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
const JoplinDatabase = require('@joplin/lib/JoplinDatabase').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const Folder = require('@joplin/lib/models/Folder').default;
const Note = require('@joplin/lib/models/Note').default;
const Setting = require('@joplin/lib/models/Setting').default;
import JoplinDatabase from '@joplin/lib/JoplinDatabase';
import BaseModel from '@joplin/lib/BaseModel';
import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note';
import Setting from '@joplin/lib/models/Setting';
const { sprintf } = require('sprintf-js');
const exec = require('child_process').exec;
const nodeSqlite = require('sqlite3');
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
const { default: shimInitCli } = require('./utils/shimInitCli');
const baseDir = `${dirname(__dirname)}/tests/cli-integration`;
const joplinAppPath = `${__dirname}/main.js`;
shimInitCli({ nodeSqlite, appVersion: () => require('../package.json').version, keytar: null });
require('@joplin/lib/testing/test-utils');
const logger = new Logger();
logger.addTarget('console');
logger.addTarget(TargetType.Console);
logger.setLevel(Logger.LEVEL_ERROR);
const dbLogger = new Logger();
dbLogger.addTarget('console');
dbLogger.addTarget(TargetType.Console);
dbLogger.setLevel(Logger.LEVEL_INFO);
const db = new JoplinDatabase(new DatabaseDriverNode());
db.setLogger(dbLogger);
function createClient(id) {
interface Client {
id: number;
profileDir: string;
}
function createClient(id: number): Client {
return {
id: id,
profileDir: `${baseDir}/client${id}`,
@@ -37,13 +48,13 @@ function createClient(id) {
const client = createClient(1);
function execCommand(client, command) {
function execCommand(client: Client, command: string) {
const exePath = `node ${joplinAppPath}`;
const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
logger.info(`${client.id}: ${command}`);
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
return new Promise<string>((resolve, reject) => {
exec(cmd, (error: string, stdout: string, stderr: string) => {
if (error) {
logger.error(stderr);
reject(error);
@@ -54,17 +65,17 @@ function execCommand(client, command) {
});
}
function assertTrue(v) {
function assertTrue(v: unknown) {
if (!v) throw new Error(sprintf('Expected "true", got "%s"."', v));
process.stdout.write('.');
}
function assertFalse(v) {
function assertFalse(v: unknown) {
if (v) throw new Error(sprintf('Expected "false", got "%s"."', v));
process.stdout.write('.');
}
function assertEquals(expected, real) {
function assertEquals(expected: unknown, real: unknown) {
if (expected !== real) throw new Error(sprintf('Expecting "%s", got "%s"', expected, real));
process.stdout.write('.');
}
@@ -73,7 +84,7 @@ async function clearDatabase() {
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
}
const testUnits = {};
const testUnits: Record<string, ()=> Promise<void>> = {};
testUnits.testFolders = async () => {
await execCommand(client, 'mkbook nb1');
@@ -85,10 +96,16 @@ testUnits.testFolders = async () => {
await execCommand(client, 'mkbook nb1');
folders = await Folder.all();
assertEquals(1, folders.length);
assertEquals(2, folders.length);
assertEquals('nb1', folders[0].title);
assertEquals('nb1', folders[1].title);
await execCommand(client, 'rm -r -f nb1');
await execCommand(client, 'rmbook -p -f nb1');
folders = await Folder.all();
assertEquals(1, folders.length);
await execCommand(client, 'rmbook -p -f nb1');
folders = await Folder.all();
assertEquals(0, folders.length);
@@ -102,7 +119,7 @@ testUnits.testNotes = async () => {
assertEquals(1, notes.length);
assertEquals('n1', notes[0].title);
await execCommand(client, 'rm -f n1');
await execCommand(client, 'rmnote -p -f n1');
notes = await Note.all();
assertEquals(0, notes.length);
@@ -112,12 +129,19 @@ testUnits.testNotes = async () => {
notes = await Note.all();
assertEquals(2, notes.length);
await execCommand(client, 'rm -f \'blabla*\'');
// Should fail to delete a non-existent note
let failed = false;
try {
await execCommand(client, 'rmnote -f \'blabla*\'');
} catch (error) {
failed = true;
}
assertEquals(failed, true);
notes = await Note.all();
assertEquals(2, notes.length);
await execCommand(client, 'rm -f \'n*\'');
await execCommand(client, 'rmnote -f -p \'n*\'');
notes = await Note.all();
assertEquals(0, notes.length);
@@ -140,10 +164,12 @@ testUnits.testCat = async () => {
testUnits.testConfig = async () => {
await execCommand(client, 'config editor vim');
await Setting.reset();
await Setting.load();
assertEquals('vim', Setting.value('editor'));
await execCommand(client, 'config editor subl');
await Setting.reset();
await Setting.load();
assertEquals('subl', Setting.value('editor'));
@@ -201,15 +227,47 @@ testUnits.testMv = async () => {
await execCommand(client, 'mknote note2');
await execCommand(client, 'mknote note3');
await execCommand(client, 'mknote blabla');
await execCommand(client, 'mv \'note*\' nb2');
notes1 = await Note.previews(f1.id);
notes2 = await Note.previews(f2.id);
assertEquals(4, notes1.length);
assertEquals(1, notes2.length);
await execCommand(client, 'mv \'note*\' nb2');
notes2 = await Note.previews(f2.id);
notes1 = await Note.previews(f1.id);
assertEquals(1, notes1.length);
assertEquals(4, notes2.length);
};
testUnits.testUse = async () => {
await execCommand(client, 'mkbook nb1');
await execCommand(client, 'mkbook nb2');
await execCommand(client, 'mknote n1');
await execCommand(client, 'mknote n2');
const f1 = await Folder.loadByTitle('nb1');
const f2 = await Folder.loadByTitle('nb2');
let notes1 = await Note.previews(f1.id);
let notes2 = await Note.previews(f2.id);
assertEquals(0, notes1.length);
assertEquals(2, notes2.length);
await execCommand(client, 'use nb1');
await execCommand(client, 'mknote note2');
await execCommand(client, 'mknote note3');
notes1 = await Note.previews(f1.id);
notes2 = await Note.previews(f2.id);
assertEquals(2, notes1.length);
assertEquals(2, notes2.length);
};
async function main() {
await fs.remove(baseDir);
@@ -217,7 +275,9 @@ async function main() {
await db.open({ name: `${client.profileDir}/database.sqlite` });
BaseModel.setDb(db);
await Setting.load();
Setting.setConstant('rootProfileDir', client.profileDir);
Setting.setConstant('profileDir', client.profileDir);
await loadKeychainServiceAndSettings([]);
let onlyThisTest = 'testMv';
onlyThisTest = '';
@@ -234,7 +294,7 @@ async function main() {
}
}
main(process.argv).catch(error => {
main().catch(error => {
console.info('');
logger.error(error);
});

View File

@@ -35,15 +35,15 @@
],
"owner": "Laurent Cozic"
},
"version": "3.4.1",
"version": "3.5.1",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "~3.4",
"@joplin/renderer": "~3.4",
"@joplin/utils": "~3.4",
"@joplin/lib": "~3.5",
"@joplin/renderer": "~3.5",
"@joplin/utils": "~3.5",
"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.2",
"sharp": "0.34.3",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
@@ -70,14 +70,14 @@
"yargs-parser": "21.1.1"
},
"devDependencies": {
"@joplin/tools": "~3.4",
"@joplin/tools": "~3.5",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.14",
"@types/node": "18.19.103",
"@types/node": "18.19.130",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",
"temp": "0.9.4",
"typescript": "5.8.2"
"typescript": "5.8.3"
}
}

View File

@@ -0,0 +1,8 @@
<en-note>
<div>
<en-media style="--en-viewerProps:{};" type="image/jpeg" hash="e2d4887c5a32ab1686276c7c5ae733ef" width="1.125in" />
</div>
<div>
<br />
</div>
</en-note>

View File

@@ -0,0 +1,8 @@
<en-note>
<div>
<img src=":/e2d4887c5a32ab1686276c7c5ae733ef" style="--en-viewerProps:{};" type="image/jpeg" hash="e2d4887c5a32ab1686276c7c5ae733ef" width="108" alt="attachment-image" />
</div>
<div>
<br/>
</div>
</en-note>

View File

@@ -1,6 +1,8 @@
<en-note>
<div><a href=":/21ca2b948f222a38802940ec7e2e5de3" hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1">attachment-1</a></div>
<div>
<br>
<a href=':/21ca2b948f222a38802940ec7e2e5de3' hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1"> attachment-1</a>
</div>
<div>
<br/>
</div>
</en-note>

View File

@@ -1,16 +1,11 @@
<en-note>
<div>
<p>For example, consider an exported Evernote list with todo checkboxes like this:</p>
<ul>
<li>
<div><input checked="checked" type="checkbox" onclick="return false;">Foo</div>
</li>
<li>
<div><input type="checkbox" onclick="return false;"><b>Bar</b></div>
</li>
<li>
<div><input type="checkbox" onclick="return false;"><i>Baz</i></div>
</li>
<li><div><input checked="checked" type="checkbox" onclick="return false;" />Foo</div></li>
<li><div><input type="checkbox" onclick="return false;" /><b>Bar</b></div></li>
<li><div><input type="checkbox" onclick="return false;" /><i>Baz</i></div></li>
</ul>
</div>
</en-note>

View File

@@ -1,19 +1,11 @@
<en-note>
<div>
<p>In Evernote a checklist is not the same as a list with checkboxes.</p>
<ul style="--en-todo:true;">
<li style="--en-checked:false;">
<input type="checkbox" onclick="return false;">
<div>One</div>
</li>
<li style="--en-checked:true;">
<input checked="checked" type="checkbox" onclick="return false;">
<div>Two</div>
</li>
<li style="--en-checked:false;">
<input type="checkbox" onclick="return false;">
<div>Three</div>
</li>
<ul STYLE="--en-todo:true;">
<li STYLE="--en-checked:false;"> <input type="checkbox" onclick="return false;" /><div>One</div></li>
<li STYLE="--en-checked:true;"> <input checked="checked" type="checkbox" onclick="return false;" /><div>Two</div>
</li><li STYLE="--en-checked:false;"> <input type="checkbox" onclick="return false;" /><div>Three</div></li>
</ul>
</div>
</en-note>

View File

@@ -1,12 +1 @@
<en-note>
<div>
<audio controls="" preload="none" style="width:480px;">
<source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4">
<p>Your browser does not support HTML5 audio.</p>
</audio>
<p><a href=":/9168ee833d03c5ea7c730ac6673978c1">audio test</a></p>
</div>
<div>
<br>
</div>
</en-note>
<en-note><div><audio controls preload="none" style="width:480px;"><source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4" /><p>Your browser does not support HTML5 audio.</p></audio><p><a href=":/9168ee833d03c5ea7c730ac6673978c1">audio test</a></p></div><div><br/></div></en-note>

View File

@@ -1,12 +1 @@
<en-note>
<div><input type="checkbox" onclick="return false;">This is a test</div>
<div><input type="checkbox" onclick="return false;">A test for <span style="font-weight: bold;">bold</span></div>
<div>
<input type="checkbox" onclick="return false;">A test for <i>italic</i>
<br>
</div>
<div>
<br>
</div>
<div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt=""></i></div>
</en-note>
<en-note><div><input type="checkbox" onclick="return false;" />This is a test</div><div><input type="checkbox" onclick="return false;" />A test for <span STYLE="font-weight: bold;">bold</span></div><div><input type="checkbox" onclick="return false;" />A test for <i>italic</i><br/></div><div><br/></div><div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt="" /></i></div></en-note>

View File

@@ -1,3 +1,3 @@
<en-note>
<h1 style="box-sizing:inherit;font-family:&quot;Guardian TextSans Web&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif;margin-top:0.2em;margin-bottom:0.35em;font-size:2.125em;font-weight:600;line-height:1.3;">Association Between mRNA Vaccination and COVID-19 Hospitalization and Disease Severity</h1>
<h1 STYLE="box-sizing:inherit;font-family:&quot;Guardian TextSans Web&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif;margin-top:0.2em;margin-bottom:0.35em;font-size:2.125em;font-weight:600;line-height:1.3;">Association Between mRNA Vaccination and COVID-19 Hospitalization and Disease Severity</h1>
</en-note>

View File

@@ -1,3 +1,5 @@
<en-note>
<div><img style="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"></div>
<div>
<img STYLE="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" SRC="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"/>
</div>
</en-note>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230724T173816Z" application="Evernote" version="10.58.8">
<note>
<title>test.json</title>
<content><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
<en-note><en-media hash="ac91cc691d21261b222681dd38c1e4ad" type="application/json"/></en-note>
]]>
</content>
<created>20191002T075850Z</created>
<updated>20191002T075850Z</updated>
<note-attributes><latitude>48.79547119140625</latitude><longitude>9.809423921920198</longitude><altitude>398.0</altitude><author>Laurent</author><source>desktop.mac</source></note-attributes>
<resource><data>eyAidGVzdCI6IDEyMyB9</data><mime>application/json</mime><width>0</width><height>0</height><resource-attributes><file-name>test.json</file-name><attachment>false</attachment></resource-attributes></resource></note></en-export>

View File

@@ -2,7 +2,7 @@ import PluginRunner from '../../../app/services/plugins/PluginRunner';
import PluginService, { PluginSettings, defaultPluginSetting } from '@joplin/lib/services/plugins/PluginService';
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
import MdToHtml from '@joplin/renderer/MdToHtml';
import shim from '@joplin/lib/shim';
import shim, { MobilePlatform } from '@joplin/lib/shim';
import Setting from '@joplin/lib/models/Setting';
import * as fs from 'fs-extra';
import Note from '@joplin/lib/models/Note';
@@ -310,7 +310,7 @@ describe('services_PluginService', () => {
let resetPlatformMock = () => {};
if (!isDesktop) {
resetPlatformMock = mockMobilePlatform('android').reset;
resetPlatformMock = mockMobilePlatform(MobilePlatform.Android).reset;
}
try {

View File

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

View File

@@ -407,7 +407,17 @@ export default class ElectronAppWrapper {
isGoingToExit = true;
} else {
event.preventDefault();
this.hide();
const w = this.win_;
if (!w) return;
if (w.isFullScreen()) {
// leave fullscreen, then hide
w.once('leave-full-screen', () => w.hide());
w.setFullScreen(false);
} else {
w.hide();
}
}
} else {
const hasBackgroundWindows = this.secondaryWindows_.size > 0;
@@ -612,7 +622,11 @@ export default class ElectronAppWrapper {
console.warn('The window object was not available during the click event from tray icon');
return;
}
this.mainWindow().show();
if (!this.mainWindow().isVisible()) {
this.mainWindow().show();
} else {
this.mainWindow().hide();
}
});
} catch (error) {
console.error('Cannot create tray', error);

View File

@@ -52,7 +52,7 @@ describe('app.reducer', () => {
...createAppDefaultState({}),
backgroundWindows: {
testWindow: {
...createAppDefaultWindowState(),
...createAppDefaultWindowState(null),
windowId: 'testWindow',
visibleDialogs: {

View File

@@ -26,10 +26,21 @@ export interface AppStateDialog {
props: Record<string, any>;
}
export interface EditorScrollPercents {
export interface NoteIdToScrollPercent {
[noteId: string]: number;
}
type RichTextEditorSelectionBookmark = unknown;
export interface EditorCursorLocations {
readonly richText?: RichTextEditorSelectionBookmark;
readonly markdown?: number;
}
export interface NoteIdToEditorCursorLocations {
[noteId: string]: EditorCursorLocations;
}
export interface VisibleDialogs {
[dialogKey: string]: boolean;
}
@@ -42,6 +53,9 @@ export interface AppWindowState extends WindowState {
devToolsVisible: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
watchedResources: any;
lastEditorScrollPercents: NoteIdToScrollPercent;
lastEditorCursorLocations: NoteIdToEditorCursorLocations;
}
interface BackgroundWindowStates {
@@ -55,7 +69,6 @@ export interface AppState extends State, AppWindowState {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
navHistory: any[];
watchedNoteFiles: string[];
lastEditorScrollPercents: EditorScrollPercents;
focusedField: string;
layoutMoveMode: boolean;
startupPluginsLoaded: boolean;
@@ -66,7 +79,7 @@ export interface AppState extends State, AppWindowState {
isResettingLayout: boolean;
}
export const createAppDefaultWindowState = (): AppWindowState => {
export const createAppDefaultWindowState = (globalState: AppState|null): AppWindowState => {
return {
...defaultWindowState,
visibleDialogs: {},
@@ -75,6 +88,12 @@ export const createAppDefaultWindowState = (): AppWindowState => {
editorCodeView: true,
devToolsVisible: false,
watchedResources: {},
// Maintain the scroll and cursor location for secondary windows separate from the
// main window. This prevents scrolling in a secondary window from changing/resetting
// the default scroll position in the main window:
lastEditorCursorLocations: globalState?.lastEditorCursorLocations ?? {},
lastEditorScrollPercents: globalState?.lastEditorScrollPercents ?? {},
};
};
@@ -82,7 +101,7 @@ export const createAppDefaultWindowState = (): AppWindowState => {
export function createAppDefaultState(resourceEditWatcherDefaultState: any): AppState {
return {
...defaultState,
...createAppDefaultWindowState(),
...createAppDefaultWindowState(null),
route: {
type: 'NAV_GO',
routeName: 'Main',
@@ -90,7 +109,6 @@ export function createAppDefaultState(resourceEditWatcherDefaultState: any): App
},
navHistory: [],
watchedNoteFiles: [],
lastEditorScrollPercents: {},
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
focusedField: null,
layoutMoveMode: false,
@@ -299,6 +317,18 @@ export default function(state: AppState, action: any) {
}
break;
case 'EDITOR_CURSOR_POSITION_SET':
{
newState = { ...state };
const newCursorLocations = { ...newState.lastEditorCursorLocations };
newCursorLocations[action.noteId] = {
...(newCursorLocations[action.noteId] ?? {}),
...action.location,
};
newState.lastEditorCursorLocations = newCursorLocations;
}
break;
case 'NOTE_DEVTOOLS_TOGGLE':
newState = { ...state };
newState.devToolsVisible = !newState.devToolsVisible;

View File

@@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/
import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer';
import Note from '@joplin/lib/models/Note';
import { createAppDefaultWindowState } from '../app.reducer';
import { AppState, createAppDefaultWindowState } from '../app.reducer';
import Setting from '@joplin/lib/models/Setting';
export const declaration: CommandDeclaration = {
@@ -25,7 +25,7 @@ export const runtime = (): CommandRuntime => {
folderId: note.parent_id,
windowId: `window-${noteId}-${idCounter++}`,
defaultAppWindowState: {
...createAppDefaultWindowState(),
...createAppDefaultWindowState(context.state as AppState),
noteVisiblePanes: Setting.value('noteVisiblePanes'),
editorCodeView: Setting.value('editor.codeView'),
},

View File

@@ -225,7 +225,7 @@ const Button = React.forwardRef(({
animation={iconAnimation}
mr={iconOnly ? '0' : '6px'}
color={color}
className={iconName}
className={`${iconName} icon`}
role='img'
/>;
}

View File

@@ -8,6 +8,7 @@ import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
import bridge from '../../../../services/bridge';
import { ItemEvent, PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import getPluginHelpUrl from '@joplin/lib/services/plugins/utils/getPluginHelpUrl';
export enum InstallState {
NotInstalled = 1,
@@ -150,8 +151,7 @@ export default function(props: Props) {
const onNameClick = useCallback(() => {
const manifest = item.manifest;
if (!manifest.homepage_url) return;
void bridge().openExternal(manifest.homepage_url);
void bridge().openExternal(getPluginHelpUrl(manifest.id));
}, [item]);
const onRecommendedClick = useCallback(() => {

View File

@@ -7,6 +7,7 @@ import useKeyboardHandler from './DialogButtonRow/useKeyboardHandler';
export interface ButtonSpec {
name: string;
label: string;
disabled?: boolean;
}
export interface ClickEvent {
@@ -51,21 +52,29 @@ export default function DialogButtonRow(props: Props) {
if (props.onClick) props.onClick(event);
}, [props.onClick]);
const onKeyDown = useKeyboardHandler({ onOkButtonClick, onCancelButtonClick });
const okButtonShow = props.okButtonShow ?? true;
const cancelButtonShow = props.cancelButtonShow ?? true;
const canClickOk = okButtonShow && !props.okButtonDisabled;
const canClickCancel = cancelButtonShow && !props.cancelButtonDisabled;
const onKeyDown = useKeyboardHandler({
onOkButtonClick: canClickOk ? onOkButtonClick : null,
onCancelButtonClick: canClickCancel ? onCancelButtonClick : null,
});
const buttonComps = [];
if (props.customButtons) {
for (const b of props.customButtons) {
buttonComps.push(
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} onKeyDown={onKeyDown}>
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} disabled={b.disabled} onKeyDown={onKeyDown}>
{b.label}
</button>,
);
}
}
if (props.okButtonShow !== false) {
if (okButtonShow) {
buttonComps.push(
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={onOkButtonClick} ref={props.okButtonRef} onKeyDown={onKeyDown}>
{props.okButtonLabel ? props.okButtonLabel : _('OK')}
@@ -73,7 +82,7 @@ export default function DialogButtonRow(props: Props) {
);
}
if (props.cancelButtonShow !== false) {
if (cancelButtonShow) {
buttonComps.push(
<button disabled={props.cancelButtonDisabled} key="cancel" style={{ ...buttonStyle }} onClick={onCancelButtonClick}>
{props.cancelButtonLabel ? props.cancelButtonLabel : _('Cancel')}

View File

@@ -2,11 +2,10 @@ import * as React from 'react';
import { useEffect, useState, useRef, useCallback } from 'react';
import { isInsideContainer } from '@joplin/lib/dom';
type OnButtonClick = ()=> void;
interface Props {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onOkButtonClick: Function;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onCancelButtonClick: Function;
onOkButtonClick: null|OnButtonClick;
onCancelButtonClick: null|OnButtonClick;
}
const globalKeydownHandlers: string[] = [];
@@ -48,15 +47,17 @@ export default (props: Props) => {
if (!isTopDialog() || isInSubModal(event.target)) return;
if (event.keyCode === 13) {
if (event.keyCode === 13 && props.onOkButtonClick) {
if ('nodeName' in event.target && event.target.nodeName === 'INPUT') {
const target = event.target as HTMLInputElement;
if (target.type !== 'button' && target.type !== 'checkbox') {
event.preventDefault();
props.onOkButtonClick();
}
}
} else if (event.keyCode === 27) {
} else if (event.keyCode === 27 && props.onCancelButtonClick) {
event.preventDefault();
props.onCancelButtonClick();
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied

View File

@@ -172,7 +172,12 @@ export default function NoteContentPropertiesDialog(props: NoteContentProperties
<div style={{ ...labelCompStyle, marginTop: 10 }}>
{readTimeLabel}
</div>
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
<DialogButtonRow
themeId={props.themeId}
onClick={buttonRow_click}
okButtonShow={false}
cancelButtonLabel={_('Close')}
/>
</Dialog>
);
}

View File

@@ -8,7 +8,36 @@ const logger = Logger.create('useEditorSearch');
// Registers a helper CodeMirror extension to be used with
// useEditorSearchHandler.
export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulation) {
interface SetMarkersOptions {
selectedIndex: number;
searchTimestamp: number;
showEditorMarkers?: boolean;
withSelection?: boolean;
}
type Keyword = { value: string };
export type OnSetMarkers = (cm: CodeMirror5Emulation, keywords: Keyword[], options: SetMarkersOptions)=> number;
// Modified from codemirror/addons/search/search.js
const searchOverlay = (query: RegExp) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return { token: function(stream: any) {
query.lastIndex = stream.pos;
const match = query.exec(stream.string);
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1;
return 'search-marker';
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
return null;
} };
};
export default function useEditorSearchExtension() {
const [markers, setMarkers] = useState([]);
const [overlay, setOverlay] = useState(null);
@@ -48,23 +77,6 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
setOverlayTimeout(null);
}, [scrollbarMarks, overlay, overlayTimeout]);
// Modified from codemirror/addons/search/search.js
const searchOverlay = useCallback((query: RegExp) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return { token: function(stream: any) {
query.lastIndex = stream.pos;
const match = query.exec(stream.string);
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1;
return 'search-marker';
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
return null;
} };
}, []);
// Highlights the currently active found work
// It's possible to get tricky with this functions and just use findNext/findPrev
@@ -115,16 +127,17 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
};
}, []);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
CodeMirror?.defineExtension('setMarkers', function(keywords: any, options: any) {
const onSetMarkers: OnSetMarkers = (cm, keywords, options) => {
// Pass arguments in via options to allow the extension to work if multiple editors are open simultaneously
// See https://github.com/laurent22/joplin/issues/13399.
if (!options) {
options = { selectedIndex: 0, searchTimestamp: 0 };
}
if (options.showEditorMarkers === false) {
clearMarkers();
clearOverlay(this);
return;
clearOverlay(cm);
return 0;
}
clearMarkers();
@@ -145,7 +158,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
try {
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
const match = highlightSearch(cm, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
if (match) marks.push(match);
} catch (error) {
if (error.name !== 'SyntaxError') {
@@ -165,7 +178,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
// SEARCHOVERLAY
// We only want to highlight all matches when there is only 1 search term
if (keywords.length !== 1 || keywords[0].value === '') {
clearOverlay(this);
clearOverlay(cm);
const prev = keywords.length > 1 ? keywords[0].value : '';
setPreviousKeywordValue(prev);
return 0;
@@ -175,22 +188,22 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
// Determine the number of matches in the source, this is passed on
// to the NoteEditor component
const regexMatches = this.getValue().match(searchTerm);
const regexMatches = cm.getValue().match(searchTerm);
const nMatches = regexMatches ? regexMatches.length : 0;
// Don't bother clearing and re-calculating the overlay if the search term
// hasn't changed
if (keywords[0].value === previousKeywordValue) return nMatches;
clearOverlay(this);
clearOverlay(cm);
setPreviousKeywordValue(keywords[0].value);
// These operations are pretty slow, so we won't add use them until the user
// has finished typing, 500ms is probably enough time
const timeout = shim.setTimeout(() => {
const scrollMarks = this.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
const scrollMarks = cm.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
const overlay = searchOverlay(searchTerm);
this.addOverlay(overlay);
cm.addOverlay(overlay);
setOverlay(overlay);
setScrollbarMarks(scrollMarks);
}, 500);
@@ -199,5 +212,9 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
overlayTimeoutRef.current = timeout;
return nMatches;
});
};
const onSetMarkersRef = useRef(onSetMarkers);
onSetMarkersRef.current = onSetMarkers;
return { onSetMarkersRef };
}

View File

@@ -2,6 +2,8 @@ import { RefObject, useEffect, useMemo, useRef } from 'react';
import usePrevious from '../../../../hooks/usePrevious';
import { RenderedBody } from './types';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import CodeMirror5Emulation from '@joplin/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation';
import useEditorSearchExtension from './useEditorSearchExtension';
const debounce = require('debounce');
interface Props {
@@ -10,8 +12,7 @@ interface Props {
searchMarkers: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
webviewRef: RefObject<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
editorRef: RefObject<any>;
editorRef: RefObject<CodeMirror5Emulation>;
noteContent: string;
renderedBody: RenderedBody;
@@ -23,6 +24,8 @@ const useEditorSearchHandler = (props: Props) => {
webviewRef, editorRef, renderedBody, noteContent, searchMarkers, showEditorMarkers,
} = props;
const { onSetMarkersRef } = useEditorSearchExtension();
const previousContent = usePrevious(noteContent);
const previousRenderedBody = usePrevious(renderedBody);
const previousSearchMarkers = usePrevious(searchMarkers);
@@ -31,15 +34,15 @@ const useEditorSearchHandler = (props: Props) => {
// Fixes https://github.com/laurent22/joplin/issues/7565
const debouncedMarkers = useMemo(() => debounce((searchMarkers: SearchMarkers) => {
if (!editorRef.current) return;
if (!onSetMarkersRef.current) return;
if (showEditorMarkersRef.current) {
const matches = editorRef.current.setMarkers(searchMarkers.keywords, searchMarkers.options);
const matches = onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, searchMarkers.options);
props.setLocalSearchResultCount(matches);
} else {
editorRef.current.setMarkers(searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
}
}, 50), [editorRef, props.setLocalSearchResultCount]);
}, 50), [editorRef, onSetMarkersRef, props.setLocalSearchResultCount]);
useEffect(() => {
if (!searchMarkers) return () => {};
@@ -59,7 +62,7 @@ const useEditorSearchHandler = (props: Props) => {
}
return () => {};
}, [
editorRef,
onSetMarkersRef,
webviewRef,
searchMarkers,
previousSearchMarkers,
@@ -71,6 +74,10 @@ const useEditorSearchHandler = (props: Props) => {
debouncedMarkers,
]);
return {
// Returned to allow quickly setting the initial search markers just after the editor loads.
onSetInitialMarkersRef: onSetMarkersRef,
};
};
export default useEditorSearchHandler;

View File

@@ -695,7 +695,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [renderedBody, webviewReady]);
useEditorSearchHandler({
const { onSetInitialMarkersRef } = useEditorSearchHandler({
setLocalSearchResultCount: props.setLocalSearchResultCount,
searchMarkers: props.searchMarkers,
webviewRef,
@@ -737,6 +737,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
<Editor
value={props.content}
searchMarkers={props.searchMarkers}
onSetMarkersRef={onSetInitialMarkersRef}
ref={editorRef}
mode={props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML ? 'xml' : 'joplin-markdown'}
codeMirrorTheme={styles.editor.codeMirrorTheme}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef, RefObject } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import CodeMirror from 'codemirror';
@@ -16,7 +16,7 @@ import useListIdent from './utils/useListIdent';
import useScrollUtils from './utils/useScrollUtils';
import useCursorUtils from './utils/useCursorUtils';
import useLineSorting from './utils/useLineSorting';
import useEditorSearch from '../utils/useEditorSearchExtension';
import { OnSetMarkers } from '../utils/useEditorSearchExtension';
import useJoplinMode from './utils/useJoplinMode';
import useKeymap from './utils/useKeymap';
import useExternalPlugins from './utils/useExternalPlugins';
@@ -77,6 +77,7 @@ export interface EditorProps {
value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
searchMarkers: any;
onSetMarkersRef: RefObject<OnSetMarkers>;
mode: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
style: any;
@@ -119,7 +120,6 @@ function Editor(props: EditorProps, ref: any) {
useScrollUtils(CodeMirror);
useCursorUtils(CodeMirror);
useLineSorting(CodeMirror);
useEditorSearch(CodeMirror);
useJoplinMode(CodeMirror);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
@@ -228,7 +228,7 @@ function Editor(props: EditorProps, ref: any) {
// It's possible for searchMarkers to be available before the editor
// In these cases we set the markers asap so the user can see them as
// soon as the editor is ready
if (props.searchMarkers) { cm.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options); }
if (props.searchMarkers) { props.onSetMarkersRef.current(cm, props.searchMarkers.keywords, props.searchMarkers.options); }
return () => {
// Clean up codemirror

View File

@@ -31,6 +31,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import useRefocusOnVisiblePaneChange from './utils/useRefocusOnVisiblePaneChange';
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
import eventManager, { EventName, ResourceChangeEvent } from '@joplin/lib/eventManager';
import useSyncEditorValue from './utils/useSyncEditorValue';
const logger = Logger.create('CodeMirror6');
const logDebug = (message: string) => logger.debug(message);
@@ -167,9 +168,8 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
},
scrollTo: (options: ScrollOptions) => {
if (options.type === ScrollOptionTypes.Hash) {
if (!webviewRef.current) return;
const hash: string = options.value;
webviewRef.current.send('scrollToHash', hash);
webviewRef.current?.send('scrollToHash', hash);
editorRef.current.jumpToHash(hash);
} else if (options.type === ScrollOptionTypes.Percent) {
const percent = options.value as number;
@@ -342,6 +342,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
} else if (event.kind === EditorEventType.Change) {
codeMirror_change(event.value);
} else if (event.kind === EditorEventType.SelectionRangeChange) {
props.onCursorMotion({ markdown: event.from });
setSelectionRange({ from: event.from, to: event.to });
} else if (event.kind === EditorEventType.UpdateSearchDialog) {
if (lastSearchState.current?.searchText !== event.searchState.searchText) {
@@ -355,7 +356,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
} else if (event.kind === EditorEventType.FollowLink) {
void CommandService.instance().execute('openItem', event.link);
}
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch]);
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch, props.onCursorMotion]);
const onSelectPastBeginning = useCallback(() => {
void CommandService.instance().execute('focusElement', 'noteTitle');
@@ -400,15 +401,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
props.tabMovesFocus,
]);
// Update the editor's value
useEffect(() => {
// Include the noteId in the update props to give plugins access
// to the current note ID.
const updateProps = { noteId: props.noteId };
if (editorRef.current?.updateBody(props.content, updateProps)) {
editorRef.current?.clearHistory();
}
}, [props.content, props.noteId]);
const initialCursorLocationRef = useRef(0);
initialCursorLocationRef.current = props.initialCursorLocation.markdown ?? 0;
useSyncEditorValue({
content: props.content,
visiblePanes: props.visiblePanes,
onMessage: props.onMessage,
editorRef,
noteId: props.noteId,
initialCursorLocationRef,
});
const renderEditor = () => {
return (
@@ -416,6 +419,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
<Editor
style={styles.editor}
initialText={props.content}
initialSelectionRef={initialCursorLocationRef}
initialNoteId={props.noteId}
ref={editorRef}
settings={editorSettings}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { ForwardedRef } from 'react';
import { ForwardedRef, RefObject } from 'react';
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { EditorProps, LogMessageCallback, OnEventCallback, ContentScriptData } from '@joplin/editor/types';
import createEditor from '@joplin/editor/CodeMirror/createEditor';
@@ -11,7 +11,6 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import { dirname } from 'path';
import useKeymap from './utils/useKeymap';
import useEditorSearch from '../utils/useEditorSearchExtension';
import CommandService from '@joplin/lib/services/CommandService';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import localisation from './utils/localisation';
@@ -23,6 +22,7 @@ import getResourceBaseUrl from '../../../utils/getResourceBaseUrl';
interface Props extends EditorProps {
style: React.CSSProperties;
pluginStates: PluginStates;
initialSelectionRef: RefObject<number>;
onEditorPaste: (event: Event)=> void;
externalSearch: SearchMarkers;
@@ -43,8 +43,6 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
onLogMessageRef.current = props.onLogMessage;
}, [props.onEvent, props.onLogMessage]);
useEditorSearch(editor);
useEffect(() => {
if (!editor) {
return () => {};
@@ -127,6 +125,9 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
direction: 'unset',
},
});
const cursor = props.initialSelectionRef.current;
editor.select(cursor, cursor);
setEditor(editor);
return () => {

View File

@@ -0,0 +1,50 @@
import { useEffect, useRef, RefObject } from 'react';
import { OnMessage } from '../../../../utils/types';
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
interface Props {
content: string;
visiblePanes: string[];
onMessage: OnMessage;
editorRef: RefObject<CodeMirrorControl>;
noteId: string;
initialCursorLocationRef: RefObject<number>;
}
// Updates the editor's value as necessary
const useSyncEditorValue = ({ content, visiblePanes, onMessage, editorRef, noteId, initialCursorLocationRef }: Props) => {
const visiblePanesRef = useRef(visiblePanes);
visiblePanesRef.current = visiblePanes;
const onMessageRef = useRef(onMessage);
onMessageRef.current = onMessage;
const lastNoteIdRef = useRef(noteId);
useEffect(() => {
// Include the noteId in the update props to give plugins access
// to the current note ID.
const updateProps = { noteId: noteId };
if (editorRef.current?.updateBody(content, updateProps)) {
editorRef.current?.clearHistory();
// Only reset the cursor location when switching notes. If, for example,
// the note is updated from a secondary window, the cursor location shouldn't
// reset.
const noteChanged = lastNoteIdRef.current !== noteId;
if (noteChanged) {
const cursorLocation = initialCursorLocationRef.current;
editorRef.current?.select(cursorLocation, cursorLocation);
}
lastNoteIdRef.current = noteId;
// If the viewer isn't visible, the content should be considered rendered
// after the editor has finished updating:
if (!visiblePanesRef.current.includes('viewer')) {
onMessageRef.current({ channel: 'noteRenderComplete' });
}
}
}, [content, noteId, editorRef, initialCursorLocationRef]);
};
export default useSyncEditorValue;

View File

@@ -23,7 +23,7 @@ import { themeStyle } from '@joplin/lib/theme';
import { loadScript } from '../../../utils/loadScript';
import bridge from '../../../../services/bridge';
import { TinyMceEditorEvents } from './utils/types';
import type { Editor, EditorEvent } from 'tinymce';
import type { Bookmark, Editor, EditorEvent } from 'tinymce';
import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands';
import shouldPasteResources from './utils/shouldPasteResources';
import lightTheme from '@joplin/lib/themes/light';
@@ -47,6 +47,7 @@ import Setting from '@joplin/lib/models/Setting';
import useTextPatternsLookup, { TextPatternContext } from './utils/useTextPatternsLookup';
import { toFileProtocolPath } from '@joplin/utils/path';
import { RenderResultPluginAsset } from '@joplin/renderer/types';
import useCursorPositioning from './utils/useCursorPositioning';
const logger = Logger.create('TinyMCE');
@@ -918,8 +919,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
editor.on('SetContent', () => {
preprocessContent();
props_onMessage.current({ channel: 'noteRenderComplete' });
});
},
});
@@ -1046,6 +1045,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
return true;
}
const { onRestoreCursorPosition } = useCursorPositioning({
initialCursorLocation: props.initialCursorLocation.richText as Bookmark,
onCursorUpdate: props.onCursorMotion,
editor,
});
const lastNoteIdRef = useRef(props.noteId);
useEffect(() => {
if (!editor) return () => {};
@@ -1113,8 +1118,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
// times would result in an empty note.
// https://github.com/laurent22/joplin/issues/3534
editor.undoManager.reset();
// Only restore the cursor position from the global state when switching notes.
// See https://github.com/laurent22/joplin/issues/13579
onRestoreCursorPosition();
} else {
// Restore the cursor location
// Restore the cursor location from the current note
editor.selection.bookmarkManager.moveToBookmark(bookmark);
}
@@ -1123,6 +1132,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
resourceInfos: props.resourceInfos,
contentKey: props.contentKey,
};
props_onMessage.current({ channel: 'noteRenderComplete' });
}
const allAssetsOptions: NoteStyleOptions = {
@@ -1387,16 +1397,18 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
async function onCopy(event: any) {
const copiedContent = editor.selection.getContent();
if (!copiedContent) return;
copyHtmlToClipboard(copiedContent);
event.preventDefault();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
async function onCut(event: any) {
event.preventDefault();
const selectedContent = editor.selection.getContent();
if (!selectedContent) return;
copyHtmlToClipboard(selectedContent);
editor.insertContent('');
event.preventDefault();
onChangeHandler();
}
@@ -1444,7 +1456,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
// `compositionend` means that a user has finished entering a Chinese
// (or other languages that require IME) character.
editor.on(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
editor.on(TinyMceEditorEvents.Cut, onCut);
editor.on(TinyMceEditorEvents.Cut, onCut, true);
editor.on(TinyMceEditorEvents.JoplinChange, onChangeHandler);
editor.on(TinyMceEditorEvents.Undo, onChangeHandler);
editor.on(TinyMceEditorEvents.Redo, onChangeHandler);

View File

@@ -0,0 +1,52 @@
import { useCallback, useEffect, useRef } from 'react';
import { Bookmark, Editor } from 'tinymce';
import { OnCursorMotion } from '../../../utils/types';
interface Props {
initialCursorLocation: Bookmark;
editor: Editor;
onCursorUpdate: OnCursorMotion;
}
const useCursorPositioning = ({ initialCursorLocation, editor, onCursorUpdate }: Props) => {
const initialCursorLocationRef = useRef(initialCursorLocation);
initialCursorLocationRef.current = initialCursorLocation;
const appliedInitialCursorLocationRef = useRef(false);
const onRestoreCursorPosition = useCallback(() => {
if (editor) {
if (initialCursorLocationRef.current) {
editor.selection.moveToBookmark(initialCursorLocationRef.current);
}
appliedInitialCursorLocationRef.current = true;
}
}, [editor]);
useEffect(() => {
if (!editor) return () => {};
const onSelectionChange = () => {
// Wait until the initial cursor position has been set. This avoids resetting
// the initial cursor position to zero when the editor first loads.
if (!appliedInitialCursorLocationRef.current) return;
// Use an offset bookmark -- the default bookmark type is not preserved after unloading
// and reloading the editor.
const offsetBookmarkId = 2;
onCursorUpdate({
richText: editor.selection.getBookmark(offsetBookmarkId, true),
});
};
editor.on('SelectionChange', onSelectionChange);
return () => {
editor.off('SelectionChange', onSelectionChange);
};
}, [editor, onCursorUpdate, onRestoreCursorPosition]);
return { onRestoreCursorPosition };
};
export default useCursorPositioning;

View File

@@ -18,7 +18,7 @@ import { NoteEditorProps, FormNote, OnChangeEvent, AllAssetsOptions, NoteBodyEdi
import CommandService from '@joplin/lib/services/CommandService';
import Button, { ButtonLevel } from '../Button/Button';
import eventManager, { EventName } from '@joplin/lib/eventManager';
import { AppState } from '../../app.reducer';
import { AppState, EditorCursorLocations } from '../../app.reducer';
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import { _, _n } from '@joplin/lib/locale';
import NoteTitleBar from './NoteTitle/NoteTitleBar';
@@ -57,6 +57,7 @@ import StatusBar from './StatusBar';
import useVisiblePluginEditorViewIds from '@joplin/lib/hooks/plugins/useVisiblePluginEditorViewIds';
import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
import getResourceBaseUrl from './utils/getResourceBaseUrl';
import useInitialCursorLocation from './utils/useInitialCursorLocation';
const debounce = require('debounce');
@@ -329,13 +330,14 @@ function NoteEditorContent(props: NoteEditorProps) {
});
}, [formNote, setFormNote, handleProvisionalFlag, props.dispatch]);
const { scrollWhenReady, clearScrollWhenReady } = useScrollWhenReadyOptions({
const { scrollWhenReadyRef, clearScrollWhenReady } = useScrollWhenReadyOptions({
noteId: formNote.id,
selectedNoteHash: props.selectedNoteHash,
lastEditorScrollPercents: props.lastEditorScrollPercents,
editorRef,
editorName: props.bodyEditor,
});
const onMessage = useMessageHandler(scrollWhenReady, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
const onMessage = useMessageHandler(scrollWhenReadyRef, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
useResourceUnwatcher({ noteId: formNote.id, windowId });
@@ -409,6 +411,14 @@ function NoteEditorContent(props: NoteEditorProps) {
});
}, [props.dispatch]);
const onCursorMotion = useCallback((location: EditorCursorLocations) => {
props.dispatch({
type: 'EDITOR_CURSOR_POSITION_SET',
noteId: formNoteRef.current.id,
location,
});
}, [props.dispatch]);
function renderNoNotes(rootStyle: React.CSSProperties) {
const emptyDivStyle = {
backgroundColor: 'black',
@@ -419,6 +429,9 @@ function NoteEditorContent(props: NoteEditorProps) {
}
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
const initialCursorLocation = useInitialCursorLocation({
lastEditorCursorLocations: props.lastEditorCursorLocations, noteId: props.noteId,
});
const markupLanguage = formNote.markup_language;
const editorProps: NoteBodyEditorPropsAndRef = {
@@ -432,6 +445,7 @@ function NoteEditorContent(props: NoteEditorProps) {
content: formNote.body,
contentMarkupLanguage: markupLanguage,
contentOriginalCss: formNote.originalCss,
initialCursorLocation,
resourceInfos: resourceInfos,
resourceDirectory: Setting.value('resourceDir'),
htmlToMarkdown: htmlToMarkdown,
@@ -442,6 +456,7 @@ function NoteEditorContent(props: NoteEditorProps) {
dispatch: props.dispatch,
noteToolbar: null,
onScroll: onScroll,
onCursorMotion,
setLocalSearchResultCount: setLocalSearchResultCount,
setLocalSearch: localSearch_change,
setShowLocalSearch,
@@ -729,6 +744,7 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
notesParentType: windowState.notesParentType,
selectedNoteTags: windowState.selectedNoteTags,
lastEditorScrollPercents: state.lastEditorScrollPercents,
lastEditorCursorLocations: state.lastEditorCursorLocations,
selectedNoteHash: windowState.selectedNoteHash,
searches: state.searches,
selectedSearchId: windowState.selectedSearchId,

View File

@@ -15,6 +15,7 @@ const joplinRendererUtils = require('@joplin/renderer').utils;
const { clipboard } = require('electron');
import * as mimeUtils from '@joplin/lib/mime-utils';
import bridge from '../../../services/bridge';
import { getCollator, getCollatorLocale } from '@joplin/lib/models/utils/getCollator';
const md5 = require('md5');
const path = require('path');
@@ -43,22 +44,30 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
if (!filePaths || !filePaths.length) return null;
}
const collatorLocale = getCollatorLocale();
const collator = getCollator(collatorLocale);
filePaths = filePaths.sort((a, b) => {
return collator.compare(a, b);
});
let pos = options.position ?? 0;
for (let i = 0; i < filePaths.length; i++) {
const filePath = filePaths[i];
const beforeLen = body.length;
try {
logger.info(`Attaching ${filePath}`);
const newBody = await shim.attachFileToNoteBody(body, filePath, options.position, {
const newBody = await shim.attachFileToNoteBody(body, filePath, pos, {
createFileURL: options.createFileURL,
resizeLargeImages: Setting.value('imageResizing'),
markupLanguage: options.markupLanguage,
resourceSuffix: i > 0 ? ' ' : '',
resourcePrefix: i > 0 ? ' ' : '',
});
if (!newBody) {
logger.info('File attachment was cancelled');
return null;
}
pos += newBody.length - beforeLen;
body = newBody;
logger.info('File was attached.');
} catch (error) {
@@ -66,7 +75,6 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
bridge().showErrorMessageBox(error.message);
}
}
return body;
}

View File

@@ -14,6 +14,7 @@ import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
import { RefObject, SetStateAction } from 'react';
import * as React from 'react';
import { ResourceEntity, ResourceLocalStateEntity } from '@joplin/lib/services/database/types';
import { EditorCursorLocations, NoteIdToEditorCursorLocations, NoteIdToScrollPercent } from '../../../app.reducer';
export interface AllAssetsOptions {
contentMaxWidthTarget?: string;
@@ -40,8 +41,8 @@ export interface NoteEditorProps {
notesParentType: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
selectedNoteTags: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
lastEditorScrollPercents: any;
lastEditorScrollPercents: NoteIdToScrollPercent;
lastEditorCursorLocations: NoteIdToEditorCursorLocations;
selectedNoteHash: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
searches: any[];
@@ -83,6 +84,14 @@ export interface NoteBodyEditorRef {
export { MarkupToHtmlOptions };
export type MarkupToHtmlHandler = (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
export type HtmlToMarkdownHandler = (markupLanguage: number, html: string, originalCss: string, parseOptions?: ParseOptions)=> Promise<string>;
export type OnCursorMotion = (event: EditorCursorLocations)=> void;
export interface MessageEvent {
channel: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partially refactored old code before rule was applied
args?: any[];
}
export type OnMessage = (event: MessageEvent)=> void;
export interface NoteBodyEditorProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -102,12 +111,13 @@ export interface NoteBodyEditorProps {
contentKey: string;
contentMarkupLanguage: number;
contentOriginalCss: string;
initialCursorLocation: EditorCursorLocations;
onChange(event: OnChangeEvent): void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onWillChange(event: any): void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onMessage(event: any): void;
onMessage: OnMessage;
onScroll(event: { percent: number }): void;
onCursorMotion: OnCursorMotion;
markupToHtml: MarkupToHtmlHandler;
htmlToMarkdown: HtmlToMarkdownHandler;
allAssets: (markupLanguage: MarkupLanguage, options: AllAssetsOptions)=> Promise<RenderResultPluginAsset[]>;

View File

@@ -0,0 +1,17 @@
import { useMemo } from 'react';
import { EditorCursorLocations, NoteIdToEditorCursorLocations } from '../../../app.reducer';
interface Props {
lastEditorCursorLocations: NoteIdToEditorCursorLocations;
noteId: string;
}
const useInitialCursorLocation = ({ noteId, lastEditorCursorLocations }: Props) => {
const lastCursorLocation = lastEditorCursorLocations[noteId];
return useMemo((): EditorCursorLocations => {
return lastCursorLocation ?? { };
}, [lastCursorLocation]);
};
export default useInitialCursorLocation;

View File

@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions } from './types';
import { RefObject, useCallback } from 'react';
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions, MessageEvent } from './types';
import contextMenu from './contextMenu';
import CommandService from '@joplin/lib/services/CommandService';
import PostMessageService from '@joplin/lib/services/PostMessageService';
@@ -8,7 +8,7 @@ import { reg } from '@joplin/lib/registry';
import bridge from '../../../services/bridge';
export default function useMessageHandler(
scrollWhenReady: ScrollOptions|null,
scrollWhenReadyRef: RefObject<ScrollOptions|null>,
clearScrollWhenReady: ()=> void,
windowId: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -21,8 +21,7 @@ export default function useMessageHandler(
htmlToMd: HtmlToMarkdownHandler,
mdToHtml: MarkupToHtmlHandler,
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return useCallback(async (event: any) => {
return useCallback(async (event: MessageEvent) => {
const msg = event.channel ? event.channel : '';
const args = event.args;
const arg0 = args && args.length >= 1 ? args[0] : null;
@@ -35,8 +34,8 @@ export default function useMessageHandler(
s.splice(0, 1);
reg.logger().error(s.join(':'));
} else if (msg === 'noteRenderComplete') {
if (scrollWhenReady) {
const options = { ...scrollWhenReady };
if (scrollWhenReadyRef.current) {
const options = { ...scrollWhenReadyRef.current };
clearScrollWhenReady();
editorRef.current.scrollTo(options);
}
@@ -78,5 +77,5 @@ export default function useMessageHandler(
// bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [dispatch, setLocalSearchResultCount, scrollWhenReady, formNote]);
}, [dispatch, setLocalSearchResultCount, scrollWhenReadyRef, formNote]);
}

View File

@@ -1,41 +1,48 @@
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
import { RefObject, useCallback, useRef } from 'react';
import { NoteBodyEditorRef, ScrollOptions, ScrollOptionTypes } from './types';
import usePrevious from '@joplin/lib/hooks/usePrevious';
import type { EditorScrollPercents } from '../../../app.reducer';
import type { NoteIdToScrollPercent } from '../../../app.reducer';
import useNowEffect from '@joplin/lib/hooks/useNowEffect';
interface Props {
noteId: string;
editorName: string;
selectedNoteHash: string;
lastEditorScrollPercents: EditorScrollPercents;
lastEditorScrollPercents: NoteIdToScrollPercent;
editorRef: RefObject<NoteBodyEditorRef>;
}
const useScrollWhenReadyOptions = ({ noteId, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions|null>(null);
const useScrollWhenReadyOptions = ({ noteId, editorName, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
const scrollWhenReadyRef = useRef<ScrollOptions|null>(null);
const previousNoteId = usePrevious(noteId);
const lastScrollPercentsRef = useRef<EditorScrollPercents>(null);
const noteIdChanged = noteId !== previousNoteId;
const previousEditor = usePrevious(editorName);
const editorChanged = editorName !== previousEditor;
const lastScrollPercentsRef = useRef<NoteIdToScrollPercent>(null);
lastScrollPercentsRef.current = lastEditorScrollPercents;
useEffect(() => {
if (noteId === previousNoteId) return;
// This needs to be a nowEffect to prevent race conditions
useNowEffect(() => {
if (!editorChanged && !noteIdChanged) return () => {};
if (editorRef.current) {
editorRef.current.resetScroll();
}
const lastScrollPercent = lastScrollPercentsRef.current[noteId] || 0;
setScrollWhenReady({
scrollWhenReadyRef.current = {
type: selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent,
value: selectedNoteHash ? selectedNoteHash : lastScrollPercent,
});
}, [noteId, previousNoteId, selectedNoteHash, editorRef]);
};
return () => {};
}, [editorChanged, noteIdChanged, noteId, selectedNoteHash, editorRef]);
const clearScrollWhenReady = useCallback(() => {
setScrollWhenReady(null);
scrollWhenReadyRef.current = null;
}, []);
return { scrollWhenReady, clearScrollWhenReady };
return { scrollWhenReadyRef, clearScrollWhenReady };
};
export default useScrollWhenReadyOptions;

View File

@@ -501,7 +501,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
<div role='table' aria-labelledby='note-properties-dialog-title'>
{noteComps}
</div>
<DialogButtonRow themeId={this.props.themeId} okButtonShow={!this.isReadOnly()} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
<DialogButtonRow
themeId={this.props.themeId}
okButtonShow={!this.isReadOnly()}
okButtonRef={this.okButton}
onClick={this.buttonRow_click}
/>
</Dialog>
);
}

View File

@@ -71,7 +71,7 @@ async function initialize() {
panes: Setting.value('noteVisiblePanes'),
});
InteropService.instance().document = document;
InteropService.instance().domParser = new DOMParser();
InteropService.instance().xmlSerializer = new XMLSerializer();
}

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import Dialog from '../Dialog';
import DialogButtonRow, { ClickEvent, ButtonSpec } from '../DialogButtonRow';
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
import DialogTitle from '../DialogTitle';
import { _ } from '@joplin/lib/locale';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FolderEntity } from '@joplin/lib/services/database/types';
import Folder from '@joplin/lib/models/Folder';
import ShareService, { ApiShare } from '@joplin/lib/services/share/ShareService';
@@ -129,7 +129,6 @@ function ShareFolderDialog(props: Props) {
const [share, setShare] = useState<StateShare>(null);
const [shareUsers, setShareUsers] = useState<StateShareUser[]>([]);
const [shareState, setShareState] = useState<ShareState>(ShareState.Idle);
const [customButtons, setCustomButtons] = useState<ButtonSpec[]>([]);
const [recipientsBeingUpdated, setRecipientsBeingUpdated] = useState<Record<string, boolean>>({});
async function synchronize(event: AsyncEffectEvent = null) {
@@ -163,13 +162,6 @@ function ShareFolderDialog(props: Props) {
void ShareService.instance().refreshShareUsers(share.id);
}, [share]);
useEffect(() => {
setCustomButtons(share ? [{
name: 'unshare',
label: _('Unshare'),
}] : []);
}, [share]);
useEffect(() => {
if (!share) return;
const sus = props.shareUsers[share.id];
@@ -177,10 +169,6 @@ function ShareFolderDialog(props: Props) {
setShareUsers(sus);
}, [share, props.shareUsers]);
useEffect(() => {
void ShareService.instance().refreshShares();
}, [props.folderId]);
const permissionsFromString = (p: string): SharePermissions => {
return {
can_read: 1,
@@ -269,7 +257,7 @@ function ShareFolderDialog(props: Props) {
}, []);
function renderAddRecipient() {
const disabled = shareState !== ShareState.Idle;
const disabled = shareState !== ShareState.Idle && shareState !== ShareState.Synchronizing;
const dropdown = !props.canUseSharePermissions ? null : <Dropdown className="permission-dropdown" options={permissionOptions} value={recipientPermissions} onChange={recipientPermissions_change}/>;
@@ -395,6 +383,17 @@ function ShareFolderDialog(props: Props) {
props.onClose();
}
const customButtons = useMemo(() => {
return share ? [{
name: 'unshare',
label: _('Unshare'),
// Don't allow unsharing the folder during the "create" action. Doing so might
// be able to cause issues similar to #13518 (e.g. if the "unshare" action completes while
// the "share" action is still in progress).
disabled: shareState === ShareState.Creating || shareState === ShareState.Synchronizing,
}] : [];
}, [share, shareState]);
function renderContent() {
return (
<StyledRoot className="share-folder-dialog">

View File

@@ -25,14 +25,13 @@ interface Props {
const SidebarComponent = (props: Props) => {
const renderSynchronizeButton = (type: string) => {
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
const iconAnimation = type !== 'sync' ? 'icon-infinite-rotation 1s linear infinite' : '';
return (
<StyledSynchronizeButton
level={ButtonLevel.SidebarSecondary}
className={`sidebar-sync-button ${type === 'sync' ? '' : '-syncing'}`}
iconName="icon-sync"
key="sync_button"
iconAnimation={iconAnimation}
title={label}
onClick={() => {
void CommandService.instance().execute('synchronize', type !== 'sync');

View File

@@ -356,6 +356,7 @@ const useOnRenderItem = (props: Props) => {
onClick={tagItem_click}
onTagDrop={onTagDrop_}
onContextMenu={onItemContextMenu}
label={item.label}
tag={tag}
itemCount={itemCount}
index={index}
@@ -384,7 +385,7 @@ const useOnRenderItem = (props: Props) => {
anchorRef={anchorRef}
selected={selected}
folderId={folder.id}
folderTitle={Folder.displayTitle(folder)}
folderTitle={item.label}
folderIcon={Folder.unserializeIcon(folder.icon)}
depth={item.depth}
isExpanded={isExpanded}

View File

@@ -49,6 +49,24 @@ const getParentOffset = (childIndex: number, listItems: ListItem[]): number|null
return null;
};
const findNextTypeAheadMatch = (selectedIndex: number, query: string, listItems: ListItem[]) => {
const matches = (item: ListItem) => {
return item.label.startsWith(query);
};
const indexBefore = listItems.slice(0, selectedIndex).findIndex(matches);
// Search in all results **after** the current. This prevents the current item from
// always being identified as the next match, if the user repeatedly presses the
// same key.
const startAfter = selectedIndex + 1;
let indexAfter = listItems.slice(startAfter).findIndex(matches);
if (indexAfter !== -1) {
indexAfter += startAfter;
}
// Prefer jumping to the next match, rather than the previous
const matchingIndex = indexAfter !== -1 ? indexAfter : indexBefore;
return matchingIndex;
};
const useOnSidebarKeyDownHandler = (props: Props) => {
const { updateSelectedIndex, listItems, selectedIndex, collapsedFolderIds, dispatch } = props;
@@ -56,6 +74,8 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
const selectedItem = listItems[selectedIndex];
let indexChange = 0;
const ctrlAltOrMeta = event.ctrlKey || event.altKey || event.metaKey;
if (selectedItem && isToggleShortcut(event.code, selectedItem, collapsedFolderIds)) {
event.preventDefault();
@@ -82,9 +102,22 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
indexChange = 1;
} else if ((event.ctrlKey || event.metaKey) && event.code === 'KeyA') { // ctrl+a or cmd+a
event.preventDefault();
} else if (event.code === 'Home') {
event.preventDefault();
updateSelectedIndex(0);
indexChange = 0;
} else if (event.code === 'End') {
event.preventDefault();
updateSelectedIndex(listItems.length - 1);
indexChange = 0;
} else if (event.code === 'Enter' && !event.shiftKey) {
event.preventDefault();
void CommandService.instance().execute('focusElement', 'noteList');
} else if (selectedIndex && selectedIndex >= 0 && event.key.length === 1 && !ctrlAltOrMeta) {
const nextMatch = findNextTypeAheadMatch(selectedIndex, event.key, listItems);
if (nextMatch !== -1) {
indexChange = nextMatch - selectedIndex;
}
}
if (indexChange !== 0) {

View File

@@ -4,6 +4,8 @@ import { FolderEntity, TagsWithNoteCountEntity } from '@joplin/lib/services/data
import { buildFolderTree, renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
import { _ } from '@joplin/lib/locale';
import toggleHeader from './utils/toggleHeader';
import Folder from '@joplin/lib/models/Folder';
import Tag from '@joplin/lib/models/Tag';
interface Props {
tags: TagsWithNoteCountEntity[];
@@ -18,6 +20,7 @@ const useSidebarListData = (props: Props): ListItem[] => {
return renderTags<ListItem>(props.tags, (tag): TagListItem => {
return {
kind: ListItemType.Tag,
label: Tag.displayTitle(tag),
tag,
key: tag.id,
depth: 1,
@@ -38,6 +41,7 @@ const useSidebarListData = (props: Props): ListItem[] => {
return renderFolders<ListItem>(renderProps, (folder, hasChildren, depth): FolderListItem => {
return {
kind: ListItemType.Folder,
label: Folder.displayTitle(folder),
folder,
hasChildren,
// The toplevel headers have depth 1, so the toplevel notebook needs
@@ -65,9 +69,9 @@ const useSidebarListData = (props: Props): ListItem[] => {
hasChildren: folderItems.items.length > 0,
};
const foldersSectionContent: ListItem[] = props.folderHeaderIsExpanded ? [
{ kind: ListItemType.AllNotes, key: 'all-notes', depth: 2, hasChildren: false },
{ kind: ListItemType.AllNotes, label: _('All notes'), key: 'all-notes', depth: 2, hasChildren: false },
...folderItems.items,
{ kind: ListItemType.Spacer, key: 'after-folders-spacer', depth: 1, hasChildren: false },
{ kind: ListItemType.Spacer, label: '', key: 'after-folders-spacer', depth: 1, hasChildren: false },
] : [];
const tagsHeader: HeaderListItem = {

View File

@@ -7,7 +7,6 @@ import Setting from '@joplin/lib/models/Setting';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
import CommandService from '@joplin/lib/services/CommandService';
import PerFolderSortOrderService from '../../../services/sortOrder/PerFolderSortOrderService';
import { _ } from '@joplin/lib/locale';
import { connect } from 'react-redux';
import EmptyExpandLink from './EmptyExpandLink';
import ListItemWrapper, { ListItemRef } from './ListItemWrapper';
@@ -70,7 +69,7 @@ const AllNotesItem: React.FC<Props> = props => {
onClick={onAllNotesClick_}
onContextMenu={toggleAllNotesContextMenu}
>
{_('All notes')}
{props.item.label}
</StyledListItemAnchor>
</ListItemWrapper>
);

View File

@@ -58,7 +58,9 @@ const HeaderItem: React.FC<Props> = props => {
{...item.extraProps}
onDrop={props.onDrop}
>
<StyledHeader onClick={onClick}>
<StyledHeader
onClick={onClick}
>
<StyledHeaderIcon aria-hidden='true' role='img' className={item.iconName}/>
<StyledHeaderLabel>{item.label}</StyledHeaderLabel>
</StyledHeader>

View File

@@ -5,7 +5,6 @@ import { StyledListItemAnchor, StyledSpanFix } from '../styles';
import { TagsWithNoteCountEntity } from '@joplin/lib/services/database/types';
import BaseModel from '@joplin/lib/BaseModel';
import NoteCount from './NoteCount';
import Tag from '@joplin/lib/models/Tag';
import EmptyExpandLink from './EmptyExpandLink';
import ListItemWrapper, { ListItemRef } from './ListItemWrapper';
@@ -15,6 +14,7 @@ interface Props {
anchorRef: ListItemRef;
selected: boolean;
tag: TagsWithNoteCountEntity;
label: string;
onTagDrop: React.DragEventHandler<HTMLElement>;
onContextMenu: React.MouseEventHandler<HTMLElement>;
onClick: (event: TagLinkClickEvent)=> void;
@@ -58,7 +58,7 @@ const TagItem = (props: Props) => {
onContextMenu={props.onContextMenu}
onClick={onClickHandler}
>
<StyledSpanFix className="tag-label">{Tag.displayTitle(tag)}</StyledSpanFix>
<StyledSpanFix className="tag-label">{props.label}</StyledSpanFix>
{noteCount}
</StyledListItemAnchor>
</ListItemWrapper>

View File

@@ -5,4 +5,5 @@
@use 'styles/sidebar-expand-link.scss';
@use 'styles/sidebar-header-container.scss';
@use 'styles/sidebar-spacer-item.scss';
@use 'styles/sidebar-header-button.scss';
@use 'styles/sidebar-header-button.scss';
@use 'styles/sidebar-sync-button.scss';

View File

@@ -28,6 +28,11 @@ export const StyledHeader = styled.div`
user-select: none;
text-transform: uppercase;
//cursor: pointer;
cursor: default;
transition: background 0.2s;
&:hover {
background: ${(props: StyleProps) => props.theme.backgroundColorHover2};
}
`;
export const StyledHeaderIcon = styled.i`

View File

@@ -0,0 +1,16 @@
@keyframes icon-infinite-rotation {
to {
transform: rotate(360deg);
}
}
.sidebar-sync-button {
&.-syncing > .icon {
animation: icon-infinite-rotation 1s linear infinite;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
}
}

View File

@@ -16,6 +16,8 @@ export enum ListItemType {
interface BaseListItem {
key: string;
// Used for typeahead
label: string;
depth: number;
hasChildren: boolean;
}
@@ -26,7 +28,6 @@ interface ToplevelListItem extends BaseListItem {
export interface HeaderListItem extends ToplevelListItem {
kind: ListItemType.Header;
label: string;
expanded: boolean;
iconName: string;
id: HeaderId;

View File

@@ -38,7 +38,7 @@ describe('NoteListUtils', () => {
const mockStore = {
getState: () => {
return {
...createAppDefaultWindowState(),
...createAppDefaultWindowState(null),
settings: {},
};
},

View File

@@ -143,7 +143,7 @@ export default class NoteListUtils {
menu.append(new MenuItem({ type: 'separator' }));
if ([9, 10].includes(Setting.value('sync.target'))) {
if ([9, 10, 11].includes(Setting.value('sync.target'))) {
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@@ -2,11 +2,19 @@
<html>
<head>
<meta charset="UTF-8">
<!--
No CPS because we need to allow everything due to some dependencies (eg. depd, which comes from maybe Node or Electron
uses 'eval'.
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'">
-->
<meta
http-equiv="Content-Security-Policy"
content="
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://* ;
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://* ;
font-src 'self' http://* https://* blob: data: joplin-content://* ;
"
/>
<title>Joplin</title>
<!-- Note: Add new dynamic CSS imports to style.scss to allow them to be included in secondary windows. -->
<link rel="stylesheet" href="style.min.css">

View File

@@ -8,6 +8,7 @@ import getMainWindow from './util/getMainWindow';
import setFilePickerResponse from './util/setFilePickerResponse';
import setMessageBoxResponse from './util/setMessageBoxResponse';
import getImageSourceSize from './util/getImageSourceSize';
import setSettingValue from './util/setSettingValue';
test.describe('main', () => {
@@ -19,6 +20,13 @@ test.describe('main', () => {
await mainPage.waitFor();
});
test('app should support French localization', async ({ mainWindow, electronApp }) => {
await setSettingValue(electronApp, mainWindow, 'locale', 'fr_FR');
// The "Notebooks" header should be localized
const localizedText = mainWindow.getByText('Carnets').first();
await expect(localizedText).toBeAttached();
});
test('should be able to create and edit a new note', async ({ mainWindow }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
const editor = await mainScreen.createNewNote('Test note');

View File

@@ -180,8 +180,8 @@ test.describe('markdownEditor', () => {
await expect(matches).toHaveCount(1);
// Should continue searching after switching to view-only mode
await noteEditor.toggleEditorLayoutButton.click();
await noteEditor.toggleEditorLayoutButton.click();
await noteEditor.toggleEditorLayout();
await noteEditor.toggleEditorLayout();
await expect(noteEditor.codeMirrorEditor).not.toBeVisible();
await expect(noteEditor.editorSearchInput).not.toBeVisible();
await expect(noteEditor.viewerSearchInput).toBeVisible();
@@ -194,7 +194,7 @@ test.describe('markdownEditor', () => {
await expect(matches).toHaveCount(0);
// After showing the viewer again, search should still be hidden
await noteEditor.toggleEditorLayoutButton.click();
await noteEditor.toggleEditorLayout();
await expect(noteEditor.codeMirrorEditor).toBeVisible();
await expect(noteEditor.editorSearchInput).not.toBeVisible();
});
@@ -274,5 +274,68 @@ test.describe('markdownEditor', () => {
expect(imageSize[0]).toBeGreaterThan(0);
expect(imageSize[1]).toBeGreaterThan(0);
});
test('ctrl-clicking on note links should open the linked note (when the viewer is hidden)', async ({ mainWindow }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
await mainScreen.createNewNote('Original');
const noteEditor = mainScreen.noteEditor;
await noteEditor.hideViewer();
await noteEditor.focusCodeMirrorEditor();
await mainWindow.keyboard.type('# Test');
await mainWindow.keyboard.press('Enter');
await mainWindow.keyboard.type('## Test 2');
await mainWindow.keyboard.press('Enter');
await mainWindow.keyboard.type('### Test 3');
const editorContent = await noteEditor.contentLocator();
// Extract the note ID
const note1Locator = mainScreen.noteList.getNoteItemByTitle('Original');
await note1Locator.dragTo(editorContent);
const linkExpression = /\[[^\]]*\]\(:\/([a-z0-9]{32})\)/;
await noteEditor.expectToHaveText(linkExpression);
const targetNoteId = (await editorContent.textContent()).match(linkExpression)[1];
await mainScreen.createNewNote('Test note links');
// Create a new link to a header
await noteEditor.focusCodeMirrorEditor();
await mainWindow.keyboard.press('Enter');
await mainWindow.keyboard.press('Enter');
await mainWindow.keyboard.type('[link](:/');
await mainWindow.keyboard.type(targetNoteId);
await mainWindow.keyboard.type('#test-2');
await mainWindow.keyboard.type(')');
await mainWindow.keyboard.press('Enter');
// Clicking the link should navigate to note1
const link = editorContent.getByText(/\[?link\]?/);
await link.click({ modifiers: ['ControlOrMeta'] });
await expect(noteEditor.noteTitleInput).toHaveValue('Original');
await noteEditor.expectToHaveText(/^# Test/);
await expect.poll(() => editorContent.evaluate(async editor => {
const selection = getSelection();
return editor.contains(selection.anchorNode);
})).toBe(true);
// The cursor should be positioned on the linked-to header
await expect.poll(async () => {
await mainWindow.keyboard.type('[[cursor]]');
await noteEditor.expectToHaveText(/## Test 2\[\[cursor\]\]/);
return true;
}).toBe(true);
});
test('should still support the legacy Markdown editor', async ({ electronApp, mainWindow }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
await mainScreen.waitFor();
await setSettingValue(electronApp, mainWindow, 'editor.legacyMarkdown', true);
await mainScreen.createNewNote('Test');
// Should show the legacy editor
await expect(mainWindow.locator('.rli-editor .CodeMirror5')).toBeVisible();
});
});

View File

@@ -65,12 +65,20 @@ export default class NoteEditorPage {
}
}
public async expectToHaveText(content: string) {
public async expectToHaveText(expected: string|RegExp) {
// expect(...).toHaveText can fail in the Rich Text Editor (perhaps due to frame locators).
// Using expect.poll refreshes the locator on each attempt, which seems to prevent flakiness.
await expect.poll(
async () => (await this.contentLocator()).textContent(),
).toBe(content);
const expectResult = expect.poll(
// Use .innerText: textContent doesn't handle line breaks correctly in the CodeMirror
// editor.
async () => (await this.contentLocator()).innerText(),
);
// Allow `expected` to be either an exact match (a string) or a pattern
if (typeof expected === 'string') {
await expectResult.toBe(expected);
} else {
await expectResult.toMatch(expected);
}
}
public getNoteViewerFrameLocator() {
@@ -117,4 +125,14 @@ export default class NoteEditorPage {
await expect(backButton).not.toBeDisabled();
await backButton.click();
}
public async toggleEditorLayout() {
await this.toggleEditorLayoutButton.click();
}
public async hideViewer() {
await expect(this.noteViewerContainer).toBeVisible();
await this.toggleEditorLayout();
await expect(this.noteViewerContainer).not.toBeVisible();
}
}

View File

@@ -30,7 +30,7 @@ test.describe('pluginApi', () => {
await mainScreen.createNewNote('First note');
const editor = mainScreen.noteEditor;
await editor.expectToHaveText('');
await editor.expectToHaveText('\n');
await mainScreen.goToAnything.runCommand(app, 'showTestDialog');
// Wait for the iframe to load

View File

@@ -44,6 +44,28 @@ test.describe('sidebar', () => {
await expect(mainWindow.locator(':focus')).toHaveText('All notes');
});
test('should allow changing the focused folder by pressing the first character of the title', async ({ electronApp, mainWindow }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
const sidebar = mainScreen.sidebar;
const folderAHeader = await sidebar.createNewFolder('1-Test A');
await expect(folderAHeader).toBeVisible();
const folderBHeader = await sidebar.createNewFolder('Folder b');
await expect(folderBHeader).toBeVisible();
await folderBHeader.click();
await sidebar.forceUpdateSorting(electronApp);
await folderBHeader.click();
await mainWindow.keyboard.type('1');
await expect(mainWindow.locator(':focus')).toHaveText('1-Test A');
await mainWindow.keyboard.type('F');
await expect(mainWindow.locator(':focus')).toHaveText('Folder b');
await mainWindow.keyboard.type('A');
await expect(mainWindow.locator(':focus')).toHaveText('All notes');
});
test('left/right arrow keys should expand/collapse notebooks', async ({ electronApp, mainWindow }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
const sidebar = mainScreen.sidebar;

View File

@@ -97,12 +97,6 @@ a {
border-radius: 5px;
}
@keyframes icon-infinite-rotation{
to {
transform: rotate(360deg);
}
}
.rdtPicker {
min-width: 250px;
width: auto !important;

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.4.11",
"version": "3.5.6",
"description": "Joplin for Desktop",
"main": "main.bundle.js",
"private": true,
@@ -12,10 +12,11 @@
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"start": "gulp before-start && JOPLIN_SOURCE_MAP_ENABLED=1 electron . --env dev --log-level debug --open-dev-tools --no-welcome",
"start": "gulp before-start && electron . --env dev --log-level debug --open-dev-tools --no-welcome",
"test": "jest",
"test-ui": "gulp before-start && playwright test",
"test-ci": "yarn test",
"resolve-sourcemap": "node tools/resolveSourceMap.js",
"modifyReleaseAssets": "node tools/modifyReleaseAssets.js"
},
"repository": {
@@ -46,6 +47,7 @@
"asar": true,
"asarUnpack": "./node_modules/node-notifier/vendor/**",
"win": {
"sign": "./sign.js",
"rfc3161TimeStampServer": "http://timestamp.digicert.com",
"icon": "../../Assets/ImageSources/Joplin.ico",
"target": [
@@ -131,28 +133,27 @@
"homepage": "https://github.com/laurent22/joplin#readme",
"devDependencies": {
"7zip-bin": "5.2.0",
"@axe-core/playwright": "4.10.1",
"@axe-core/playwright": "4.10.2",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.2",
"@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4",
"@joplin/default-plugins": "~3.4",
"@joplin/editor": "~3.4",
"@joplin/lib": "~3.4",
"@joplin/renderer": "~3.4",
"@joplin/tools": "~3.4",
"@joplin/utils": "~3.4",
"@playwright/test": "1.52.0",
"@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",
"@playwright/test": "1.53.2",
"@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.103",
"@types/node": "18.19.130",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
"@types/react-redux": "7.1.33",
"@types/styled-components": "5.1.32",
"@types/tesseract.js": "2.0.0",
"async-mutex": "0.5.0",
"axios": "^1.7.7",
"codemirror": "5.65.9",
@@ -160,13 +161,13 @@
"compare-versions": "6.1.1",
"countable": "3.0.1",
"debounce": "1.2.1",
"electron": "37.4.0",
"electron": "37.7.0",
"electron-builder": "24.13.3",
"electron-updater": "6.6.2",
"electron-window-state": "5.0.3",
"esbuild": "^0.25.3",
"formatcoords": "1.1.3",
"glob": "11.0.2",
"glob": "11.0.3",
"gulp": "4.0.2",
"highlight.js": "11.11.1",
"immer": "9.0.21",
@@ -187,7 +188,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-redux": "8.1.3",
"react-select": "5.10.1",
"react-select": "5.10.2",
"react-test-renderer": "18.3.1",
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",
@@ -199,15 +200,15 @@
"styled-components": "5.3.11",
"styled-system": "5.1.5",
"taboverride": "4.0.3",
"tesseract.js": "5.1.1",
"tesseract.js": "6.0.1",
"tinymce": "6.8.5",
"ts-jest": "29.3.1",
"ts-jest": "29.4.1",
"ts-node": "10.9.2",
"typescript": "5.8.2"
"typescript": "5.8.3"
},
"dependencies": {
"@electron/remote": "2.1.2",
"@joplin/onenote-converter": "~3.4",
"@electron/remote": "2.1.3",
"@joplin/onenote-converter": "~3.5",
"fs-extra": "11.2.0",
"keytar": "7.9.0",
"node-fetch": "2.6.7",

View File

@@ -0,0 +1,109 @@
/* eslint-disable no-console */
const { execSync } = require('child_process');
const { chdir, cwd } = require('process');
const { mkdirpSync, moveSync, pathExists } = require('fs-extra');
const { readdirSync, writeFileSync } = require('fs');
const { dirname } = require('path');
const signToolName = 'CodeSignTool.bat';
const getTempDir = () => {
if (process.env.RUNNER_TEMP) return process.env.RUNNER_TEMP;
if (process.env.GITHUB_WORKSPACE) return process.env.GITHUB_WORKSPACE;
const output = `${dirname(dirname(__dirname))}/temp`;
mkdirpSync(output);
return output;
};
const tempDir = getTempDir();
const downloadSignTool = async () => {
const signToolUrl = 'https://www.ssl.com/download/codesigntool-for-windows/';
const downloadDir = `${tempDir}/signToolDownloadTemp`;
const extractDir = `${tempDir}/signToolExtractTemp`;
if (await pathExists(`${extractDir}/${signToolName}`)) {
console.info('sign.js: Sign tool has already been downloaded - skipping');
return extractDir;
}
mkdirpSync(downloadDir);
mkdirpSync(extractDir);
const response = await fetch(signToolUrl);
if (!response.ok) throw new Error(`sign.js: HTTP error ${response.status}: ${response.statusText}`);
const zipPath = `${downloadDir}/codeSignTool.zip`;
const buffer = Buffer.from(await response.arrayBuffer());
writeFileSync(zipPath, buffer);
console.info('sign.js: Downloaded sign tool zip:', readdirSync(downloadDir));
mkdirpSync(extractDir);
execSync(
`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${extractDir}' -Force"`,
{ stdio: 'inherit' },
);
console.info('sign.js: Extracted sign tool zip:', readdirSync(extractDir));
return extractDir;
};
exports.default = async (configuration) => {
const inputFilePath = configuration.path;
const {
SSL_ESIGNER_USER_NAME,
SSL_ESIGNER_USER_PASSWORD,
SSL_ESIGNER_CREDENTIAL_ID,
SSL_ESIGNER_USER_TOTP,
SIGN_APPLICATION,
} = process.env;
console.info('sign.js: File to sign:', inputFilePath);
console.info('sign.js: Using temp dir:', tempDir);
if (SIGN_APPLICATION !== '1') {
console.info('sign.js: SIGN_APPLICATION != 1 - not signing application');
return;
}
console.info('sign.js: SIGN_APPLICATION = 1 - signing application');
const signToolDir = await downloadSignTool();
const signToolOutDir = `${tempDir}/signedToolOutDir`;
mkdirpSync(signToolOutDir);
const previousDir = cwd();
chdir(signToolDir);
try {
const cmd = [
`${signToolName} sign`,
`-input_file_path="${inputFilePath}"`,
`-output_dir_path="${signToolOutDir}"`,
`-credential_id="${SSL_ESIGNER_CREDENTIAL_ID}"`,
`-username="${SSL_ESIGNER_USER_NAME}"`,
`-password="${SSL_ESIGNER_USER_PASSWORD}"`,
`-totp_secret="${SSL_ESIGNER_USER_TOTP}"`,
];
execSync(cmd.join(' '));
const createdFiles = readdirSync(signToolOutDir);
console.info('sign.js: Created files:', createdFiles);
moveSync(`${signToolOutDir}/${createdFiles[0]}`, inputFilePath, { overwrite: true });
} catch (error) {
console.error('sign.js: Could not sign file:', error);
process.exit(1);
} finally {
chdir(previousDir);
}
};

View File

@@ -9,7 +9,7 @@ const baseNodeModules = join(baseDir, 'node_modules');
// Note: Roughly based on js-draw's use of esbuild:
// https://github.com/personalizedrefrigerator/js-draw/blob/6fe6d6821402a08a8d17f15a8f48d95e5d7b084f/packages/build-tool/src/BundledFile.ts#L64
const makeBuildContext = (entryPoint: string, renderer: boolean, computeFileSizeStats: boolean) => {
const makeBuildContext = (entryPoint: string, renderer: boolean, addDebugStats: boolean) => {
return esbuild.context({
entryPoints: [entryPoint],
outfile: `${filename(entryPoint)}.bundle.js`,
@@ -19,7 +19,7 @@ const makeBuildContext = (entryPoint: string, renderer: boolean, computeFileSize
format: 'iife', // Immediately invoked function expression
sourcemap: true,
sourcesContent: false, // Do not embed full source file content in the .map file
metafile: computeFileSizeStats,
metafile: addDebugStats,
platform: 'node',
target: ['node20.0'],
mainFields: renderer ? ['browser', 'main'] : ['main'],
@@ -92,26 +92,29 @@ const makeBuildContext = (entryPoint: string, renderer: boolean, computeFileSize
{
name: 'joplin--smaller-source-map-size',
setup: build => {
// Exclude dependencies from node_modules. This significantly reduces the size of the
// Unless bundling with additional debug information, exclude 3rd-party
// dependencies from source maps. This significantly reduces the size of the
// source map, improving startup performance.
//
// See https://github.com/evanw/esbuild/issues/1685#issuecomment-944916409
// and https://github.com/evanw/esbuild/issues/4130
const emptyMapData = Buffer.from(
JSON.stringify({ version: 3, sources: [null], mappings: 'AAAA' }),
'utf-8',
).toString('base64');
const emptyMapUrl = `data:application/json;base64,${emptyMapData}`;
if (!addDebugStats) {
const emptyMapData = Buffer.from(
JSON.stringify({ version: 3, sources: [null], mappings: 'AAAA' }),
'utf-8',
).toString('base64');
const emptyMapUrl = `data:application/json;base64,${emptyMapData}`;
build.onLoad({ filter: /node_modules.*js$/ }, args => {
return {
contents: [
readFileSync(args.path, 'utf8'),
`//# sourceMappingURL=${emptyMapUrl}`,
].join('\n'),
loader: 'default',
};
});
build.onLoad({ filter: /node_modules.*js$/ }, args => {
return {
contents: [
readFileSync(args.path, 'utf8'),
`//# sourceMappingURL=${emptyMapUrl}`,
].join('\n'),
loader: 'default',
};
});
}
},
},
],

View File

@@ -0,0 +1,55 @@
import { dirname, relative } from 'path';
import * as yargs from 'yargs';
const { wrapCallSite } = require('source-map-support');
/* eslint-disable no-console */
const resolveLine = (lineNumber: number, columnNumber: number, filePath: string) => {
// Note: This is an undocumented function provided by source-map-support. It
// may change in the future:
const frame = wrapCallSite({
getFileName: () => filePath,
isEval: ()=>false,
isNative: ()=>false,
getLineNumber: ()=>lineNumber,
getColumnNumber: ()=>columnNumber,
});
const baseDir = dirname(dirname(dirname(__dirname)));
const relativeFilePath = relative(baseDir, frame.getFileName());
return `${relativeFilePath}:${frame.getLineNumber()}`;
};
const resolvePosition = (position: string, sourceMap: string) => {
const match = /^(\d{1,10}):(\d{1,10})$/.exec(position.trim());
if (!match) {
throw new Error('Invalid format. Expected line:col');
}
const lineNumber = Number(match[1]);
const columnNumber = Number(match[2]);
return resolveLine(lineNumber, columnNumber, sourceMap);
};
void yargs
.usage('$0 [args]')
.command(
'$0 <position>',
'Resolves a position based on a source map. If resolving a position in a specific error message, be sure to use the source map generated by "yarn bundle" from that specific commit.',
(yargs) => {
return yargs.options({
'position': { type: 'string', help: 'A line:col position (e.g. 123:4567)' },
'sourcemap': {
type: 'string',
default: './main-html.bundle.js',
help: 'The path to the source map. This source map should be a source map compiled from the commit/release that created the error.',
},
});
},
async (args) => {
console.log(await resolvePosition(args.position, args.sourcemap));
process.exit(0);
},
)
.help()
.argv;

View File

@@ -1,6 +1,8 @@
// source-map-support can add 1-3 seconds to the application startup
// time -- disable it unless requested:
if (process.env.JOPLIN_SOURCE_MAP_ENABLED) {
// time. In the future, it may make sense to either:
// - Use Sentry for resolving source maps: https://docs.sentry.io/platforms/javascript/guides/electron/sourcemaps/
// - Use NodeJS source map support (if https://github.com/electron/electron/issues/38875 is resolved)
if (!process.env.JOPLIN_SOURCE_MAP_DISABLED) {
require('source-map-support').install();
}

View File

@@ -90,7 +90,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097780
versionName "3.4.7"
versionName "3.5.0"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -24,29 +24,8 @@ buildscript {
allprojects {
repositories {
mavenCentral()
// Seems to be required for react-native-vosk, otherwise the lib looks for it at "https://maven.aliyun.com/repository/jcenter/com/alphacephei/vosk-android/0.3.46/vosk-android-0.3.46.aar" but it's not there. And we get this error:
//
// Execution failed for task ':app:checkDebugAarMetadata'.
// > Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
// > Failed to transform vosk-android-0.3.46.aar (com.alphacephei:vosk-android:0.3.46) to match attributes {artifactType=android-aar-metadata, org.gradle.status=release}.
// > Could not find vosk-android-0.3.46.aar (com.alphacephei:vosk-android:0.3.46).
// Searched in the following locations:
// https://maven.aliyun.com/repository/jcenter/com/alphacephei/vosk-android/0.3.46/vosk-android-0.3.46.aar
//
// But according to this page, the lib is on the Apache repository:
//
// https://search.maven.org/artifact/com.alphacephei/vosk-android/0.3.46/aar
maven { url "https://maven.apache.org" }
// Also required for react-native-vosk?
maven { url "https://maven.google.com" }
// Maybe still needed to fetch above package?
google()
maven { url 'https://www.jitpack.io' }
mavenCentral()
maven {
// expo-camera bundles a custom com.google.android:cameraview

View File

@@ -4,7 +4,7 @@ import { Platform, ScrollView, StyleSheet, View } from 'react-native';
import { BarcodeScanner } from './utils/useBarcodeScanner';
import { LinkButton, PrimaryButton } from '../buttons';
import { _ } from '@joplin/lib/locale';
import DismissibleDialog, { DialogSize } from '../DismissibleDialog';
import DismissibleDialog, { DialogVariant } from '../DismissibleDialog';
import { Chip, Text } from 'react-native-paper';
import { isCallbackUrl, parseCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import CommandService from '@joplin/lib/services/CommandService';
@@ -84,7 +84,7 @@ const ScannedBarcodes: React.FC<Props> = props => {
visible={dialogVisible}
onDismiss={onHideDialog}
themeId={props.themeId}
size={DialogSize.Small}
size={DialogVariant.Small}
>
<ScrollView>
<Text variant='titleMedium' role='heading'>{_('Scanned code')}</Text>

View File

@@ -66,12 +66,12 @@ describe('ComboBox', () => {
unmount();
});
test('changing the search query should limit which items are visible', () => {
test('changing the search query should limit which items are visible and be case insensitive', () => {
const testItems = [
{ title: 'a' },
{ title: 'b' },
{ title: 'c' },
{ title: 'aa' },
{ title: 'Aa' },
];
const { unmount } = render(
<WrappedComboBox items={testItems}/>,
@@ -82,7 +82,7 @@ describe('ComboBox', () => {
const updatedResults = getSearchResults();
expect(updatedResults[0]).toHaveTextContent('a');
expect(updatedResults[1]).toHaveTextContent('aa');
expect(updatedResults[1]).toHaveTextContent('Aa');
expect(updatedResults).toHaveLength(2);
unmount();

View File

@@ -12,7 +12,7 @@ import focusView from '../utils/focusView';
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
import NestableFlatList, { NestableFlatListControl } from './NestableFlatList';
import useKeyboardState from '../utils/hooks/useKeyboardState';
const naturalCompare = require('string-natural-compare');
import { getCollator, getCollatorLocale } from '@joplin/lib/models/utils/getCollator';
export interface Option {
@@ -64,17 +64,20 @@ interface UseSearchResultsOptions {
const useSearchResults = ({
search, setSearch, options, onAddItem, canAddItem,
}: UseSearchResultsOptions) => {
const collatorLocale = getCollatorLocale();
const results = useMemo(() => {
const collator = getCollator(collatorLocale);
const lowerSearch = search?.toLowerCase();
return options
.filter(option => option.title.toLowerCase().includes(search))
.filter(option => option.title.toLowerCase().includes(lowerSearch))
.sort((a, b) => {
if (a.title === b.title) return 0;
// Full matches should go first
if (a.title === search) return -1;
if (b.title === search) return 1;
return naturalCompare(a.title, b.title);
if (a.title.toLowerCase() === lowerSearch) return -1;
if (b.title.toLowerCase() === lowerSearch) return 1;
return collator.compare(a.title, b.title);
});
}, [search, options]);
}, [search, options, collatorLocale]);
const canAdd = (
!!onAddItem

View File

@@ -6,7 +6,10 @@ import { themeStyle } from './global-style';
import Modal from './Modal';
import { _ } from '@joplin/lib/locale';
export enum DialogSize {
export enum DialogVariant {
// Small width, auto-determined height
SmallResize = 'small-resize',
Small = 'small',
// Ideal for panels and dialogs that should be fullscreen even on large devices
@@ -20,34 +23,58 @@ interface Props {
containerStyle?: ViewStyle;
children: React.ReactNode;
heading?: string;
scrollOverflow?: boolean;
size: DialogSize;
size: DialogVariant;
}
const useStyles = (themeId: number, containerStyle: ViewStyle, size: DialogSize) => {
const useStyles = (themeId: number, containerStyle: ViewStyle, size: DialogVariant) => {
const windowSize = useWindowDimensions();
return useMemo(() => {
const theme = themeStyle(themeId);
const maxWidth = size === DialogSize.Large ? windowSize.width : 500;
const maxHeight = size === DialogSize.Large ? windowSize.height : 700;
const maxWidth = size === DialogVariant.Large ? windowSize.width : 500;
const maxHeight = size === DialogVariant.Large ? windowSize.height : 700;
const dialogSizing: ViewStyle = {
width: '100%',
...(size !== DialogVariant.SmallResize ? {
height: '100%',
} : { }),
};
return StyleSheet.create({
closeButtonContainer: {
closeButtonRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignContent: 'center',
marginBottom: 8,
},
closeButtonRowWithHeading: {
marginBottom: 16,
},
closeButton: {
margin: 0,
},
// Ensure that the close button is aligned with the center of the header:
// Make its container smaller and center it.
closeButtonWrapper: {
height: 18,
flexDirection: 'column',
justifyContent: 'center',
alignSelf: 'center',
},
heading: {
alignSelf: 'center',
},
modalBackground: {
justifyContent: 'center',
},
dialogContainer: {
maxHeight,
maxWidth,
width: '100%',
height: '100%',
flexShrink: 1,
...dialogSizing,
// Center
marginLeft: 'auto',
@@ -58,11 +85,11 @@ const useStyles = (themeId: number, containerStyle: ViewStyle, size: DialogSize)
...containerStyle,
},
dialogSurface: {
borderRadius: 12,
borderRadius: 24,
backgroundColor: theme.backgroundColor,
padding: 10,
width: '100%',
height: '100%',
paddingHorizontal: 16,
paddingVertical: 24,
...dialogSizing,
},
});
}, [themeId, windowSize.width, windowSize.height, containerStyle, size]);
@@ -76,13 +103,16 @@ const DismissibleDialog: React.FC<Props> = props => {
<Text variant='headlineSmall' role='heading' style={styles.heading}>{props.heading}</Text>
) : null;
const closeButtonRow = (
<View style={styles.closeButtonContainer}>
<View style={[styles.closeButtonRow, !!props.heading && styles.closeButtonRowWithHeading]}>
{heading ?? <View/>}
<IconButton
icon='close'
accessibilityLabel={_('Close')}
onPress={props.onDismiss}
/>
<View style={styles.closeButtonWrapper}>
<IconButton
icon='close'
accessibilityLabel={_('Close')}
onPress={props.onDismiss}
style={styles.closeButton}
/>
</View>
</View>
);
@@ -92,9 +122,13 @@ const DismissibleDialog: React.FC<Props> = props => {
onDismiss={props.onDismiss}
onRequestClose={props.onDismiss}
containerStyle={styles.dialogContainer}
modalBackgroundStyle={styles.modalBackground}
animationType='fade'
backgroundColor={theme.backgroundColorTransparent2}
transparent={true}
scrollOverflow={props.scrollOverflow}
// Allows the modal background to extend under the statusbar
statusBarTranslucent
>
<Surface style={styles.dialogSurface} elevation={1}>
{closeButtonRow}

View File

@@ -132,6 +132,7 @@ const EditorToolbar: React.FC<Props> = props => {
style={styles.content}
contentContainerStyle={styles.contentContainer}
onLayout={onContainerLayout}
keyboardShouldPersistTaps="always"
>
{buttonInfos.map(renderButton)}
<View style={styles.spacer}/>

View File

@@ -12,7 +12,7 @@ import { AppState } from '../../utils/types';
import CommandService from '@joplin/lib/services/CommandService';
import allToolbarCommandNamesFromState from './utils/allToolbarCommandNamesFromState';
import Setting from '@joplin/lib/models/Setting';
import DismissibleDialog, { DialogSize } from '../DismissibleDialog';
import DismissibleDialog, { DialogVariant } from '../DismissibleDialog';
import selectedCommandNamesFromState from './utils/selectedCommandNamesFromState';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import { DeleteButton } from '../buttons';
@@ -158,7 +158,7 @@ const ToolbarEditorScreen: React.FC<EditorDialogProps> = props => {
return (
<DismissibleDialog
size={DialogSize.Small}
size={DialogVariant.Small}
themeId={props.themeId}
visible={props.visible}
onDismiss={props.onDismiss}

View File

@@ -7,6 +7,7 @@ import createMockReduxStore from '../utils/testing/createMockReduxStore';
import setupGlobalStore from '../utils/testing/setupGlobalStore';
import { act, fireEvent, render, screen } from '@testing-library/react-native';
import FeedbackBanner from './FeedbackBanner';
import { MobilePlatform } from '@joplin/lib/shim';
interface WrapperProps { }
@@ -84,7 +85,7 @@ describe('FeedbackBanner', () => {
setupGlobalStore(store);
jest.useFakeTimers({ advanceTimers: true });
mockMobilePlatform('web');
mockMobilePlatform(MobilePlatform.Web);
});
afterEach(() => {
@@ -93,9 +94,9 @@ describe('FeedbackBanner', () => {
});
test.each([
{ platform: 'android', shouldShow: false },
{ platform: 'web', shouldShow: true },
{ platform: 'ios', shouldShow: false },
{ platform: MobilePlatform.Android, shouldShow: false },
{ platform: MobilePlatform.Web, shouldShow: true },
{ platform: MobilePlatform.Ios, shouldShow: false },
])('should correctly show/hide the feedback banner on %s', ({ platform, shouldShow }) => {
mockMobilePlatform(platform);

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