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

Compare commits

...

309 Commits

Author SHA1 Message Date
Laurent Cozic
f53bb2d167 Merge branch 'dev' into sharing_bug_2 2025-07-11 17:28:09 +01:00
Laurent Cozic
4231f8cced Chore: asset files 2025-07-11 17:26:01 +01:00
Laurent Cozic
3f9c60dd10 Doc: Fix JSB link 2025-07-11 17:25:42 +01:00
mrjo118
35e189ef6e Desktop, Mobile: Resolves #12594: Move the conflicts folder to the top of the notebook list to improve visibility (#12688) 2025-07-11 10:59:26 +01:00
renovate[bot]
a15bad37b1 Update dependency lint-staged to v15.5.0 (#12693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 10:56:47 +01:00
Laurent Cozic
8b4ad0aaf7 Doc: Add Joplin Server Business to plans and add page about it (#12694) 2025-07-10 20:48:56 +01:00
Henry Heino
c3575672b2 Web: Image editor: Fix scrollbars sometimes incorrectly visible (#12692) 2025-07-10 12:35:42 +01:00
Joplin Bot
e840d0c3fd Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-07-10 01:10:52 +00:00
Laurent Cozic
5227ba1adb Merge branch 'release-3.3' into dev 2025-07-10 00:47:30 +01:00
Laurent Cozic
ea49907327 iOS 13.3.9 2025-07-10 00:17:48 +01:00
Laurent Cozic
0dd90a7542 Android 3.3.11 2025-07-09 23:59:54 +01:00
Henry Heino
a962f48b38 Mobile: Biometrics: Fix notebook list can still be accessed when the app is locked (#12691) 2025-07-09 23:47:06 +01:00
renovate[bot]
f68d2bbc7c Update dependency pg to v8.14.1 (#12690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 21:15:47 +01:00
renovate[bot]
65c9665a2a Update dependency pg to v8.14.0 (#12689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 19:14:50 +01:00
Henry Heino
2c50ad36c5 Desktop: Fix secondary window controls greyed out when first opened (#12685) 2025-07-09 08:43:06 +01:00
Henry Heino
7212269107 Clipper: Fixes #12683: Fix web clipper fails to clip pages that include comments in inline styles (#12686) 2025-07-09 08:42:23 +01:00
renovate[bot]
1387470f2a Update dependency katex to v0.16.22 (#12687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 05:59:16 +00:00
Henry Heino
a6d5eb9b8e Chore: Resolves #12283: Server: Add fuzzer for detecting sync bugs (#12592)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-07-07 16:07:27 +01:00
pedr
5d1a055d2a Chore: Fixes #12673: Removing warning of obsolete snapshots from OneNote importer tests (#12675) 2025-07-07 16:06:52 +01:00
Henry Heino
36910a2a9b Chore: Desktop: Decrease source map size (#12679) 2025-07-07 16:06:33 +01:00
Joplin Bot
b4a57a10aa Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-07-07 12:33:47 +00:00
Laurent Cozic
bca8cb1c2d Doc: Update sponsors 2025-07-07 12:33:05 +01:00
Joplin Bot
0b489a9c98 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-07-06 16:59:09 +00:00
renovate[bot]
ce32651794 Update dependency koa to v2.16.1 (#12677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-06 04:55:44 +00:00
Laurent Cozic
f0159cdd89 Doc: Update sponsors 2025-07-05 11:57:37 +01:00
Laurent Cozic
97652fa362 Tools: Setup runForTesting script to also create shares and send to recipient 2025-07-05 11:57:37 +01:00
Laurent Cozic
113b259b81 update 2025-07-05 11:52:27 +01:00
Henry Heino
2af895477f Cli: Refresh shares when running the sync command (#12667) 2025-07-04 18:52:54 +01:00
Laurent Cozic
c7e31d1ac9 update 2025-07-04 16:37:17 +01:00
Laurent Cozic
c51b13ca73 update 2025-07-04 15:37:35 +01:00
Laurent Cozic
f5febb18b4 Tools: Setup runForTesting script to also create shares and send to recipient 2025-07-04 15:35:32 +01:00
renovate[bot]
9d6aa1c739 Update dependency @rollup/plugin-node-resolve to v16.0.1 (#12672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 03:03:47 +00:00
renovate[bot]
3b27f84996 Update dependency @rollup/plugin-node-resolve to v16 (#12668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 00:09:25 +01:00
Henry Heino
fc38691f3a Desktop: Fixes #12451: Fix incorrect line numbers/files in debug output (#12664) 2025-07-03 17:48:11 +01:00
renovate[bot]
d2274319f9 Update dependency uuid to v11.1.0 (#12665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 14:56:17 +01:00
renovate[bot]
a40448fed9 Update dependency uuid to v11 (#12659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 01:31:32 +01:00
Henry Heino
5ec79c74e2 Desktop: Disable console wrapper (#12663) 2025-07-02 20:48:46 +01:00
Laurent Cozic
bbba19eb40 All: Fixes #12089: Moving sub-notebook of shared notebook should unshare it (#12647) 2025-07-02 18:14:47 +01:00
Henry Heino
75b89c7e09 Desktop,Cli: Fix data API failure when including both conflicts and deleted notes in results (#12650) 2025-07-02 17:22:02 +01:00
Laurent Cozic
f9af9a724c Server: Ensure shares are writable (#12651) 2025-07-02 16:14:52 +01:00
renovate[bot]
6e7c9c059d Update dependency react-native-share to v12.0.9 (#12656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 15:04:24 +01:00
renovate[bot]
69ee435b0b Update dependency react-native-zip-archive to v7 (#12657)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 14:19:07 +01:00
renovate[bot]
204f1bf509 Update dependency react-native-share to v12 (#12655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 09:31:17 +01:00
renovate[bot]
7a7a2c4cec Update dependency react-native-device-info to v14.0.4 (#12654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 09:31:10 +01:00
renovate[bot]
441486acaa Update dependency react-native-modal-datetime-picker to v18 (#12652)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 09:31:01 +01:00
renovate[bot]
4684142df7 Update dependency raw-body to v3 (#12646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 00:41:59 +01:00
renovate[bot]
0a871ea44b Update dependency react-native-device-info to v14 (#12649)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 00:41:46 +01:00
Henry Heino
901fe73c08 Cli: Support managing shared notebooks (#12637) 2025-07-01 22:47:03 +01:00
renovate[bot]
41553eb963 Update dependency nodejs to v23.8.0 (#12641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 22:43:59 +01:00
Laurent Cozic
cada200575 CI: Fixed random Joplin Server test failure on CI 2025-07-01 21:42:21 +01:00
renovate[bot]
13711c6a9c Update dependency python to v3.13.1 (#12645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 21:25:11 +01:00
renovate[bot]
1a6acee5c8 Update dependency @types/react-dom to v18.3.6 (#12640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 18:21:58 +01:00
renovate[bot]
0c2547a780 Update dependency python (#12644)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 18:21:47 +01:00
renovate[bot]
e0204d672b Update dependency npm-package-json-lint to v8 (#12642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 16:15:00 +01:00
renovate[bot]
9c9b06de2d Update dependency @types/react to v19.0.14 (#12636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 12:23:54 +01:00
renovate[bot]
58f3344564 Update dependency nodejs to v23 (#12638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 12:23:41 +01:00
renovate[bot]
f6fef5a8ec Update dependency madge to v8 (#12629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 08:40:43 +01:00
renovate[bot]
e0211045db Update types (#12634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 08:39:37 +01:00
renovate[bot]
f757221d44 Update dependency glob to v11.0.1 (#12631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 03:31:06 +00:00
Henry Heino
552ecc9064 Android, iOS: Fix camera screen (#12624) 2025-06-30 21:11:59 +01:00
renovate[bot]
7d4864193f Update dependency jsdom to v25 (#12628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 21:11:41 +01:00
renovate[bot]
81e2205a53 Update dependency glob to v11 (#12627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 21:11:27 +01:00
renovate[bot]
4e89890a23 Update dependency @types/node to v18.19.86 (#12625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 21:10:44 +01:00
renovate[bot]
60de33b8be Update dependency @rollup/plugin-replace to v6.0.2 (#12616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 17:44:30 +01:00
renovate[bot]
84d6f5dfcb Update dependency @types/uuid to v10 (#12617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 17:44:22 +01:00
mrjo118
d0d80c0e4a Mobile: Add missing title to the revision viewer for mobile (#12611) 2025-06-30 14:53:19 +01:00
Laurent Cozic
798c1e1c2b Update renovate.json5 2025-06-30 14:01:07 +01:00
renovate[bot]
1eef44d243 Update dependency @react-native-community/cli-platform-ios to v16 (#12608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:54:40 +01:00
renovate[bot]
e5adaa7f74 Update dependency @rollup/plugin-replace to v6 (#12612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:54:09 +01:00
renovate[bot]
671997af96 Update dependency @types/node to v18.19.85 (#12614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:53:57 +01:00
renovate[bot]
2bf968f9ad Update dependency @crowdin/cli to v4 (#12601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 11:55:25 +01:00
renovate[bot]
3e06dd989f Update dependency @rollup/plugin-commonjs to v28.0.3 (#12613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 11:50:32 +01:00
renovate[bot]
3459355285 Update dependency @rollup/plugin-commonjs to v28 (#12610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 09:01:02 +01:00
renovate[bot]
7406a89dc0 Update dependency @react-native-community/cli-platform-android to v16.0.2 (#12607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 01:41:17 +00:00
renovate[bot]
ace662cc79 Update dependency @react-native-community/cli to v16.0.2 (#12605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 22:33:57 +00:00
renovate[bot]
0c5d5e59f3 Update bitnami/postgresql Docker tag to v17.3.0 (#12604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 20:09:06 +01:00
renovate[bot]
b00aadb542 Update dependency @react-native-community/cli-platform-android to v16 (#12606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 20:08:30 +01:00
renovate[bot]
d6883e6ec1 Update dependency re-resizable to v6.11.2 (#12591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 18:08:25 +01:00
renovate[bot]
6ac64ca0d9 Update dependency @react-native-community/cli to v16 (#12602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 17:34:03 +01:00
renovate[bot]
9890d267a1 Update bitnami/postgresql Docker tag to v17 (#12600)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 16:40:21 +01:00
Laurent Cozic
1a1335a7d5 Update renovate.json5 2025-06-29 14:56:37 +01:00
renovate[bot]
67288f0b44 Update dependency @pmmmwh/react-refresh-webpack-plugin to v0.5.16 (#12595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 13:16:48 +01:00
renovate[bot]
a0cd09cd5b Update dependency node to v18.20.8 (#12596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 13:16:33 +01:00
renovate[bot]
6e5623ce6a Update dependency node to v18.20.7 (#12581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 10:30:47 +01:00
renovate[bot]
032f26b1c5 Update dependency koa to v2.16.0 (#12590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 10:29:48 +01:00
renovate[bot]
d0030a904c Update Rust crate bytes to v1.10.1 (#12589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-28 22:03:24 +00:00
Jozef Gaal
a23d5d10b6 All: Translation: Update sk_SK.po (#12588) 2025-06-28 18:00:31 -04:00
mrjo118
f9ccd15615 Mobile: Add delete line, duplicate line and sort selected lines buttons to editor toolbar (#12555) 2025-06-28 21:01:05 +01:00
pedr
1f9f63d176 CI: Fixes #12440: Disable logging from onenote-converter library by checking if it a test run (#12566) 2025-06-28 20:24:30 +01:00
mrjo118
813f077312 Desktop: Fixes #12419: Ensure min and max validation is enforced when setting is not yet present (#12553) 2025-06-28 20:19:07 +01:00
Henry Heino
6a5c85d3d7 Android: Voice typing: Add setting to allow specifying a glossary (#12370)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-28 20:06:12 +01:00
Henry Heino
1644f56447 Android: Fixes #12484: Fix cursor jumps to the beginning of inputs on tap (#12499) 2025-06-28 20:05:29 +01:00
renovate[bot]
85518edca1 Update Rust crate bytes to v1.10.0 (#12561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-28 20:03:29 +01:00
renovate[bot]
ebc070b3c7 Update dependency sass to v1.85.1 (#12540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-28 20:02:40 +01:00
Henry Heino
a33fb575fd Chore: Mobile: Add internal support for taking multiple pictures from a camera component (#12357) 2025-06-28 20:01:13 +01:00
Henry Heino
ecc781ee39 Chore: VSCode workspace: Default to tab indentation (#12587) 2025-06-28 20:00:39 +01:00
renovate[bot]
098cabad40 Update Rust crate log to v0.4.27 (#12580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 11:35:34 +00:00
renovate[bot]
4d01738029 Update dependency @types/serviceworker to v0.0.127 (#12577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 07:47:04 +00:00
renovate[bot]
3433293a0e Update dependency webpack-dev-server to v5.2.1 (#12576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 08:31:53 +00:00
renovate[bot]
02fd244096 Update dependency ldapts to v7.3.3 (#12575)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 02:42:52 +00:00
renovate[bot]
00cd26fd82 Update dependency @types/react to v18.3.20 (#12574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 23:43:29 +00:00
renovate[bot]
38ca224a16 Update dependency webpack-dev-server to v5.2.0 (#12543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 09:51:43 -07:00
summoner001
0fec577932 All: Translation: Update hu_HU.po (#12562) 2025-06-18 17:07:13 -04:00
renovate[bot]
780d049502 Update react monorepo (#12557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 02:04:30 +00:00
renovate[bot]
a5d74e1ee7 Update dependency nanoid to v3.3.11 (#12556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 02:01:49 +00:00
renovate[bot]
d6b369b4f4 Update dependency react-select to v5.10.1 (#12542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 23:34:11 +00:00
Nick
572e40c635 All: Translation: Update sv.po (#12546) 2025-06-16 14:41:21 -04:00
Henry Heino
4af5c609fd Chore: CI: Disable UI tests on MacOS (#12547) 2025-06-16 10:40:45 -07:00
renovate[bot]
8487fc1a34 Update dependency @types/react to v19.0.11 (#12539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 12:02:11 +01:00
Laurent Cozic
a76fad3ddf Chore: Disable cache on CI (#12509) 2025-06-16 10:37:29 +01:00
renovate[bot]
a08af91153 Update dependency react-native-vector-icons to v10.2.0 (#12536)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-16 10:37:17 +01:00
renovate[bot]
3bcf221e52 Update dependency react-select to v5.10.0 (#12538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 10:20:05 +01:00
renovate[bot]
0dd211c2fd Update dependency react-native-localize to v3.4.1 (#12535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-16 08:36:24 +01:00
renovate[bot]
b6fea2a4e2 Update dependency re-resizable to v6.10.4 (#12534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-16 08:36:16 +01:00
Gustavo V. F.
73eb6cca38 All: Translation: Update pt_BR.po (#12537) 2025-06-15 17:18:04 -04:00
Laurent Cozic
449f49379d Chore: Trying to fix RSA error on CI (#12526) 2025-06-15 22:08:27 +01:00
renovate[bot]
c4b951544b Update dependency node-mocks-http to v1.16.2 (#12523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-15 19:23:40 +01:00
renovate[bot]
5746d4cdf6 Update dependency re-resizable to v6.10.3 (#12532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 19:22:49 +01:00
renovate[bot]
71e4f35e79 Update dependency react-native-image-picker to v7.2.3 (#12533)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 19:22:37 +01:00
renovate[bot]
5169371b68 Update dependency pg to v8.13.3 (#12530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 16:18:39 +01:00
renovate[bot]
24845bd7d8 Update dependency nodemailer to v6.10.0 (#12529)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 14:26:43 +01:00
renovate[bot]
00b7726cda Update dependency ldapts to v7.3.1 (#12517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 10:17:12 +01:00
Laurent Cozic
ce9008998c Chore: Add auto-generated files 2025-06-15 09:39:52 +01:00
renovate[bot]
776813acfe Update dependency nan to v2.22.2 (#12522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 09:33:52 +01:00
renovate[bot]
d13c213657 Update dependency nan to v2.22.0 (#12520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 07:34:15 +01:00
renovate[bot]
1895b3d067 Update dependency lint-staged to v15.4.3 (#12519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 07:34:06 +01:00
renovate[bot]
863d00c595 Update dependency license-checker-rseidelsohn to v4.4.2 (#12518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 07:33:42 +01:00
renovate[bot]
1b8b1f7b2a Update dependency git to v2.47.2 (#12516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 02:13:18 +00:00
renovate[bot]
ca8b68bd95 Update dependency domutils to v3.2.2 (#12505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-15 00:30:19 +01:00
renovate[bot]
3c3e7c4854 Update dependency git to v2.47.1 (#12514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 00:29:46 +01:00
renovate[bot]
aeab8e03ab Update dependency highlight.js to v11.11.1 (#12515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 00:29:37 +01:00
renovate[bot]
a3cc34938b Update dependency nanoid to v3.3.10 (#12511)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-14 14:08:48 +00:00
renovate[bot]
ec057e4e2e Update dependency @types/serviceworker to v0.0.126 (#12510)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-14 14:06:01 +00:00
renovate[bot]
78eca9dfa9 Update dependency @react-native-community/geolocation to v3.4.0 (#12498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-14 10:37:01 +01:00
renovate[bot]
174cc76ef4 Update dependency @rollup/plugin-node-resolve to v15.3.1 (#12503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-14 10:36:50 +01:00
Laurent Cozic
ba5bb6e8ea Chore: Exclude "bat" from Renovate 2025-06-14 10:36:27 +01:00
Laurent Cozic
0a8a19748e Chore: Disable GitHub Action TCP/UDP offload on Windows to try to fix CI issue "getaddrinfo ENOTFOUND nodejs.org" 2025-06-13 22:46:16 +01:00
renovate[bot]
7884ce61a0 Update dependency @react-native-clipboard/clipboard to v1.16.2 (#12497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 21:21:26 +00:00
Henry Heino
d7f2ffaa1e Chore: CI: Windows: Skip installing Rust (#12502) 2025-06-12 22:19:10 +01:00
Laurent Cozic
a79409dfa8 Chore: Fixes #12500: Trying to fix random "concurrent map writes" error on CI by disabling parallelism 2025-06-12 21:22:22 +01:00
renovate[bot]
4777bd393c Update dependency @react-native-community/cli-platform-android to v15.1.3 (#12495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 16:37:40 +01:00
renovate[bot]
c7575e4726 Update dependency @react-native-clipboard/clipboard to v1.16.1 (#12493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 16:36:51 +01:00
renovate[bot]
efc5059d65 Update dependency @crowdin/cli to v3.19.4 (#12491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 12:00:53 +01:00
renovate[bot]
bd203ffe88 Update dependency @babel/plugin-transform-export-namespace-from to v7.25.9 (#12490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 11:41:57 +01:00
renovate[bot]
27717b46ac Update dependency @types/serviceworker to v0.0.125 (#12488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 11:24:28 +01:00
Henry Heino
364b2496d6 Chore: CI: Retry opening the go-to-anything dialog on failure (#12481) 2025-06-12 07:50:10 +01:00
renovate[bot]
b701992524 Update olegtarasov/get-tag action to v2.1.4 (#12471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-12 07:49:58 +01:00
Henry Heino
d00bd3b89d Chore: Editor: Retry list tests on failure (#12479) 2025-06-12 07:49:48 +01:00
renovate[bot]
c80e789a8d Update rust-wasm-bindgen monorepo (#12485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-12 07:49:37 +01:00
renovate[bot]
503b31e67a Update bitnami/postgresql Docker tag to v16.6.0 (#12486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 07:48:58 +01:00
renovate[bot]
1b5e538d6a Update Rust crate log to v0.4.26 (#12483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-12 07:48:44 +01:00
Henry Heino
c8cbe7271e Chore: CI: Fix plugin-related test failure (#12482) 2025-06-11 23:37:34 +01:00
renovate[bot]
7330efceaf Update dependency @types/proper-lockfile to v4.1.4 (#12474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-11 22:42:29 +01:00
renovate[bot]
da2229706c Update dependency koa to v2.15.4 (#12478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-11 22:42:20 +01:00
Henry Heino
b81f5cb91e Chore: Mobile: Increase test waitFor timeouts (#12475) 2025-06-11 22:42:06 +01:00
Henry Heino
3b7a677302 Chore: Mobile: Fix "missing act()" warning (#12476) 2025-06-11 21:48:00 +01:00
renovate[bot]
0d176e434a Update dependency axios to v1.7.9 (#12477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 21:47:48 +01:00
Laurent Cozic
ca46df5627 Chore: Trying to fix random ipc test error on CI 2025-06-11 19:07:51 +01:00
renovate[bot]
7389712093 Update dependency @types/serviceworker to v0.0.124 (#12468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 16:55:51 +01:00
renovate[bot]
3f3f7328f9 Update pascalgn/automerge-action action to v0.16.4 (#12473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 16:55:41 +01:00
Laurent Cozic
fda69c7a1e Chore: Trying to fix random IPC test failures (#12470) 2025-06-11 16:54:23 +01:00
renovate[bot]
66ec4f8c51 Update dependency nodemon to v3.1.9 (#12445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-11 15:55:51 +01:00
pedr
d62ac838b8 Chore: Add Joplin Transcribe (#12403) 2025-06-11 15:54:11 +01:00
renovate[bot]
487cb4f743 Update dependency ts-loader to v9.5.2 (#12464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-11 14:01:14 +01:00
renovate[bot]
8eef48ac4b Update dependency wasm-pack to v0.13.1 (#12469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 13:33:56 +01:00
Henry Heino
42a156c2bb Mobile: Upgrade to React Native 0.79 (#12337) 2025-06-11 09:35:51 +01:00
renovate[bot]
caba91fdf6 Update dependency style-to-js to v1.1.16 (#12456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-11 09:35:22 +01:00
renovate[bot]
360446cc79 Update dependency tesseract.js to v5.1.1 (#12463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-11 09:35:15 +01:00
Henry Heino
8c0d5f4ac5 Chore: CI: Increase timeout for sync in ShareNoteDialog.test.tsx (#12467) 2025-06-11 09:34:37 +01:00
Joplin Bot
696fe4d5a6 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-06-11 06:44:39 +00:00
renovate[bot]
c9027719dd Update dependency terminal-kit to v3.1.2 (#12461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 00:37:37 +01:00
Henry Heino
c9936723c8 Chore: Desktop: Fix layout-related warning shown in console on startup (#12452) 2025-06-11 00:14:08 +01:00
Henry Heino
f6056b2d75 Chore: Mobile: Fix Note.test.tsx warnings (#12448) 2025-06-11 00:13:42 +01:00
Henry Heino
303ccce7d2 Chore: Silence expected warning while running tests in lib/ (#12450) 2025-06-11 00:13:13 +01:00
Henry Heino
7a611ac5c5 Chore: Web: Fix TypeMismatchWarning logged frequently on startup in dev mode (#12453) 2025-06-11 00:11:53 +01:00
Henry Heino
45d1d862a1 Chore: Web: Fix "BackHandler is not supported" warning in most cases (#12458) 2025-06-11 00:10:11 +01:00
renovate[bot]
99178fb1c6 Update dependency standard to v17.1.2 (#12455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 22:20:54 +00:00
cedecode
1462284f2f All: Translation: Update de_DE.po (#12462) 2025-06-10 18:18:39 -04:00
Henry Heino
c392854bdf Merge remote-tracking branch 'origin/release-3.3' into dev 2025-06-10 13:19:58 -07:00
renovate[bot]
2ebb3f039d Update dependency sharp to v0.33.5 (#12454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 21:10:18 +01:00
summoner001
c8fd9a2b39 All: Translation: Update hu_HU.po (#12449) 2025-06-10 13:54:19 -04:00
renovate[bot]
869b1e6f98 Update dependency mermaid to v11.4.1 (#12438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 18:30:25 +01:00
renovate[bot]
ee04f28356 Update dependency pm2 to v5.4.3 (#12446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 18:30:01 +01:00
renovate[bot]
d3ac7ad1c3 Update dependency rate-limiter-flexible to v5.0.5 (#12447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 18:29:54 +01:00
renovate[bot]
da4e3fc5bb Update dependency koa to v2.15.4 (#12437)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 14:46:37 +01:00
renovate[bot]
73808f5a25 Update dependency nanoid to v3.3.9 (#12439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 14:46:24 +01:00
Laurent Cozic
c887a86fd8 Chore: Fixes #12443: Fixed tagServerLatest script to work with multiple architectures 2025-06-10 14:44:04 +01:00
Laurent Cozic
b0d888ee73 Android 3.3.10 2025-06-10 10:24:11 +01:00
Eric Duarte
d9c9bed393 Chore: correcting translations into Spanish (June 2025) (#12436) 2025-06-10 10:02:16 +01:00
Henry Heino
f16b6e8887 Mobile: Add additional checks when updating sidebar state (#12428) 2025-06-10 09:03:32 +01:00
Henry Heino
9c8fcbe0c2 Chore: Fix CI: Disable yarn install cache on ARM runners (#12429)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-10 09:03:17 +01:00
renovate[bot]
6760468da3 Update dependency @types/proper-lockfile to v4.1.4 (#12411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-10 09:03:04 +01:00
renovate[bot]
3d3e8a70fa Update dependency dayjs to v1.11.13 (#12431)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-10 09:02:36 +01:00
renovate[bot]
9b65123335 Update dependency katex to v0.16.21 (#12435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 09:02:18 +01:00
renovate[bot]
8493decc03 Update dependency jsdom to v24.1.3 (#12434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 06:07:34 +00:00
renovate[bot]
c28e838f17 Update dependency form-data to v4.0.2 (#12433)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 02:30:49 +00:00
Joplin Bot
22779a7f15 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-06-10 02:02:05 +00:00
renovate[bot]
f2aac66e56 Update dependency bulma to v1.0.3 (#12424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-10 00:00:36 +01:00
renovate[bot]
b956da47fa Update dependency crypto-browserify to v3.12.1 (#12426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 22:08:45 +00:00
Laurent Cozic
8134390bf4 iOS 13.3.8 2025-06-09 18:15:13 +01:00
Laurent Cozic
631211b40c Android 3.3.9 2025-06-09 18:12:40 +01:00
Laurent Cozic
144ed593cc Desktop release v3.3.13 2025-06-09 18:06:38 +01:00
renovate[bot]
47d0d3eb9e Update dependency @types/serviceworker to v0.0.123 (#12412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-09 18:03:52 +01:00
renovate[bot]
4498c5bc0f Update dependency axios to v1.7.9 (#12413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-06-09 18:03:37 +01:00
renovate[bot]
1716562292 Update react monorepo (#12418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 18:03:17 +01:00
Henry Heino
efcfd12489 Chore: CI: Upgrade to Node v18.18 (#12408) 2025-06-09 13:21:29 +01:00
Henry Heino
42f6a9d03d Chore: CI: Disable accessibility scanner tests that cause a crash in CI (#12417) 2025-06-09 13:20:17 +01:00
Henry Heino
73a2075a69 Chore: CI: Fix accessibility scanner sometimes fails due to name conflict (#12415) 2025-06-09 13:20:06 +01:00
Henry Heino
c5ca0151a1 Chore: Upgrade to Yarn v4 (#12407) 2025-06-08 14:07:10 +01:00
mrjo118
9ffeb8c725 Mobile: Change revisions to be presented in reverse order in the dropdown (#12406) 2025-06-08 12:16:13 +01:00
Henry Heino
884141c3e2 Chore: CI: Desktop: Re-run the accessibility scanner on failure (#12409) 2025-06-08 12:00:55 +01:00
Henry Heino
f0d1dd1dd0 Android: Voice typing: Improve silence detection (#12404) 2025-06-07 12:56:41 +01:00
Henry Heino
70e7d8f820 Android: Voice typing: Fix memory leak (#12402) 2025-06-07 12:56:26 +01:00
Henry Heino
100c35cf7f Desktop: Upgrade to Electron 35.5.1 (#12396) 2025-06-07 12:55:42 +01:00
pedr
5b42f4f2a2 Desktop: Fixes #12362: Fixed import of Markdown files that contain links with Windows paths (#12386) 2025-06-07 12:55:34 +01:00
mrjo118
a47d7906af Desktop, Mobile: Fixes #12097: Add ability to delete all history for individual notes (#12381) 2025-06-07 12:52:55 +01:00
mrjo118
73ed17e851 Desktop, Mobile: Extend the maximum note history expiry days to 99999 (#12374) 2025-06-07 12:50:41 +01:00
Henry Heino
484deb450b Desktop: Resolves #12113: Allow users to change the font used in the Markdown viewer and Rich Text Editor (#12356) 2025-06-07 11:15:59 +01:00
Henry Heino
3d2ac91b8a Mobile: Joplin Cloud/Server: Support publishing notes (#12350) 2025-06-06 15:04:09 -07:00
Henry Heino
0fc665d6d8 Desktop: Rich Text Editor: Auto-format "---", "***" and "___" as dividers (#12397) 2025-06-06 19:22:02 +01:00
Henry Heino
961349c1f3 Desktop: Fixes #9593: Rich Text Editor: Fix including $s creates math blocks on save (#12398) 2025-06-06 10:35:03 +01:00
pedr
ab95d728d9 Desktop, Cli: Fixes #12363: Fix Yinxiang HTML imported notes being enclosed by a open anchor tag (#12395) 2025-06-06 10:33:54 +01:00
Henry Heino
ca653d3e88 Desktop: Rich text editor: Add a right-click "Open" menu item for external links (#12391) 2025-06-06 10:32:35 +01:00
Henry Heino
945b309a4d Desktop: Fixes #12377: Fix changing focused window when clicking on the note viewer (#12390) 2025-06-06 10:30:42 +01:00
Henry Heino
ab17625ed8 Desktop: Fixes #12214: Change how the main content size is determined (#12388) 2025-06-06 10:30:17 +01:00
Henry Heino
8e8ab3bd80 Mobile: Increase space available for revisions dropdown (#12379) 2025-06-06 10:21:56 +01:00
Henry Heino
591a56e510 Chore: CI: Update the windows-2019 runner to windows-2025 (#12378) 2025-06-06 10:21:40 +01:00
Henry Heino
86ee95a8d0 Desktop: Performance: Faster startup and smaller application size (#12366) 2025-06-06 10:10:49 +01:00
Henry Heino
a527a278a9 Desktop, Mobile: Resolves #11845: Adjust list toggle behavior for consistency with other apps (#12360) 2025-06-06 10:10:11 +01:00
pedr
eb1970fd1a Chore: Improve OneNote converter debugging (#12359) 2025-06-06 10:09:24 +01:00
Henry Heino
1ecaaa1910 Android: Default to a larger voice typing model (#12352) 2025-06-06 10:05:40 +01:00
pedr
c75b48fbb1 Desktop: Fixes #12295: Fix import of certain OneNote files that contain invalid properties (#12338) 2025-06-06 10:04:50 +01:00
Henry Heino
608dbab453 Desktop: Resolves #11687: Plugins: Allow editor plugins to support multiple windows (#12041) 2025-06-06 10:00:47 +01:00
Henry Heino
291ba88224 Server: Security: SAML: Update samlify to v2.10.0 (#12371) 2025-06-02 21:03:27 +01:00
Tom Chedmail
bdbd16240b Desktop, Cli, Mobile, Server: Add Joplin Server SAML support (#11865) 2025-06-02 16:34:08 +01:00
Joplin Bot
4f826c045a Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-06-01 02:16:46 +00:00
Joplin Bot
885fde4119 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-05-29 12:56:23 +00:00
Henry Heino
f8200efffc Android: Fix voice typing fails to start on certain devices (#12351) 2025-05-28 09:48:23 +01:00
Henry Heino
700ddf269a Android: Voice typing: Update whisper.cpp to v1.7.5 (#12353) 2025-05-28 09:48:06 +01:00
pedr
a755f09033 Desktop: Fixes #11548: Show warning when a plugin is not compatible with the new Markdown editor (#12040)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-05-27 17:24:24 +01:00
Henry Heino
293eac9c04 Mobile: Add note revision viewer (#12305) 2025-05-27 17:22:52 +01:00
Henry Heino
f5e751c27a iOS: Fixes #12314: Fix error shown the first time a user attempts to record (#12328) 2025-05-27 17:20:42 +01:00
Henry Heino
47e4f36f97 Mobile: Allow recording to continue while the app is in the background (#12330) 2025-05-27 17:20:31 +01:00
Henry Heino
fa0dbddb9b iOS: Fixes #12331: Fix sharing to Joplin (#12334) 2025-05-27 17:20:24 +01:00
Henry Heino
c9eb9af741 Chore: Remove unused canvas dependency (#12335) 2025-05-27 17:20:10 +01:00
shawnzhang31
d1cd8e9db4 Doc: Fixed outdated joplin/renderer package README.md (#12346) 2025-05-27 17:18:32 +01:00
Liffindra Angga Zaaldian
16ebff78b4 All: Translation: Update id_ID.po (#12348) 2025-05-27 02:15:47 -04:00
Celestial.y
423ae0d633 All: Translation: Update zh_CN.po (#12347) 2025-05-27 02:14:41 -04:00
Joplin Bot
88c95cc91c Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-05-20 12:57:17 +00:00
Laurent Cozic
24df674726 Desktop release v3.4.1 2025-05-20 09:04:08 +01:00
Laurent Cozic
3faa95a066 Setup new release 3.4 2025-05-20 09:03:55 +01:00
pedr
60e27924ae All: Resolves #12157: Add information about failing tests of OneNote importer inside test names (#12159)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-05-19 23:03:15 +01:00
Henry Heino
d5830dd3a1 Chore: Refactor htmlpack for mobile compatibility (#12174) 2025-05-19 23:02:26 +01:00
Henry Heino
a4dacd65e6 Mobile: Upgrade to React Native 0.77 (#12179) 2025-05-19 23:02:18 +01:00
Henry Heino
cbf6d5506f Chore: Desktop: Update components for compatibility with React 19 (#12184) 2025-05-19 23:02:09 +01:00
Henry Heino
80696fe324 Desktop: Fix keyboard can't add text after certain confirm dialogs are shown (#12200) 2025-05-19 23:00:49 +01:00
Henry Heino
1780a530c9 Desktop: Fix "Open" option for attachments shown in context menu for web links (#12215) 2025-05-19 23:00:15 +01:00
Henry Heino
dc786e8178 Chore: Desktop: Fix electronApp.evaluate-related test failure (#12216) 2025-05-19 22:59:31 +01:00
Self Not Found
e65fbecef0 All: Set new encryption methods as default (#12229) 2025-05-19 22:59:20 +01:00
Henry Heino
5b2f409254 Chore: Remove node-canvas dependency (#12238)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-05-19 22:57:43 +01:00
Henry Heino
77a07c937e MacOS: Fixes #12240: Fix printing (#12244) 2025-05-19 22:57:34 +01:00
Henry Heino
50c5139fa6 Windows: Fix "file://" URLs setting does not allow loading local images (#12281) 2025-05-19 22:55:59 +01:00
Henry Heino
465defb194 Windows: Rich Text Editor: Fix dropping a URL from the Firefox addressbar inserts nothing (#12282) 2025-05-19 22:55:42 +01:00
Henry Heino
12c688eb83 Mobile: Accessibility: Auto-fill the editor search input with the global search (#12291) 2025-05-19 22:55:32 +01:00
Henry Heino
1a6059072a Desktop: Make default window color match system theme (#12303) 2025-05-19 22:55:22 +01:00
Henry Heino
e2ef406aa2 Chore: Web: Simplify accessing certain global APIs in dev mode (#12308) 2025-05-19 22:54:54 +01:00
Henry Heino
dca28f8c2a Mobile: Update js-draw to v1.30.0 (#12322) 2025-05-19 22:54:17 +01:00
Henry Heino
a8ea71c349 Desktop: Built-in plugins: Update Freehand Drawing to v3.1.0 (#12323) 2025-05-19 22:54:08 +01:00
NBA2K1
b13e7c1a3f All: Translation: Update de_DE.po (#12316) 2025-05-19 12:29:55 -04:00
Mihai Vasiliu
ffd9c8e0a0 All: Translation: Update ro_RO.po and ro_MD.po (#12310) 2025-05-17 17:40:08 -04:00
summoner001
a4b0d149bc All: Translation: Update hu_HU.po (#12311) 2025-05-17 16:19:07 -04:00
Mihai Vasiliu
c2b7228170 Revert "All: Translation: Update ro_RO.po and ro_MD.po" (#12257) 2025-05-16 19:35:32 -04:00
SilviaAC
11c8f1e111 All: Translation: Update gl_ES.po (#12256)
Co-authored-by: silvia <s.alvarez.carrera@hotmail.com>
2025-05-16 19:34:19 -04:00
ERYpTION
a1f37dc414 All: Translation: Update da_DK.po (#12255) 2025-05-16 19:32:56 -04:00
summoner001
e4a20f505d All: Translation: Update hu_HU.po (#12252) 2025-05-16 19:31:53 -04:00
Joplin Bot
dfa7ed0a5c Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-05-15 01:55:42 +00:00
Laurent Cozic
f8bf799f00 Doc: Updated sponsors 2025-05-15 00:22:32 +01:00
renovate[bot]
c70faf50f7 Update dependency @adobe/css-tools to v4.4.2 (#12296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 23:20:53 +01:00
Arda Kılıçdağı
b6043489a0 All: Translation: Update tr_TR.po (#12242) 2025-05-06 17:49:02 -04:00
Mihai Vasiliu
37270479e2 All: Translation: Update ro_RO.po and ro_MD.po (#12231) 2025-05-06 17:45:28 -04:00
Laurent Cozic
318ca3de5f Desktop: Setup auto-update service when the feature flag is set in config 2025-05-05 22:13:39 +01:00
Laurent Cozic
1f57a94225 Update translations 2025-05-05 17:28:03 +01:00
Laurent Cozic
cce2f66f01 Chore: Add es_ES translation (credit @ericdq) 2025-05-05 17:27:17 +01:00
Laurent Cozic
277935b8b1 Chore: Fixed conflict between TS and eslint 2025-05-05 10:46:00 +01:00
Laurent Cozic
981759691d Chore: Fix TS errors 2025-05-05 10:06:47 +01:00
Laurent Cozic
370f6bd70e Plugins: Added the webviewApi.menuPopupFromTemplate() API to create context menus 2025-05-05 00:29:31 +01:00
Laurent Cozic
067ce65532 Plugins: Added copyToClipboard command 2025-05-05 00:29:31 +01:00
Joplin Bot
302577ed43 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-05-04 18:39:34 +00:00
Laurent Cozic
5356a8ae36 Doc: Add sponsor 2025-05-04 18:23:44 +01:00
Laurent Cozic
4d790b6ffe Desktop release v3.3.12 2025-05-04 17:57:48 +01:00
Laurent Cozic
b6d8dcee8d Merge branch 'dev' into release-3.3 2025-05-04 17:57:34 +01:00
Laurent Cozic
e5b2e22479 Chore: Fixed TS error 2025-05-04 17:57:09 +01:00
Laurent Cozic
6e98a8ac2d Desktop release v3.3.11 2025-05-04 16:52:28 +01:00
Laurent Cozic
6b60a88dcb Update translations 2025-05-04 16:52:13 +01:00
Laurent Cozic
765cde10fb Merge branch 'dev' into release-3.3 2025-05-04 16:49:41 +01:00
Henry Heino
8f97bb6ddf Desktop: Fixes #12204: Fix crash after removing "toggle tab indentation" keyboard shortcut (#12213) 2025-05-04 16:48:52 +01:00
Henry Heino
073984b9ef Windows: Fix printing (#12219) 2025-05-04 16:48:45 +01:00
Laurent Cozic
5e5a77786c Desktop: Fixes #12222: Prevent application from hanging when multi-instance setup does not work 2025-05-04 16:48:12 +01:00
cedecode
7fa3a223b2 All: Translation: Update de_DE.po (#12221) 2025-05-03 17:55:35 -04:00
Laurent Cozic
5c965d4b95 Update translations 2025-05-03 08:48:48 +01:00
Joplin Bot
cc3da4fd09 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-05-03 01:53:45 +00:00
Mihai Vasiliu
34528d146f All: Translation: Update ro_RO.po and ro_MD.po (#12211) 2025-05-02 10:35:30 -04:00
summoner001
9c76560642 All: Translation: Update hu_HU.po (#12206) 2025-05-02 10:29:03 -04:00
Laurent Cozic
3cb621eb60 Merge branch 'release-3.3' into dev 2025-05-02 15:28:28 +01:00
Laurent Cozic
d9ba532889 Desktop release v3.3.10 2025-05-02 15:27:54 +01:00
Laurent Cozic
5aca4bc72e Desktop: Fixes #11989: Notify user when they are using the Intel app on Apple Silicon 2025-05-02 15:27:24 +01:00
Jozef Gaal
54e7499aa2 All: Translation: Update sk_SK.po (#12205) 2025-05-01 22:14:52 -04:00
Joplin Bot
941b70471f Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-05-02 01:55:37 +00:00
Laurent Cozic
3f364a4a9b Plugins: Undeprecate joplin.settings.value() 2025-05-01 23:14:22 +01:00
804 changed files with 68583 additions and 40245 deletions

30
.env-transcribe-sample Normal file
View File

@@ -0,0 +1,30 @@
# =============================================================================
# Required
# -----------------------------------------------------------------------------
# =============================================================================
SERVER_PORT=4567
API_KEY=random-string
QUEUE_TTL=900000
QUEUE_RETRY_COUNT=2
QUEUE_MAINTENANCE_INTERVAL=30000
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:0.0.2
# Fullpath to images folder
HTR_CLI_IMAGES_FOLDER=/home/user/joplin/packages/transcribe/images
QUEUE_DRIVER=pg
# QUEUE_DRIVER=sqlite
# =============================================================================
# Queue driver
# -----------------------------------------------------------------------------
# =============================================================================
#
# QUEUE_DATABASE_NAME=./queue.sqlite3
QUEUE_DATABASE_NAME=transcribe
QUEUE_DATABASE_USER=transcribe
QUEUE_DATABASE_PASSWORD=transcribe
QUEUE_DATABASE_PORT=5432

View File

@@ -78,6 +78,7 @@ packages/plugins/**/api
packages/plugins/**/dist
packages/server/dist/
packages/utils/dist/
packages/transcribe/dist/
packages/tools/node_modules
packages/tools/PortableAppsLauncher
packages/turndown-plugin-gfm/
@@ -121,6 +122,8 @@ packages/app-cli/app/command-rmnote.test.js
packages/app-cli/app/command-rmnote.js
packages/app-cli/app/command-set.js
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-share.test.js
packages/app-cli/app/command-share.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-use.js
@@ -129,6 +132,8 @@ packages/app-cli/app/gui/FolderListWidget.js
packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
packages/app-cli/tests/MarkupToHtml.js
@@ -151,6 +156,7 @@ packages/app-desktop/app.js
packages/app-desktop/bridge.js
packages/app-desktop/checkForUpdates.js
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyToClipboard.js
packages/app-desktop/commands/editProfileConfig.js
packages/app-desktop/commands/emptyTrash.js
packages/app-desktop/commands/exportDeletionLog.test.js
@@ -296,6 +302,7 @@ packages/app-desktop/gui/NoteEditor/utils/markupRenderOptions.js
packages/app-desktop/gui/NoteEditor/utils/resourceHandling.test.js
packages/app-desktop/gui/NoteEditor/utils/resourceHandling.js
packages/app-desktop/gui/NoteEditor/utils/types.js
packages/app-desktop/gui/NoteEditor/utils/useConnectToEditorPlugin.js
packages/app-desktop/gui/NoteEditor/utils/useDropHandler.js
packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
@@ -419,6 +426,7 @@ packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.js
packages/app-desktop/gui/Sidebar/listItemComponents/TagItem.js
packages/app-desktop/gui/Sidebar/styles/index.js
packages/app-desktop/gui/Sidebar/types.js
packages/app-desktop/gui/SsoLoginScreen/SsoLoginScreen.js
packages/app-desktop/gui/StatusScreen/StatusScreen.js
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
packages/app-desktop/gui/SyncWizard/Dialog.js
@@ -445,7 +453,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/index.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/linkToNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newFolder.js
@@ -534,12 +541,15 @@ packages/app-desktop/integration-tests/sidebar.spec.js
packages/app-desktop/integration-tests/simpleBackup.spec.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/evaluateWithRetry.js
packages/app-desktop/integration-tests/util/extendedExpect.js
packages/app-desktop/integration-tests/util/getImageSourceSize.js
packages/app-desktop/integration-tests/util/getMainWindow.js
packages/app-desktop/integration-tests/util/retryOnFailure.js
packages/app-desktop/integration-tests/util/setDarkMode.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/setSettingValue.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/integration-tests/wcag.spec.js
@@ -573,6 +583,7 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
packages/app-desktop/tools/bundleJs.js
packages/app-desktop/tools/copy7Zip.js
packages/app-desktop/tools/generateLatestArm64Yml.js
packages/app-desktop/tools/githubReleasesUtils.js
@@ -587,11 +598,13 @@ packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/getAssetPath.js
packages/app-desktop/utils/initializeCommandService.js
packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
packages/app-desktop/utils/restartInSafeModeFromMain.js
packages/app-desktop/utils/sourceMapSetup.js
packages/app-desktop/utils/window/types.js
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/commands/dismissPluginPanels.js
@@ -608,12 +621,16 @@ packages/app-mobile/components/BottomDrawer.js
packages/app-mobile/components/CameraView/ActionButtons.js
packages/app-mobile/components/CameraView/Camera/index.jest.js
packages/app-mobile/components/CameraView/Camera/index.js
packages/app-mobile/components/CameraView/Camera/index.web.js
packages/app-mobile/components/CameraView/Camera/types.js
packages/app-mobile/components/CameraView/CameraView.test.js
packages/app-mobile/components/CameraView/CameraView.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.test.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.js
packages/app-mobile/components/CameraView/ScannedBarcodes.js
packages/app-mobile/components/CameraView/types.js
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
packages/app-mobile/components/CameraView/utils/testing.js
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
packages/app-mobile/components/Checkbox.js
packages/app-mobile/components/DialogManager/PromptButton.js
@@ -803,6 +820,8 @@ packages/app-mobile/components/screens/Note/commands/insertDateTime.js
packages/app-mobile/components/screens/Note/commands/setTags.js
packages/app-mobile/components/screens/Note/commands/toggleVisiblePanes.js
packages/app-mobile/components/screens/Note/types.js
packages/app-mobile/components/screens/NoteRevisionViewer.test.js
packages/app-mobile/components/screens/NoteRevisionViewer.js
packages/app-mobile/components/screens/NoteTagsDialog.js
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
packages/app-mobile/components/screens/Notes/NewNoteButton.js
@@ -813,6 +832,9 @@ packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
packages/app-mobile/components/screens/ShareManager/IncomingShareItem.js
packages/app-mobile/components/screens/ShareManager/index.test.js
packages/app-mobile/components/screens/ShareManager/index.js
packages/app-mobile/components/screens/ShareNoteDialog.test.js
packages/app-mobile/components/screens/ShareNoteDialog.js
packages/app-mobile/components/screens/SsoLoginScreen.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.test.js
@@ -874,6 +896,7 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
@@ -890,6 +913,7 @@ packages/app-mobile/utils/makeShowMessageBox.test.js
packages/app-mobile/utils/makeShowMessageBox.js
packages/app-mobile/utils/pickDocument.js
packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareFile.js
@@ -902,6 +926,7 @@ packages/app-mobile/utils/testing/createMockReduxStore.js
packages/app-mobile/utils/testing/getWebViewDomById.js
packages/app-mobile/utils/testing/getWebViewWindowById.js
packages/app-mobile/utils/testing/setupGlobalStore.js
packages/app-mobile/utils/testing/testingLibrary.js
packages/app-mobile/utils/types.js
packages/app-mobile/web/serviceWorker.js
packages/app-mobile/web/webpack.config.js
@@ -912,7 +937,6 @@ packages/default-plugins/commands/editPatch.js
packages/default-plugins/utils/getCurrentCommitHash.js
packages/default-plugins/utils/getPathToPatchFileFor.js
packages/default-plugins/utils/readRepositoryJson.js
packages/default-plugins/utils/waitForCliInput.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5BuiltInOptions.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
@@ -1017,7 +1041,10 @@ packages/generator-joplin/generators/app/templates/api/types.js
packages/generator-joplin/generators/app/templates/api_index.js
packages/generator-joplin/generators/app/templates/src/index.js
packages/generator-joplin/tools/updateCategories.js
packages/htmlpack/src/index.js
packages/htmlpack/index.test.js
packages/htmlpack/index.js
packages/htmlpack/packToString.js
packages/htmlpack/utils/parseHtmlAsync.js
packages/lib/ArrayUtils.js
packages/lib/AsyncActionQueue.test.js
packages/lib/AsyncActionQueue.js
@@ -1042,6 +1069,7 @@ packages/lib/RotatingLogs.js
packages/lib/SyncTargetFilesystem.js
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServerSAML.js
packages/lib/SyncTargetNone.js
packages/lib/SyncTargetOneDrive.js
packages/lib/SyncTargetRegistry.js
@@ -1056,6 +1084,7 @@ packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js
packages/lib/commands/index.js
packages/lib/commands/leaveSharedFolder.js
packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/renderMarkup.test.js
@@ -1068,6 +1097,15 @@ packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
packages/lib/components/shared/SamlShared.js
packages/lib/components/shared/ShareNoteDialog/onUnshareNoteClick.js
packages/lib/components/shared/ShareNoteDialog/types.js
packages/lib/components/shared/ShareNoteDialog/useEncryptionWarningMessage.js
packages/lib/components/shared/ShareNoteDialog/useOnShareLinkClick.js
packages/lib/components/shared/ShareNoteDialog/useShareStatusMessage.js
packages/lib/components/shared/SsoScreenShared.js
packages/lib/components/shared/config/config-shared.js
packages/lib/components/shared/config/plugins/types.js
packages/lib/components/shared/config/plugins/useOnDeleteHandler.js
@@ -1102,12 +1140,13 @@ packages/lib/fsDriver.test.js
packages/lib/geolocation-node.js
packages/lib/getAppName.test.js
packages/lib/getAppName.js
packages/lib/hooks/plugins/usePlugin.js
packages/lib/hooks/plugins/useVisiblePluginEditorViewIds.js
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useEventListener.js
packages/lib/hooks/useNowEffect.test.js
packages/lib/hooks/useNowEffect.js
packages/lib/hooks/usePlugin.js
packages/lib/hooks/usePrevious.js
packages/lib/hooks/useQueuedAsyncEffect.test.js
packages/lib/hooks/useQueuedAsyncEffect.js
@@ -1348,12 +1387,14 @@ packages/lib/services/plugins/testing/MockPluginRunner.js
packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getActivePluginEditorView.js
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
packages/lib/services/plugins/utils/getPluginSettingValue.js
packages/lib/services/plugins/utils/getShownPluginEditorView.js
packages/lib/services/plugins/utils/getShownPluginEditorViewIds.js
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
packages/lib/services/plugins/utils/isCompatible/index.test.js
packages/lib/services/plugins/utils/isCompatible/index.js
@@ -1509,11 +1550,13 @@ packages/lib/utils/ipc/utils/separateCallbacksFromSerializableArray.js
packages/lib/utils/joplinCloud/index.js
packages/lib/utils/joplinCloud/types.js
packages/lib/utils/markupLanguageUtils.js
packages/lib/utils/prefixWithHttps.js
packages/lib/utils/processStartFlags.js
packages/lib/utils/replaceUnsupportedCharacters.test.js
packages/lib/utils/replaceUnsupportedCharacters.js
packages/lib/utils/resolvePathWithinDir.test.js
packages/lib/utils/resolvePathWithinDir.js
packages/lib/utils/types/pdfJs.js
packages/lib/utils/userFetcher.js
packages/lib/utils/webDAVUtils.test.js
packages/lib/utils/webDAVUtils.js
@@ -1605,6 +1648,18 @@ packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionTracker.js
packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.test.js

View File

@@ -23,6 +23,8 @@ module.exports = {
'FileSystemCreateWritableOptions': 'readonly',
'FileSystemHandle': 'readonly',
'IDBTransactionMode': 'readonly',
'BigInt': 'readonly',
'globalThis': 'readonly',
// ServiceWorker
'ExtendableEvent': 'readonly',

View File

@@ -117,7 +117,7 @@ if [ "$RUN_TESTS" == "1" ]; then
# On Linux, we run the Joplin Server tests using PostgreSQL
if [ "$IS_LINUX" == "1" ]; then
echo "Running Joplin Server tests using PostgreSQL..."
sudo docker compose --file docker-compose.db-dev.yml up -d
sudo docker compose --parallel 1 --file docker-compose.db-dev.yml up -d
cmdResult=$?
if [ $cmdResult -ne 0 ]; then
exit $cmdResult

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- id: automerge
name: automerge
uses: "pascalgn/automerge-action@v0.16.3"
uses: "pascalgn/automerge-action@v0.16.4"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_METHOD: "squash"

View File

@@ -8,12 +8,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: olegtarasov/get-tag@v2.1.3
- uses: olegtarasov/get-tag@v2.1.4
- uses: actions/setup-node@v4
with:
# We need to pin the version to 18.15, because 18.16+ fails with this error:
# https://github.com/facebook/react-native/issues/36440
node-version: '18.15.0'
node-version: '18.20.8'
cache: 'yarn'
- name: Install Yarn
@@ -30,7 +30,7 @@ jobs:
# See github-action-main.yml for explanation
- uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.13'
- name: Set Publish Flag
run: |

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-2019, ubuntu-22.04-arm]
os: [macos-13, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
steps:
- uses: actions/checkout@v4
@@ -122,7 +122,6 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'yarn'
- name: Install Yarn
run: |
@@ -141,11 +140,6 @@ jobs:
echo "RUNNER_ARCH=$RUNNER_ARCH"
echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM"
# Canvas is only needed for tests and it doesn't build in ARM64 so remove it
cd packages/lib
yarn remove canvas
cd ../..
yarn install
yarn buildServerDocker --platform $DOCKER_IMAGE_PLATFORM --tag-name server-v0.0.0 --repository joplin/server
@@ -159,7 +153,7 @@ jobs:
docker run -p 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
# Wait for server to start
sleep 30
sleep 120
# Check if status code is correct
# if the actual_status DOES NOT include the expected_status

View File

@@ -5,10 +5,10 @@ runs:
steps:
# Trying to fix random networking issues on Windows
# https://github.com/actions/runner-images/issues/1187#issuecomment-686735760
- name: Disable TCP/UDP offload on Windows
if: runner.os == 'Windows'
shell: pwsh
run: Disable-NetAdapterChecksumOffload -Name * -TcpIPv4 -UdpIPv4 -TcpIPv6 -UdpIPv6
# - name: Disable TCP/UDP offload on Windows
# if: runner.os == 'Windows'
# shell: pwsh
# run: Disable-NetAdapterChecksumOffload -Name * -TcpIPv4 -UdpIPv4 -TcpIPv6 -UdpIPv6
- name: Disable TCP/UDP offload on Linux
if: runner.os == 'Linux'
@@ -47,14 +47,17 @@ runs:
# Required for building the canvas package
brew install pango
- uses: olegtarasov/get-tag@v2.1.3
- uses: olegtarasov/get-tag@v2.1.4
- uses: dtolnay/rust-toolchain@stable
if: ${{ runner.os != 'Windows' }}
- uses: actions/setup-node@v4
with:
# We need to pin the version to 18.15, because 18.16+ fails with this error:
# https://github.com/facebook/react-native/issues/36440
node-version: '18.15.0'
cache: 'yarn'
node-version: '18.20.8'
# Disable the cache on ARM runners. For now, we don't run "yarn install" on these
# environments and this breaks actions/setup-node.
# See https://github.com/laurent22/joplin/commit/47d0d3eb9e89153a609fb5441344da10904c6308#commitcomment-159577783.
# cache: ${{ (!contains(runner.os, 'arm') && 'yarn') || '' }}
- name: Install Yarn
shell: bash
@@ -69,4 +72,4 @@ runs:
# Ref: https://github.com/nodejs/node-gyp/issues/2869
- uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.13'

View File

@@ -9,13 +9,18 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-13, ubuntu-22.04, windows-2025]
os: [ubuntu-22.04, windows-2025]
steps:
- uses: actions/checkout@v4
- name: Setup build environment
uses: ./.github/workflows/shared/setup-build-environment
- name: Build
run: yarn install
env:
# The onenote-converter package uses Rust, which isn't installed on all CI
# runners. Since the onenote-converter doesn't have UI tests, it can be excluded
# from build:
SKIP_ONENOTE_CONVERTER_BUILD: 1
- name: Run UI tests
shell: bash
run: |

64
.gitignore vendored
View File

@@ -46,6 +46,7 @@ sync_staging.sh
TODO.md
packages/tools/commit_hook.txt
packages/tools/github_oauth_token.txt
packages/app-desktop/main-html-out.js
lerna-debug.log
.env
docs/**/*.mustache
@@ -58,6 +59,7 @@ docs/**/*.mustache
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/joplin-empty-package
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
@@ -95,6 +97,8 @@ packages/app-cli/app/command-rmnote.test.js
packages/app-cli/app/command-rmnote.js
packages/app-cli/app/command-set.js
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-share.test.js
packages/app-cli/app/command-share.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-use.js
@@ -103,6 +107,8 @@ packages/app-cli/app/gui/FolderListWidget.js
packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
packages/app-cli/tests/MarkupToHtml.js
@@ -125,6 +131,7 @@ packages/app-desktop/app.js
packages/app-desktop/bridge.js
packages/app-desktop/checkForUpdates.js
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyToClipboard.js
packages/app-desktop/commands/editProfileConfig.js
packages/app-desktop/commands/emptyTrash.js
packages/app-desktop/commands/exportDeletionLog.test.js
@@ -270,6 +277,7 @@ packages/app-desktop/gui/NoteEditor/utils/markupRenderOptions.js
packages/app-desktop/gui/NoteEditor/utils/resourceHandling.test.js
packages/app-desktop/gui/NoteEditor/utils/resourceHandling.js
packages/app-desktop/gui/NoteEditor/utils/types.js
packages/app-desktop/gui/NoteEditor/utils/useConnectToEditorPlugin.js
packages/app-desktop/gui/NoteEditor/utils/useDropHandler.js
packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
@@ -393,6 +401,7 @@ packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.js
packages/app-desktop/gui/Sidebar/listItemComponents/TagItem.js
packages/app-desktop/gui/Sidebar/styles/index.js
packages/app-desktop/gui/Sidebar/types.js
packages/app-desktop/gui/SsoLoginScreen/SsoLoginScreen.js
packages/app-desktop/gui/StatusScreen/StatusScreen.js
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
packages/app-desktop/gui/SyncWizard/Dialog.js
@@ -419,7 +428,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/index.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/linkToNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newFolder.js
@@ -508,12 +516,15 @@ packages/app-desktop/integration-tests/sidebar.spec.js
packages/app-desktop/integration-tests/simpleBackup.spec.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/evaluateWithRetry.js
packages/app-desktop/integration-tests/util/extendedExpect.js
packages/app-desktop/integration-tests/util/getImageSourceSize.js
packages/app-desktop/integration-tests/util/getMainWindow.js
packages/app-desktop/integration-tests/util/retryOnFailure.js
packages/app-desktop/integration-tests/util/setDarkMode.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/setSettingValue.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/integration-tests/wcag.spec.js
@@ -547,6 +558,7 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
packages/app-desktop/tools/bundleJs.js
packages/app-desktop/tools/copy7Zip.js
packages/app-desktop/tools/generateLatestArm64Yml.js
packages/app-desktop/tools/githubReleasesUtils.js
@@ -561,11 +573,13 @@ packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/getAssetPath.js
packages/app-desktop/utils/initializeCommandService.js
packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
packages/app-desktop/utils/restartInSafeModeFromMain.js
packages/app-desktop/utils/sourceMapSetup.js
packages/app-desktop/utils/window/types.js
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/commands/dismissPluginPanels.js
@@ -582,12 +596,16 @@ packages/app-mobile/components/BottomDrawer.js
packages/app-mobile/components/CameraView/ActionButtons.js
packages/app-mobile/components/CameraView/Camera/index.jest.js
packages/app-mobile/components/CameraView/Camera/index.js
packages/app-mobile/components/CameraView/Camera/index.web.js
packages/app-mobile/components/CameraView/Camera/types.js
packages/app-mobile/components/CameraView/CameraView.test.js
packages/app-mobile/components/CameraView/CameraView.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.test.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.js
packages/app-mobile/components/CameraView/ScannedBarcodes.js
packages/app-mobile/components/CameraView/types.js
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
packages/app-mobile/components/CameraView/utils/testing.js
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
packages/app-mobile/components/Checkbox.js
packages/app-mobile/components/DialogManager/PromptButton.js
@@ -777,6 +795,8 @@ packages/app-mobile/components/screens/Note/commands/insertDateTime.js
packages/app-mobile/components/screens/Note/commands/setTags.js
packages/app-mobile/components/screens/Note/commands/toggleVisiblePanes.js
packages/app-mobile/components/screens/Note/types.js
packages/app-mobile/components/screens/NoteRevisionViewer.test.js
packages/app-mobile/components/screens/NoteRevisionViewer.js
packages/app-mobile/components/screens/NoteTagsDialog.js
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
packages/app-mobile/components/screens/Notes/NewNoteButton.js
@@ -787,6 +807,9 @@ packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
packages/app-mobile/components/screens/ShareManager/IncomingShareItem.js
packages/app-mobile/components/screens/ShareManager/index.test.js
packages/app-mobile/components/screens/ShareManager/index.js
packages/app-mobile/components/screens/ShareNoteDialog.test.js
packages/app-mobile/components/screens/ShareNoteDialog.js
packages/app-mobile/components/screens/SsoLoginScreen.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.test.js
@@ -848,6 +871,7 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
@@ -864,6 +888,7 @@ packages/app-mobile/utils/makeShowMessageBox.test.js
packages/app-mobile/utils/makeShowMessageBox.js
packages/app-mobile/utils/pickDocument.js
packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareFile.js
@@ -876,6 +901,7 @@ packages/app-mobile/utils/testing/createMockReduxStore.js
packages/app-mobile/utils/testing/getWebViewDomById.js
packages/app-mobile/utils/testing/getWebViewWindowById.js
packages/app-mobile/utils/testing/setupGlobalStore.js
packages/app-mobile/utils/testing/testingLibrary.js
packages/app-mobile/utils/types.js
packages/app-mobile/web/serviceWorker.js
packages/app-mobile/web/webpack.config.js
@@ -886,7 +912,6 @@ packages/default-plugins/commands/editPatch.js
packages/default-plugins/utils/getCurrentCommitHash.js
packages/default-plugins/utils/getPathToPatchFileFor.js
packages/default-plugins/utils/readRepositoryJson.js
packages/default-plugins/utils/waitForCliInput.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5BuiltInOptions.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
@@ -991,7 +1016,10 @@ packages/generator-joplin/generators/app/templates/api/types.js
packages/generator-joplin/generators/app/templates/api_index.js
packages/generator-joplin/generators/app/templates/src/index.js
packages/generator-joplin/tools/updateCategories.js
packages/htmlpack/src/index.js
packages/htmlpack/index.test.js
packages/htmlpack/index.js
packages/htmlpack/packToString.js
packages/htmlpack/utils/parseHtmlAsync.js
packages/lib/ArrayUtils.js
packages/lib/AsyncActionQueue.test.js
packages/lib/AsyncActionQueue.js
@@ -1016,6 +1044,7 @@ packages/lib/RotatingLogs.js
packages/lib/SyncTargetFilesystem.js
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServerSAML.js
packages/lib/SyncTargetNone.js
packages/lib/SyncTargetOneDrive.js
packages/lib/SyncTargetRegistry.js
@@ -1030,6 +1059,7 @@ packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js
packages/lib/commands/index.js
packages/lib/commands/leaveSharedFolder.js
packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/renderMarkup.test.js
@@ -1042,6 +1072,15 @@ packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
packages/lib/components/shared/SamlShared.js
packages/lib/components/shared/ShareNoteDialog/onUnshareNoteClick.js
packages/lib/components/shared/ShareNoteDialog/types.js
packages/lib/components/shared/ShareNoteDialog/useEncryptionWarningMessage.js
packages/lib/components/shared/ShareNoteDialog/useOnShareLinkClick.js
packages/lib/components/shared/ShareNoteDialog/useShareStatusMessage.js
packages/lib/components/shared/SsoScreenShared.js
packages/lib/components/shared/config/config-shared.js
packages/lib/components/shared/config/plugins/types.js
packages/lib/components/shared/config/plugins/useOnDeleteHandler.js
@@ -1076,12 +1115,13 @@ packages/lib/fsDriver.test.js
packages/lib/geolocation-node.js
packages/lib/getAppName.test.js
packages/lib/getAppName.js
packages/lib/hooks/plugins/usePlugin.js
packages/lib/hooks/plugins/useVisiblePluginEditorViewIds.js
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useEventListener.js
packages/lib/hooks/useNowEffect.test.js
packages/lib/hooks/useNowEffect.js
packages/lib/hooks/usePlugin.js
packages/lib/hooks/usePrevious.js
packages/lib/hooks/useQueuedAsyncEffect.test.js
packages/lib/hooks/useQueuedAsyncEffect.js
@@ -1322,12 +1362,14 @@ packages/lib/services/plugins/testing/MockPluginRunner.js
packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getActivePluginEditorView.js
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
packages/lib/services/plugins/utils/getPluginSettingValue.js
packages/lib/services/plugins/utils/getShownPluginEditorView.js
packages/lib/services/plugins/utils/getShownPluginEditorViewIds.js
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
packages/lib/services/plugins/utils/isCompatible/index.test.js
packages/lib/services/plugins/utils/isCompatible/index.js
@@ -1483,11 +1525,13 @@ packages/lib/utils/ipc/utils/separateCallbacksFromSerializableArray.js
packages/lib/utils/joplinCloud/index.js
packages/lib/utils/joplinCloud/types.js
packages/lib/utils/markupLanguageUtils.js
packages/lib/utils/prefixWithHttps.js
packages/lib/utils/processStartFlags.js
packages/lib/utils/replaceUnsupportedCharacters.test.js
packages/lib/utils/replaceUnsupportedCharacters.js
packages/lib/utils/resolvePathWithinDir.test.js
packages/lib/utils/resolvePathWithinDir.js
packages/lib/utils/types/pdfJs.js
packages/lib/utils/userFetcher.js
packages/lib/utils/webDAVUtils.test.js
packages/lib/utils/webDAVUtils.js
@@ -1579,6 +1623,18 @@ packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionTracker.js
packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.test.js

View File

@@ -8,6 +8,7 @@
"@joplin/fork-sax",
"@joplin/fork-uslug",
"@joplin/htmlpack",
"@joplin/transcribe",
"@joplin/lib",
"@joplin/onenote-converter",
"@joplin/pdf-viewer",

View File

@@ -1,3 +1,4 @@
{
"cSpell.enabled": true
"cSpell.enabled": true,
"editor.insertSpaces": false
}

View File

@@ -0,0 +1,10 @@
# @joplin/empty
An empty package. This package can be used to exclude certain dependencies from build.
For example, the `canvas` dependency is an optional dependency of `pdfjs-dist`. However, it isn't used by Joplin and can cause build to fail in certain environments. The `@joplin/empty` package can exclude `canvas` from the build by adding a resolution to `resolutions` in the top-level `package.json`. For example, resolving `canvas@npm:^2.11` to `file:./packages/empty/`.
See also:
- [Yarn docs: Manifest resolutions](https://yarnpkg.com/configuration/manifest#resolutions)
- [GitHub comment: Yarn: Ignoring packages](https://github.com/yarnpkg/yarn/issues/4611#issuecomment-1370284462)

View File

@@ -0,0 +1,10 @@
{
"name": "@joplin/empty",
"version": "0.0.0",
"description": "An empty package, used as a way to exclude certain packages from build",
"private": true,
"repository": {
"type": "git",
"url": "git+https://github.com/laurent22/joplin.git"
}
}

View File

@@ -1,6 +1,22 @@
# We remove the `canvas` optional dependency because electron-rebuild fails to build it, and
# the `canvas` API is already part of Electron
diff --git a/build/pdf.js b/build/pdf.js
index 4acf16b1d6f9351bda1a98649ea4f926618fe617..f63dbc6050ca63ca8e8ed982edea134103fa15dd 100644
--- a/build/pdf.js
+++ b/build/pdf.js
@@ -6244,8 +6244,9 @@ class NodeFilterFactory extends _base_factory.BaseFilterFactory {}
exports.NodeFilterFactory = NodeFilterFactory;
class NodeCanvasFactory extends _base_factory.BaseCanvasFactory {
_createCanvas(width, height) {
- const Canvas = require("canvas");
- return Canvas.createCanvas(width, height);
+ throw new Error('Node canvas disabled');
+ // const Canvas = require("canvas");
+ // return Canvas.createCanvas(width, height);
}
}
exports.NodeCanvasFactory = NodeCanvasFactory;
diff --git a/package.json b/package.json
index 105811f53d508486e08a60dc1b6e437cd24d7427..dea6a4e6612c4a4006cc482e46ff5270dcfda1e5 100644
--- a/package.json

View File

@@ -1,25 +0,0 @@
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
index 8a719ca35af1cc3a4192c5c5f8258fd4f7fea990..5f8831f81cd164a4f627423427ead92fa286b115 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
@@ -37,7 +37,7 @@ import com.facebook.react.uimanager.common.ViewUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -149,7 +149,10 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
}
private class ConcurrentOperationQueue {
- private final Queue<UIThreadOperation> mQueue = new ConcurrentLinkedQueue<>();
+ // Patch: Use LinkedBlockingQueue instead of ConcurrentLinkedQueue.
+ // In some versions of Android, ConcurrentLinkedQueue is known to drop
+ // items, causing crashing. See https://github.com/laurent22/joplin/issues/8425
+ private final Queue<UIThreadOperation> mQueue = new LinkedBlockingQueue<>();
@Nullable private UIThreadOperation mPeekedOperation = null;
@AnyThread

View File

@@ -0,0 +1,205 @@
# This patch fixes two issues:
# - Updates RCTDeviceInfo.m to match https://github.com/facebook/react-native/commit/0b8db7e5e814cfbf9974cc5b6ceb64e8006d8a3c.
# This fixes an issue in which useWindowDimensions returns incorrect
# values in landscape mode in iOS.
# This should be fixed in React Native 0.80. See https://github.com/facebook/react-native/issues/51086.
# - Updates NativeAnimatedModule.java to work around an Android 12-specific crash.
diff --git a/React/CoreModules/RCTDeviceInfo.mm b/React/CoreModules/RCTDeviceInfo.mm
index 6b4fcef852252e8d4ac2aceb12175fdfafb4def7..8ceab21e8653d429876d10e2d12ed1342780ad7d 100644
--- a/React/CoreModules/RCTDeviceInfo.mm
+++ b/React/CoreModules/RCTDeviceInfo.mm
@@ -14,9 +14,7 @@
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTInitializing.h>
#import <React/RCTInvalidating.h>
-#import <React/RCTKeyWindowValuesProxy.h>
#import <React/RCTUtils.h>
-#import <React/RCTWindowSafeAreaProxy.h>
#import <atomic>
#import "CoreModulesPlugins.h"
@@ -31,8 +29,13 @@ using namespace facebook::react;
NSDictionary *_currentInterfaceDimensions;
BOOL _isFullscreen;
std::atomic<BOOL> _invalidated;
+ NSDictionary *_constants;
+
+ __weak UIWindow *_applicationWindow;
}
+static NSString *const kFrameKeyPath = @"frame";
+
@synthesize moduleRegistry = _moduleRegistry;
RCT_EXPORT_MODULE()
@@ -40,14 +43,26 @@ RCT_EXPORT_MODULE()
- (instancetype)init
{
if (self = [super init]) {
- [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary];
+ _applicationWindow = RCTKeyWindow();
+ [_applicationWindow addObserver:self forKeyPath:kFrameKeyPath options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ if ([keyPath isEqualToString:kFrameKeyPath]) {
+ [self interfaceFrameDidChange];
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self];
+ }
+}
+
+ (BOOL)requiresMainQueueSetup
{
- return NO;
+ return YES;
}
- (dispatch_queue_t)methodQueue
@@ -81,7 +96,7 @@ RCT_EXPORT_MODULE()
#if TARGET_OS_IOS
- _currentInterfaceOrientation = [RCTKeyWindowValuesProxy sharedInstance].currentInterfaceOrientation;
+ _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(interfaceFrameDidChange)
@@ -98,6 +113,15 @@ RCT_EXPORT_MODULE()
selector:@selector(invalidate)
name:RCTBridgeWillInvalidateModulesNotification
object:nil];
+
+ _constants = @{
+ @"Dimensions" : [self _exportedDimensions],
+ // Note:
+ // This prop is deprecated and will be removed in a future release.
+ // Please use this only for a quick and temporary solution.
+ // Use <SafeAreaView> instead.
+ @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()),
+ };
}
- (void)invalidate
@@ -120,6 +144,8 @@ RCT_EXPORT_MODULE()
[[NSNotificationCenter defaultCenter] removeObserver:self name:RCTBridgeWillInvalidateModulesNotification object:nil];
+ [_applicationWindow removeObserver:self forKeyPath:kFrameKeyPath];
+
#if TARGET_OS_IOS
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
#endif
@@ -132,8 +158,13 @@ static BOOL RCTIsIPhoneNotched()
#if TARGET_OS_IOS
dispatch_once(&onceToken, ^{
+ RCTAssertMainQueue();
+
// 20pt is the top safeArea value in non-notched devices
- isIPhoneNotched = [RCTWindowSafeAreaProxy sharedInstance].currentSafeAreaInsets.top > 20;
+ UIWindow *keyWindow = RCTKeyWindow();
+ if (keyWindow) {
+ isIPhoneNotched = keyWindow.safeAreaInsets.top > 20;
+ }
});
#endif
@@ -142,11 +173,13 @@ static BOOL RCTIsIPhoneNotched()
static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
{
+ RCTAssertMainQueue();
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
+ UIView *mainWindow = RCTKeyWindow();
// We fallback to screen size if a key window is not found.
- CGSize windowSize = [RCTKeyWindowValuesProxy sharedInstance].windowSize;
+ CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize;
NSDictionary<NSString *, NSNumber *> *dimsWindow = @{
@"width" : @(windowSize.width),
@@ -170,7 +203,10 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
RCTAssert(_moduleRegistry, @"Failed to get exported dimensions: RCTModuleRegistry is nil");
RCTAccessibilityManager *accessibilityManager =
(RCTAccessibilityManager *)[_moduleRegistry moduleForName:"AccessibilityManager"];
- RCTAssert(accessibilityManager, @"Failed to get exported dimensions: AccessibilityManager is nil");
+ // TOOD(T225745315): For some reason, accessibilityManager is nil in some cases.
+ // We default the fontScale to 1.0 in this case. This should be okay: if we assume
+ // that accessibilityManager will eventually become available, js will eventually
+ // be updated with the correct fontScale.
CGFloat fontScale = accessibilityManager ? accessibilityManager.multiplier : 1.0;
return RCTExportedDimensions(fontScale);
}
@@ -182,14 +218,7 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
- (NSDictionary<NSString *, id> *)getConstants
{
- return @{
- @"Dimensions" : [self _exportedDimensions],
- // Note:
- // This prop is deprecated and will be removed in a future release.
- // Please use this only for a quick and temporary solution.
- // Use <SafeAreaView> instead.
- @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()),
- };
+ return _constants;
}
- (void)didReceiveNewContentSizeMultiplier
@@ -209,10 +238,11 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
- (void)interfaceOrientationDidChange
{
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
- UIWindow *keyWindow = RCTKeyWindow();
- UIInterfaceOrientation nextOrientation = keyWindow.windowScene.interfaceOrientation;
+ UIApplication *application = RCTSharedApplication();
+ UIInterfaceOrientation nextOrientation = RCTKeyWindow().windowScene.interfaceOrientation;
- BOOL isRunningInFullScreen = CGRectEqualToRect(keyWindow.frame, keyWindow.screen.bounds);
+ BOOL isRunningInFullScreen =
+ CGRectEqualToRect(application.delegate.window.frame, application.delegate.window.screen.bounds);
// We are catching here two situations for multitasking view:
// a) The app is in Split View and the container gets resized -> !isRunningInFullScreen
// b) The app changes to/from fullscreen example: App runs in slide over mode and goes into fullscreen->
@@ -276,3 +306,4 @@ Class RCTDeviceInfoCls(void)
{
return RCTDeviceInfo.class;
}
+
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
index cf14e51cf5f561b84f1b6ace8410fc77d626758e..abc8c64adf26fbf73429aee7fd4f76877e98849a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
@@ -42,6 +42,7 @@ import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -155,8 +156,15 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
}
private class ConcurrentOperationQueue {
- private final Queue<UIThreadOperation> mQueue = new ConcurrentLinkedQueue<>();
- @Nullable private UIThreadOperation mPeekedOperation = null;
+ // Patch: Use LinkedBlockingQueue instead of ConcurrentLinkedQueue.
+ // In some versions of Android, ConcurrentLinkedQueue is known to drop
+ // items, causing crashing. See https://github.com/laurent22/joplin/issues/8425
+ private final Queue<UIThreadOperation> mQueue = (
+ // The issue exists for Android 12, which corresponds to API levels 31 and 32.
+ Build.VERSION.SDK_INT == 31 || Build.VERSION.SDK_INT == 32
+ ) ? new LinkedBlockingQueue<>() : new ConcurrentLinkedQueue<>();
+
+ @Nullable private UIThreadOperation mPeekedOperation = null;
@AnyThread
boolean isEmpty() {

View File

@@ -1,21 +1,21 @@
# This patch improves the note actions menu (the kebab menu)'s accessibility
# by labelling its dismiss button.
diff --git a/build/rnpm.js b/build/rnpm.js
index 1111c2de99b3d4c5651ca4eee3ba59c0ce8e13e1..d410ee12b38d02c399b0a40973217da0082d73c0 100644
index 47bc91a88b9e2246a0ce4295f9f932da6a572461..75b5a22bdcbc2594238bcf953df6d54e18cc7793 100644
--- a/build/rnpm.js
+++ b/build/rnpm.js
@@ -1573,7 +1573,9 @@
@@ -1267,7 +1267,9 @@
onPress = _this$props.onPress,
style = _this$props.style;
return /*#__PURE__*/React__default.createElement(reactNative.TouchableWithoutFeedback, {
return React__default.createElement(reactNative.TouchableWithoutFeedback, {
- onPress: onPress
+ onPress: onPress,
+ accessibilityLabel: _this$props.accessibilityLabel,
+ accessibilityRole: 'button',
}, /*#__PURE__*/React__default.createElement(reactNative.Animated.View, {
}, React__default.createElement(reactNative.Animated.View, {
style: [styles.fullscreen, {
opacity: this.fadeAnim
@@ -1588,7 +1590,8 @@
@@ -1282,7 +1284,8 @@
}(React.Component);
Backdrop.propTypes = {
@@ -25,24 +25,33 @@ index 1111c2de99b3d4c5651ca4eee3ba59c0ce8e13e1..d410ee12b38d02c399b0a40973217da0
};
var styles = reactNative.StyleSheet.create({
fullscreen: {
@@ -1658,6 +1661,7 @@
@@ -1352,6 +1355,7 @@
style: styles$1.placeholder
}, /*#__PURE__*/React__default.createElement(Backdrop, {
}, React__default.createElement(Backdrop, {
onPress: ctx._onBackdropPress,
+ accessibilityLabel: this.props.closeButtonLabel,
style: backdropStyles,
ref: ctx.onBackdropRef
}), ctx._makeOptions());
@@ -2090,6 +2094,7 @@
}), /*#__PURE__*/React__default.createElement(MenuPlaceholder, {
@@ -1784,6 +1788,7 @@
}), React__default.createElement(MenuPlaceholder, {
ctx: this,
backdropStyles: customStyles.backdrop,
+ closeButtonLabel: this.props.closeButtonLabel,
ref: this._onPlaceholderRef
}))));
}
@@ -1854,7 +1859,7 @@
var _options$props = options.props,
optionsContainerStyle = _options$props.optionsContainerStyle,
renderOptionsContainer = _options$props.renderOptionsContainer,
- customStyles = _options$props.customStyles;
+ customStyles = _options$props.customStyles || {};
var optionsRenderer = renderOptionsContainer || defaultOptionsContainerRenderer;
var isOutside = !triggerLayout || !optionsLayout;
diff --git a/src/index.d.ts b/src/index.d.ts
index 1db1e643a915e4bfb715e33354678ec1be219f50..007157e366d1935368bdd8eff5e7a0773e183d0f 100644
index 7e1ef2e441a665e97c304984080399f9646395df..673c4f713757abfb1851cba0d4560020c83e5f50 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -18,6 +18,7 @@ declare module "react-native-popup-menu" {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

942
.yarn/releases/yarn-4.9.2.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -2,17 +2,20 @@ nmHoistingLimits: workspaces
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
compressionLevel: mixed
enableGlobalCache: false
yarnPath: .yarn/releases/yarn-3.8.3.cjs
yarnPath: .yarn/releases/yarn-4.9.2.cjs
logFilters:
# Disable useless non-actionable warnings.
# https://github.com/yarnpkg/yarn/issues/4064
# e.g. "Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details."
- code: YN0086
level: discard
# eg "@joplin/app-desktop@workspace:packages/app-desktop provides react (p87edd) with version 18.2.0, which doesn't satisfy what @testing-library/react-hooks and some of its descendants request"
- code: YN0060
level: discard

View File

@@ -1300,4 +1300,9 @@ footer .bottom-links-row p {
:lang(zh-cn) #plans-section .faq {
display: none;
}
.cfa-button {
margin-top: 10px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,24 +1,28 @@
<div class="col-12 col-lg-4 account-type-{{priceMonthly.accountType}}">
<div class="col-12 col-lg-4 account-type-{{priceMonthly.accountType}} hosting-type-{{hostingType}}">
<div class="price-container {{#featured}}price-container-blue{{/featured}}">
<div class="price-row">
<div class="plan-type">
<img src="{{imageBaseUrl}}/{{iconName}}.png"/>&nbsp;{{title}}
<div class="price-row">
<div class="plan-type">
<img src="{{imageBaseUrl}}/{{iconName}}.png"/>&nbsp;{{title}}
</div>
{{#priceMonthly.formattedMonthlyAmount}}
<div class="plan-price plan-price-monthly">
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
<div class="plan-price plan-price-yearly">
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
{{/priceMonthly.formattedMonthlyAmount}}
</div>
<div class="plan-price plan-price-monthly">
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
{{#priceYearly.formattedMonthlyAmount}}
<div class="plan-price-yearly-per-year">
<div>
({{priceYearly.formattedAmount}}<sub class="per-year">&nbsp;<span translate>/year</span></sub>)
</div>
</div>
<div class="plan-price plan-price-yearly">
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
</div>
<div class="plan-price-yearly-per-year">
<div>
({{priceYearly.formattedAmount}}<sub class="per-year">&nbsp;<span translate>/year</span></sub>)
</div>
</div>
{{/priceYearly.formattedMonthlyAmount}}
{{#featureLabelsOn}}
<p><i class="fas fa-check feature feature-on"></i>{{.}}</p>
@@ -29,7 +33,11 @@
{{/featureLabelsOff}}
<p class="text-center subscribe-wrapper">
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton">{{cfaLabel}}</a>
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton cfa-button">{{cfaLabel}}</a>
{{#learnMoreUrl}}
<a id="learnMore-{{name}}" href="{{learnMoreUrl}}" class="button-link btn-white learnMoreButton cfa-button">Learn more</a>
{{/learnMoreUrl}}
</p>
{{#footnote}}<sub>(*) {{.}}</sub>{{/footnote}}

View File

@@ -1,23 +1,91 @@
<div id="plans-section" class="env-{{env}}">
<style>
.toggle-container {
display: flex;
border: 2px solid black;
border-radius: 100px;
overflow: hidden;
cursor: pointer;
margin-top: 20px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.toggle-option {
flex: 1;
padding: 10px 20px;
text-align: center;
transition: background 0.3s, color 0.3s;
user-select: none;
white-space: nowrap;
}
.active {
background: black;
color: white;
}
.inactive {
background: white;
color: black;
}
@media (max-width: 480px) {
.toggle-container {
flex-direction: column;
width: 100%;
border-radius: 10px;
}
}
</style>
<div class="container">
<div class="row">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Cloud <span class="frame-bg frame-bg-yellow">plans</span>
Our synchronisation and sharing <span class="frame-bg frame-bg-yellow">solutions</span>
</h1>
<p translate class="text-center sub-title">
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
Synchronise and share your notes with our range of plans.
</p>
</div>
</div>
<div class="toggle-container" id="toggle">
<div class="toggle-option active toggle-button-managed">Managed hosting</div>
<div class="toggle-option inactive toggle-button-self">Self-hosting</div>
</div>
<noscript>
<div class="alert alert-danger alert-env-dev" role="alert" style='text-align: center; margin-top: 10px;'>
To use this page please enable JavaScript!
</div>
</noscript>
<div style="display: flex; justify-content: center; margin-top: 1.2em">
<div class="row hosting-type-managed">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Cloud
</h1>
<p translate class="text-center sub-title">
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
</p>
</div>
</div>
<div class="row hosting-type-self">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Server Business
</h1>
<p translate class="text-center sub-title">
Joplin Server Business is a synchronisation server that you can install on your own infrastructure, so that your data remains private and secure within your business.
</p>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.2em" class="hosting-type-managed">
<div class="form-check form-check-inline">
<input id="pay-monthly-radio" class="form-check-input" type="radio" name="pay-radio" checked value="monthly">
<label translate style="font-weight: bold" class="form-check-label" for="pay-monthly-radio">
@@ -46,7 +114,11 @@
{{> plan}}
{{/plans.teams}}
<p translate class="joplin-cloud-login-info">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
{{#plans.joplinServerBusiness}}
{{> plan}}
{{/plans.joplinServerBusiness}}
<p translate class="joplin-cloud-login-info hosting-type-managed">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
</div>
<div class="row">
@@ -148,4 +220,30 @@
});
});
</script>
<script>
const setHostingType = (type) => {
const other = type === 'managed' ? 'self' : 'managed';
$('.toggle-button-' + type).addClass('active');
$('.toggle-button-' + type).removeClass('inactive');
$('.toggle-button-' + other).addClass('inactive');
$('.toggle-button-' + other).removeClass('active');
$('.hosting-type-' + type).show();
$('.hosting-type-' + other).hide();
}
$('.toggle-button-managed').click((event) => {
event.preventDefault();
setHostingType('managed');
});
$('.toggle-button-self').click((event) => {
event.preventDefault();
setHostingType('self');
});
setHostingType('managed');
</script>
</div>

View File

@@ -17,7 +17,6 @@ RUN corepack enable
WORKDIR /build
COPY .yarn/plugins ./.yarn/plugins
COPY .yarn/releases ./.yarn/releases
COPY .yarn/patches ./.yarn/patches
COPY package.json .

51
Dockerfile.transcribe Normal file
View File

@@ -0,0 +1,51 @@
FROM node:18-bullseye
RUN apt-get update \
&& apt-get install -y \
ca-certificates curl \
python3 tini
## install docker
RUN install -m 0755 -d /etc/apt/keyrings
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
RUN chmod a+r /etc/apt/keyrings/docker.asc
RUN echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo bullseye) stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
RUN apt-get update \
&& apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
RUN corepack enable
WORKDIR /app
COPY .yarn/plugins ./.yarn/plugins
COPY .yarn/releases ./.yarn/releases
COPY .yarn/patches ./.yarn/patches
COPY package.json .
COPY .yarnrc.yml .
COPY yarn.lock .
COPY gulpfile.js .
COPY tsconfig.json .
COPY packages/lib ./packages/lib
COPY packages/utils ./packages/utils
COPY packages/tools ./packages/tools
COPY packages/renderer ./packages/renderer
COPY packages/htmlpack ./packages/htmlpack
COPY packages/transcribe ./packages/transcribe
# We don't want to build onenote-converter since it is not used by the server
RUN sed --in-place '/onenote-converter/d' ./packages/lib/package.json
RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \
&& yarn cache clean \
&& rm -rf .yarn/berry
WORKDIR /app/packages/transcribe
# Start the Node.js application
CMD ["yarn", "start"]

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://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://casinoreviews.net"><img title="Casino Reviews" width="256" src="https://joplinapp.org/images/sponsors/CasinoReviews.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://realgambling.ca/"><img title="RealGambling.ca" width="256" src="https://joplinapp.org/images/sponsors/RealGambling.png" alt="RealGambling.ca"/></a> <a href="https://essaypro.com/"><img title="write an essay online with EssayPro" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="write an essay online with EssayPro"/></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://www.reddit.com/r/tiktokRise/"><img title="Tiktok Rise" width="256" src="https://joplinapp.org/images/sponsors/TiktokRise.jpg" alt="Tiktok Rise"/></a> <a href="https://essaywriter.pro"><img title="write my essay services by EssayWriter" width="256" src="https://joplinapp.org/images/sponsors/EssayWriterPro.png" alt="write my essay services by EssayWriter"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://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://realgambling.ca/"><img title="RealGambling.ca" width="256" src="https://joplinapp.org/images/sponsors/RealGambling.png" alt="RealGambling.ca"/></a> <a href="https://essaypro.com/"><img title="write an essay online with EssayPro" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="write an essay online with EssayPro"/></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://essaywriter.pro"><img title="write my essay services by EssayWriter" width="256" src="https://joplinapp.org/images/sponsors/EssayWriterPro.png" alt="write my essay services by EssayWriter"/></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://homeworkguy.org/someone-to-take-my-online-class"><img title="someone to take my online class" width="256" src="https://joplinapp.org/images/sponsors/HomeworkGuy.png" alt="someone to take my online class"/></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>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -9,24 +9,20 @@
"vips.dev": {
"platforms": ["aarch64-darwin"],
},
"nodejs": "latest",
"nodejs": "23.8.0",
"pkg-config": "latest",
"pixman": "latest",
"cairo.dev": "",
"pango.dev": "",
"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
"version": "",
"platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"python": "latest",
"python": "3.13.1",
"bat": "latest",
"electron": {
"version": "latest",
"excluded_platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"git": "latest",
"giflib": "latest",
"git": "2.47.2",
},
"shell": {
"init_hook": [

View File

@@ -21,7 +21,7 @@ version: '2'
services:
postgresql-master:
image: 'bitnami/postgresql:16.3.0'
image: 'bitnami/postgresql:17.3.0'
ports:
- '5432:5432'
environment:
@@ -38,7 +38,7 @@ services:
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
postgresql-slave:
image: 'bitnami/postgresql:16.3.0'
image: 'bitnami/postgresql:17.3.0'
ports:
- '5433:5432'
depends_on:

View File

@@ -339,6 +339,7 @@
"packages/renderer/MdToHtml/rules/fence.js": true,
"packages/renderer/MdToHtml/rules/mermaid.js": true,
"packages/renderer/MdToHtml/rules/sanitize_html.js": true,
"packages/transcribe/dist": true,
"packages/server/db-*.sqlite": true,
"packages/server/dist/": true,
"packages/utils/dist/": true,

View File

@@ -10,14 +10,14 @@
},
"engines": {
"node": ">=18",
"yarn": "3.8.3"
"yarn": "4.9.2"
},
"scripts": {
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
"buildScriptIndexes": "node packages/tools/gulp/tasks/buildScriptIndexesRun.js",
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn tsc",
"buildParallel": "yarn workspaces foreach --worktree --verbose --interlaced --parallel --jobs 2 --topological-dev run build && yarn tsc",
"buildPluginDoc": "cd packages/generate-plugin-doc && yarn buildPluginDoc_",
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn tsc",
"buildSequential": "yarn workspaces foreach --worktree --verbose --interlaced --topological-dev run build && yarn tsc",
"buildServerDocker": "node packages/tools/buildServerDocker.js",
"buildSettingJsonSchema": "yarn workspace joplin start settingschema ../../../joplin-website/docs/schema/settings.json",
"buildTranslations": "node packages/tools/build-translation.js",
@@ -38,6 +38,7 @@
"linter-precommit": "eslint --resolve-plugins-relative-to . --fix --ext .js --ext .jsx --ext .ts --ext .tsx",
"linter": "eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"packageJsonLint": "node ./packages/tools/packageJsonLint.js",
"syncFuzzer": "node ./packages/tools/fuzzer/sync-fuzzer.js",
"postinstall": "husky && gulp build",
"postPreReleasesToForum": "node ./packages/tools/postPreReleasesToForum",
"publishAll": "git pull && yarn buildParallel && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
@@ -53,19 +54,19 @@
"setupNewRelease": "node ./packages/tools/setupNewRelease",
"spellcheck": "node packages/tools/spellcheck.js",
"tagServerLatest": "node packages/tools/tagServerLatest.js",
"test-ci": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test-ci",
"test": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test",
"tsc": "yarn workspaces foreach --parallel --verbose --interlaced run tsc",
"test-ci": "yarn workspaces foreach --worktree --parallel --verbose --interlaced --jobs 2 run test-ci",
"test": "yarn workspaces foreach --worktree --parallel --verbose --interlaced --jobs 2 run test",
"tsc": "yarn workspaces foreach --worktree --parallel --verbose --interlaced run tsc",
"updateIgnored": "node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js",
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
"updateNews": "node ./packages/tools/website/updateNews",
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
"validateFilenames": "node ./packages/tools/validateFilenames.js",
"watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch",
"watch": "yarn workspaces foreach --worktree --parallel --verbose --interlaced --jobs 999 run watch",
"watchWebsite": "nodemon --delay 1 --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --watch packages/doc-builder/build --ext md,ts,js,mustache,css,tsx,gif,png,svg --exec \"node packages/tools/website/build.js && http-server --port 8077 ../joplin-website/docs -a localhost\""
},
"devDependencies": {
"@crowdin/cli": "3",
"@crowdin/cli": "4",
"@joplin/utils": "~2.12",
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "6.21.0",
@@ -79,13 +80,13 @@
"eslint-plugin-react": "7.34.3",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "10.4.5",
"glob": "11.0.1",
"gulp": "4.0.2",
"husky": "9.1.7",
"lerna": "3.22.1",
"lint-staged": "15.2.8",
"madge": "7.0.0",
"npm-package-json-lint": "7.1.0",
"lint-staged": "15.5.0",
"madge": "8.0.0",
"npm-package-json-lint": "8.0.0",
"typescript": "5.4.5"
},
"dependencies": {
@@ -93,9 +94,9 @@
"eslint-plugin-github": "4.10.2",
"http-server": "14.1.1",
"node-gyp": "9.4.1",
"nodemon": "3.1.7"
"nodemon": "3.1.9"
},
"packageManager": "yarn@3.8.3",
"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",
@@ -104,12 +105,16 @@
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"chokidar@^2.0.0": "3.5.3",
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch",
"rn-fetch-blob@0.12.0": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch",
"app-builder-lib@26.0.0-alpha.7": "patch:app-builder-lib@npm%3A26.0.0-alpha.7#./.yarn/patches/app-builder-lib-npm-26.0.0-alpha.7-e1b3dca119.patch",
"app-builder-lib@24.13.3": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch",
"react-native-sqlite-storage@6.0.1": "patch:react-native-sqlite-storage@npm%3A6.0.1#./.yarn/patches/react-native-sqlite-storage-npm-6.0.1-8369d747bd.patch",
"react-native-paper@5.13.1": "patch:react-native-paper@npm%3A5.13.1#./.yarn/patches/react-native-paper-npm-5.13.1-f153e542e2.patch",
"react-native-popup-menu@0.16.1": "patch:react-native-popup-menu@npm%3A0.16.1#./.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch"
"react-native-popup-menu@0.17.0": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch",
"react-native@0.79.2": "patch:react-native@npm%3A0.79.2#./.yarn/patches/react-native-npm-0.79.2-9db13eddfe.patch",
"pdfjs-dist@2.16.105": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"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/"
}
}

View File

@@ -6,9 +6,9 @@ import Folder from '@joplin/lib/models/Folder';
import BaseItem from '@joplin/lib/models/BaseItem';
import Note from '@joplin/lib/models/Note';
import Tag from '@joplin/lib/models/Tag';
import Setting from '@joplin/lib/models/Setting';
import Setting, { Env } from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry.js';
import { fileExtension } from '@joplin/lib/path-utils';
import { dirname, fileExtension } from '@joplin/lib/path-utils';
import { splitCommandString } from '@joplin/utils';
import { _ } from '@joplin/lib/locale';
import { pathExists, readFile, readdirSync } from 'fs-extra';
@@ -16,6 +16,7 @@ import RevisionService from '@joplin/lib/services/RevisionService';
import shim from '@joplin/lib/shim';
import setupCommand from './setupCommand';
import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
import initializeCommandService from './utils/initializeCommandService';
const { cliUtils } = require('./cli-utils.js');
const Cache = require('@joplin/lib/Cache');
const { splitCommandBatch } = require('@joplin/lib/string-utils');
@@ -76,6 +77,12 @@ class Application extends BaseApplication {
}
}
public async loadItemOrFail(type: ModelType | 'folderOrNote', pattern: string) {
const output = await this.loadItem(type, pattern);
if (!output) throw new Error(_('Cannot find "%s".', pattern));
return output;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async loadItems(type: ModelType | 'folderOrNote', pattern: string, options: any = null): Promise<(FolderEntity | NoteEntity)[]> {
if (type === 'folderOrNote') {
@@ -397,8 +404,12 @@ class Application extends BaseApplication {
}
public async start(argv: string[]) {
const keychainEnabled = this.checkIfKeychainEnabled(argv);
// TODO: Currently, `pluginAssetDir` needs to be set differently for each platform and requires
// a call to Setting.setConstant. Ideally, this would be done in a way that requires users to
// set this constant on startup.
Setting.setConstant('pluginAssetDir', `${dirname(require.resolve('@joplin/renderer'))}/assets`);
const keychainEnabled = this.checkIfKeychainEnabled(argv);
argv = await super.start(argv, { keychainEnabled });
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -408,8 +419,15 @@ class Application extends BaseApplication {
this.initRedux();
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
if (!shim.sharpEnabled()) this.logger().warn('Sharp is disabled - certain image-related features will not be available');
initializeCommandService(this.store(), Setting.value('env') === Env.Dev);
// If we have some arguments left at this point, it's a command
// so execute it.
if (argv.length) {
@@ -448,11 +466,6 @@ class Application extends BaseApplication {
this.gui_.setLogger(this.logger());
await this.gui_.start();
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
await refreshFolders((action: any) => this.store().dispatch(action), '');

View File

@@ -13,7 +13,7 @@ describe('command-done', () => {
});
it('should make a note as "done"', async () => {
const note = await Note.save({ title: 'hello', is_todo: 1, todo_completed: 0 });
const note = await Note.save({ title: 'hello', is_todo: 1, todo_completed: 0, parent_id: '' });
const command = setupCommandForTesting(Command);

View File

@@ -26,6 +26,7 @@ class Command extends BaseCommand {
['-v, --verbose', 'More verbose output for the `target-status` command'],
['-o, --output <directory>', 'Output directory'],
['--retry-failed-items', 'Applies to `decrypt` command - retries decrypting items that previously could not be decrypted.'],
['-f, --force', 'Do not ask for input on failure'],
];
}
@@ -67,7 +68,7 @@ class Command extends BaseCommand {
this.stdout(line.join('\n'));
break;
} catch (error) {
if (error.code === 'masterKeyNotLoaded') {
if (error.code === 'masterKeyNotLoaded' && !args.options.force) {
const ok = await askForMasterKey(error);
if (!ok) return;
continue;

View File

@@ -26,8 +26,7 @@ class Command extends BaseCommand {
const pattern = args['notebook'];
const force = args.options && args.options.force === true;
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
const folder = await app().loadItemOrFail(BaseModel.TYPE_FOLDER, pattern);
const permanent = args.options?.permanent === true || !!folder.deleted_time;
const ellipsizedFolderTitle = substrWithEllipsis(folder.title, 0, 32);

View File

@@ -0,0 +1,179 @@
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import mockShareService, { ApiMock } from '@joplin/lib/testing/share/mockShareService';
import { setupCommandForTesting, setupApplication } from './utils/testUtils';
import Folder from '@joplin/lib/models/Folder';
import ShareService from '@joplin/lib/services/share/ShareService';
import BaseItem from '@joplin/lib/models/BaseItem';
import { ModelType } from '@joplin/lib/BaseModel';
import { ShareInvitation, ShareUserStatus, StateShare } from '@joplin/lib/services/share/reducer';
import app from './app';
const Command = require('./command-share');
const setUpCommand = () => {
const output: string[] = [];
const stdout = (content: string) => {
output.push(...content.split('\n'));
};
const command = setupCommandForTesting(Command, stdout);
return { command, output };
};
const shareId = 'test-id';
const defaultFolderShare: StateShare = {
id: shareId,
type: ModelType.Folder,
folder_id: 'some-folder-id-here',
note_id: undefined,
master_key_id: undefined,
user: {
full_name: 'Test user',
email: 'test@localhost',
id: 'some-user-id',
},
};
const mockShareServiceForFolderSharing = (eventHandlerOverrides: Partial<ApiMock>&{ onExec?: undefined }) => {
const invitations: ShareInvitation[] = [];
mockShareService({
getShareInvitations: async () => ({
items: invitations,
}),
getShares: async () => ({ items: [defaultFolderShare] }),
getShareUsers: async (_id: string) => ({ items: [] }),
postShareUsers: async (_id, _body) => { },
postShares: async () => ({ id: shareId }),
...eventHandlerOverrides,
}, ShareService.instance(), app().store());
return {
addInvitation: (invitation: Partial<ShareInvitation>) => {
const defaultInvitation: ShareInvitation = {
share: defaultFolderShare,
id: 'some-invitation-id',
master_key: undefined,
status: ShareUserStatus.Waiting,
can_read: 1,
can_write: 1,
};
invitations.push({ ...defaultInvitation, ...invitation });
},
};
};
describe('command-share', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
BaseItem.shareService_ = ShareService.instance();
});
test('should allow adding a user to a share', async () => {
const folder = await Folder.save({ title: 'folder1' });
let lastShareUserUpdate: unknown|null = null;
mockShareServiceForFolderSharing({
getShares: async () => {
const isShared = !!lastShareUserUpdate;
if (isShared) {
return {
items: [{ ...defaultFolderShare, folder_id: folder.id }],
};
} else {
return { items: [] };
}
},
// Called when a new user is added to a share
postShareUsers: async (_id, body) => {
lastShareUserUpdate = body;
},
});
const { command } = setUpCommand();
// Should share read-write by default
await command.action({
'command': 'add',
'notebook': 'folder1',
'user': 'test@localhost',
options: {},
});
expect(lastShareUserUpdate).toMatchObject({
email: 'test@localhost',
can_write: 1,
can_read: 1,
});
// Should also support sharing as read only
await command.action({
'command': 'add',
'notebook': 'folder1',
'user': 'test2@localhost',
options: {
'read-only': true,
},
});
expect(lastShareUserUpdate).toMatchObject({
email: 'test2@localhost',
can_write: 0,
can_read: 1,
});
});
test.each([
{
label: 'should list a single pending invitation',
invitations: [{ id: 'test', status: ShareUserStatus.Waiting }],
expectedOutput: [
'Incoming shares:',
'\tWaiting: Notebook some-folder-id-here from test@localhost',
'All shared folders:',
'\tNone',
].join('\n'),
},
{
label: 'should list accepted invitations for non-existent folders with [None] as the folder title',
invitations: [
{ id: 'test2', status: ShareUserStatus.Accepted },
],
expectedOutput: [
'Incoming shares:',
'\tAccepted: Notebook [None] from test@localhost',
'All shared folders:',
'\tNone',
].join('\n'),
},
{
label: 'should not list rejected shares',
invitations: [
{ id: 'test3', status: ShareUserStatus.Rejected },
],
expectedOutput: [
'Incoming shares:',
'\tNone',
'All shared folders:',
'\tNone',
].join('\n'),
},
])('share invitations: $label', async ({ invitations, expectedOutput }) => {
const mock = mockShareServiceForFolderSharing({});
for (const invitation of invitations) {
mock.addInvitation(invitation);
}
await ShareService.instance().refreshShareInvitations();
const { command, output } = setUpCommand();
await command.action({
'command': 'list',
options: {},
});
expect(output.join('\n')).toBe(expectedOutput);
});
});

View File

@@ -0,0 +1,297 @@
import { _ } from '@joplin/lib/locale';
import BaseCommand from './base-command';
import app from './app';
import { reg } from '@joplin/lib/registry';
import Logger from '@joplin/utils/Logger';
import ShareService from '@joplin/lib/services/share/ShareService';
import { ModelType } from '@joplin/lib/BaseModel';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { ShareUserStatus } from '@joplin/lib/services/share/reducer';
import Folder from '@joplin/lib/models/Folder';
import invitationRespond from '@joplin/lib/services/share/invitationRespond';
import CommandService from '@joplin/lib/services/CommandService';
import { substrWithEllipsis } from '@joplin/lib/string-utils';
const logger = Logger.create('command-share');
type Args = {
command: string;
// eslint-disable-next-line id-denylist -- The "notebook" identifier comes from the UI.
notebook?: string;
user?: string;
options: {
'read-only'?: boolean;
json?: boolean;
force?: boolean;
};
};
const folderTitle = (folder: FolderEntity|null) => {
return folder ? substrWithEllipsis(folder.title, 0, 32) : _('[None]');
};
const getShareState = () => app().store().getState().shareService;
const getShareFromFolderId = (folderId: string) => {
const shareState = getShareState();
const allShares = shareState.shares;
const share = allShares.find(share => share.folder_id === folderId);
return share;
};
const getShareUsers = (folderId: string) => {
const share = getShareFromFolderId(folderId);
if (!share) {
throw new Error(`No share found for folder ${folderId}`);
}
return getShareState().shareUsers[share.id];
};
class Command extends BaseCommand {
public usage() {
return 'share <command> [notebook] [user]';
}
public description() {
return [
_('Shares or unshares the specified [notebook] with [user]. Requires Joplin Cloud or Joplin Server.'),
_('Commands: `add`, `remove`, `list`, `delete`, `accept`, `leave`, and `reject`.'),
].join('\n');
}
public options() {
return [
['--read-only', _('Don\'t allow the share recipient to write to the shared notebook. Valid only for the `add` subcommand.')],
['-f, --force', _('Do not ask for user confirmation.')],
['--json', _('Prefer JSON output.')],
];
}
public async action(args: Args) {
const commandShareAdd = async (folder: FolderEntity, email: string) => {
await reg.waitForSyncFinishedThenSync();
const share = await ShareService.instance().shareFolder(folder.id);
const permissions = {
can_read: 1,
can_write: args.options['read-only'] ? 0 : 1,
};
logger.debug('Sharing folder', folder.id, 'with', email, 'permissions=', permissions);
await ShareService.instance().addShareRecipient(share.id, share.master_key_id, email, permissions);
await ShareService.instance().refreshShares();
await ShareService.instance().refreshShareUsers(share.id);
await reg.waitForSyncFinishedThenSync();
};
const commandShareRemove = async (folder: FolderEntity, email: string) => {
await ShareService.instance().refreshShares();
const share = getShareFromFolderId(folder.id);
if (!share) {
throw new Error(`No share found for folder ${folder.id}`);
}
await ShareService.instance().refreshShareUsers(share.id);
const shareUsers = getShareUsers(folder.id);
if (!shareUsers) {
throw new Error(`No share found for folder ${folder.id}`);
}
const targetUser = shareUsers.find(user => user.user?.email === email);
if (!targetUser) {
throw new Error(`No recipient found with email ${email}`);
}
await ShareService.instance().deleteShareRecipient(targetUser.id);
this.stdout(_('Removed %s from share.', targetUser.user.email));
};
const commandShareList = async () => {
let folder = null;
if (args.notebook) {
folder = await app().loadItemOrFail(ModelType.Folder, args.notebook);
}
await ShareService.instance().maintenance();
if (folder) {
const share = getShareFromFolderId(folder.id);
await ShareService.instance().refreshShareUsers(share.id);
const shareUsers = getShareUsers(folder.id);
const output = {
folderTitle: folderTitle(folder),
sharedWith: (shareUsers ?? []).map(user => ({
email: user.user.email,
readOnly: user.can_read && !user.can_write,
})),
};
if (args.options.json) {
this.stdout(JSON.stringify(output));
} else {
this.stdout(_('Folder "%s" is shared with:', output.folderTitle));
for (const user of output.sharedWith) {
this.stdout(`\t${user.email}\t${user.readOnly ? _('(Read-only)') : ''}`);
}
}
} else {
const shareState = getShareState();
const output = {
invitations: shareState.shareInvitations.map(invitation => ({
accepted: invitation.status === ShareUserStatus.Accepted,
waiting: invitation.status === ShareUserStatus.Waiting,
rejected: invitation.status === ShareUserStatus.Rejected,
folderId: invitation.share.folder_id,
fromUser: {
email: invitation.share.user?.email,
},
})),
shares: shareState.shares.map(share => ({
isFolder: !!share.folder_id,
isNote: !!share.note_id,
itemId: share.folder_id ?? share.note_id,
fromUser: {
email: share.user?.email,
},
})),
};
if (args.options.json) {
this.stdout(JSON.stringify(output));
} else {
this.stdout(_('Incoming shares:'));
let loggedInvitation = false;
for (const invitation of output.invitations) {
let message;
if (invitation.waiting) {
message = _('Waiting: Notebook %s from %s', invitation.folderId, invitation.fromUser.email);
}
if (invitation.accepted) {
const folder = await Folder.load(invitation.folderId);
message = _('Accepted: Notebook %s from %s', folderTitle(folder), invitation.fromUser.email);
}
if (message) {
this.stdout(`\t${message}`);
loggedInvitation = true;
}
}
if (!loggedInvitation) {
this.stdout(`\t${_('None')}`);
}
this.stdout(_('All shared folders:'));
if (output.shares.length) {
for (const share of output.shares) {
let title;
if (share.isFolder) {
title = folderTitle(await Folder.load(share.itemId));
} else {
title = share.itemId;
}
if (share.fromUser?.email) {
this.stdout(`\t${_('%s from %s', title, share.fromUser?.email)}`);
} else {
this.stdout(`\t${title} - ${share.itemId}`);
}
}
} else {
this.stdout(`\t${_('None')}`);
}
}
}
};
const commandShareAcceptOrReject = async (folderId: string, accept: boolean) => {
await ShareService.instance().maintenance();
const shareState = getShareState();
const invitations = shareState.shareInvitations.filter(invitation => {
return invitation.share.folder_id === folderId && invitation.status === ShareUserStatus.Waiting;
});
if (invitations.length === 0) throw new Error('No such invitation found');
// If there are multiple invitations for the same folder, stop early to avoid
// accepting the wrong invitation.
if (invitations.length > 1) throw new Error('Multiple invitations found with the same ID');
const invitation = invitations[0];
this.stdout(accept ? _('Accepting share...') : _('Rejecting share...'));
await invitationRespond(invitation.id, invitation.share.folder_id, invitation.master_key, accept);
};
const commandShareAccept = (folderId: string) => (
commandShareAcceptOrReject(folderId, true)
);
const commandShareReject = (folderId: string) => (
commandShareAcceptOrReject(folderId, false)
);
const commandShareDelete = async (folder: FolderEntity) => {
const force = args.options.force;
const ok = force ? true : await this.prompt(
_('Unshare notebook "%s"? This may cause other users to lose access to the notebook.', folderTitle(folder)),
{ booleanAnswerDefault: 'n' },
);
if (!ok) return;
logger.info('Unsharing folder', folder.id);
await ShareService.instance().unshareFolder(folder.id);
await reg.scheduleSync();
};
if (args.command === 'add' || args.command === 'remove' || args.command === 'delete') {
if (!args.notebook) throw new Error('[notebook] is required');
const folder = await app().loadItemOrFail(ModelType.Folder, args.notebook);
if (args.command === 'delete') {
return commandShareDelete(folder);
} else {
if (!args.user) throw new Error('[user] is required');
const email = args.user;
if (args.command === 'add') {
return commandShareAdd(folder, email);
} else if (args.command === 'remove') {
return commandShareRemove(folder, email);
}
}
}
if (args.command === 'leave') {
const folder = args.notebook ? await app().loadItemOrFail(ModelType.Folder, args.notebook) : null;
await ShareService.instance().maintenance();
return CommandService.instance().execute(
'leaveSharedFolder', folder?.id, { force: args.options.force },
);
}
if (args.command === 'list') {
return commandShareList();
}
if (args.command === 'accept') {
return commandShareAccept(args.notebook);
}
if (args.command === 'reject') {
return commandShareReject(args.notebook);
}
throw new Error(`Unknown subcommand: ${args.command}`);
}
}
module.exports = Command;

View File

@@ -17,6 +17,7 @@ import { pathExists, writeFile } from 'fs-extra';
import { checkIfLoginWasSuccessful, generateApplicationConfirmUrl } from '@joplin/lib/services/joplinCloudUtils';
import Logger from '@joplin/utils/Logger';
import { uuidgen } from '@joplin/lib/uuid';
import ShareService from '@joplin/lib/services/share/ShareService';
const logger = Logger.create('command-sync');
@@ -230,6 +231,10 @@ class Command extends BaseCommand {
return cleanUp();
}
// Refresh share invitations -- if running without a GUI, some of the
// maintenance tasks may otherwise be skipped.
await ShareService.instance().maintenance();
this.stdout(_('Starting synchronisation...'));
const contextKey = `sync.${this.syncTargetId_}.context`;

View File

@@ -4,7 +4,7 @@ import Note from '@joplin/lib/models/Note';
import uuid from '@joplin/lib/uuid';
import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
import { readCredentialFile } from '@joplin/lib/utils/credentialFiles';
import JoplinServerApi from '@joplin/lib/JoplinServerApi';
import JoplinServerApi, { Session } from '@joplin/lib/JoplinServerApi';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function randomElement(array: any[]): any {
@@ -107,6 +107,7 @@ class Command extends BaseCommand {
userContentBaseUrl: () => joplinServerAuth.userContentBaseUrl,
username: () => joplinServerAuth.email,
password: () => joplinServerAuth.password,
session: (): Session => null,
});
const apiPut = async () => {

View File

@@ -22,7 +22,7 @@ const Setting = require('@joplin/lib/models/Setting').default;
const Revision = require('@joplin/lib/models/Revision').default;
const Logger = require('@joplin/utils/Logger').default;
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const shimInitCli = require('./utils/shimInitCli').default;
const shim = require('@joplin/lib/shim').default;
const { _ } = require('@joplin/lib/locale');
const FileApiDriverLocal = require('@joplin/lib/file-api-driver-local').default;
@@ -73,7 +73,7 @@ function appVersion() {
return p.version;
}
shimInit({ sharp, keytar, appVersion, nodeSqlite });
shimInitCli({ sharp, keytar, appVersion, nodeSqlite });
const logger = new Logger();
Logger.initializeGlobalLogger(logger);

View File

@@ -0,0 +1,14 @@
import CommandService from '@joplin/lib/services/CommandService';
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
import libCommands from '@joplin/lib/commands/index';
import { State } from '@joplin/lib/reducer';
import { Store } from 'redux';
export default function initializeCommandService(store: Store<State>, devMode: boolean) {
CommandService.instance().initialize(store, devMode, stateToWhenClauseContext);
for (const command of libCommands) {
CommandService.instance().registerDeclaration(command.declaration);
CommandService.instance().registerRuntime(command.declaration.name, command.runtime());
}
}

View File

@@ -0,0 +1,32 @@
import shim, { ShowMessageBoxOptions } from '@joplin/lib/shim';
import type { ShimInitOptions } from '@joplin/lib/shim-init-node';
import app from '../app';
import { _ } from '@joplin/lib/locale';
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const shimInitCli = (options: ShimInitOptions) => {
shimInit(options);
shim.showMessageBox = async (message: string, options: ShowMessageBoxOptions) => {
const gui = app()?.gui();
let answers = options.buttons ?? [_('Ok'), _('Cancel')];
if (options.type === 'error' || options.type === 'info') {
answers = [];
}
message += answers.length ? `(${answers.join(', ')})` : '';
const answer = await gui.prompt(options.title ?? '', `${message} `, { answers });
if (answers.includes(answer)) {
return answers.indexOf(answer);
} else if (answer) {
return answers.findIndex(a => a.startsWith(answer));
}
return -1;
};
};
export default shimInitCli;

View File

@@ -15,4 +15,7 @@ export const setupApplication = async () => {
// such notebook.
await Folder.save({ title: 'default' });
await app().refreshCurrentFolder();
// Some tests also need access to the Redux store
app().initRedux();
};

View File

@@ -35,15 +35,15 @@
],
"owner": "Laurent Cozic"
},
"version": "3.3.1",
"version": "3.4.0",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "~3.3",
"@joplin/renderer": "~3.3",
"@joplin/utils": "~3.3",
"@joplin/lib": "~3.4",
"@joplin/renderer": "~3.4",
"@joplin/utils": "~3.4",
"aws-sdk": "2.1340.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",
@@ -55,24 +55,25 @@
"node-rsa": "1.1.1",
"open": "8.4.2",
"proper-lockfile": "4.1.2",
"redux": "4.2.1",
"server-destroy": "1.0.1",
"sharp": "0.33.4",
"sharp": "0.33.5",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
"strip-ansi": "6.0.1",
"tcp-port-used": "1.0.2",
"terminal-kit": "3.1.1",
"terminal-kit": "3.1.2",
"tkwidgets": "0.5.27",
"url-parse": "1.5.10",
"word-wrap": "1.2.5",
"yargs-parser": "21.1.1"
},
"devDependencies": {
"@joplin/tools": "~3.3",
"@joplin/tools": "~3.4",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.12",
"@types/node": "18.19.67",
"@types/node": "18.19.86",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",

View File

@@ -0,0 +1 @@
<p><span style="/* Comment */ text-decoration: underline;">Test</span>. In the past, <span style="font-size: auto;/* Test! */">comments</span> in CSS have caused issues.</p>

View File

@@ -0,0 +1 @@
<ins>Test</ins>. In the past, comments in CSS have caused issues.

View File

@@ -0,0 +1 @@
<p>Some **format** characters $need$ to be `escaped`, if the characters were included directly in HTML.</p>

View File

@@ -0,0 +1 @@
Some \*\*format\*\* characters \$need\$ to be \`escaped\`, if the characters were included directly in HTML.

View File

@@ -351,7 +351,7 @@ describe('services_PluginService', () => {
joplin.plugins.register({
onStart: async function() {
const dataDir = await joplin.plugins.dataDir();
joplin.data.post(['folders'], null, { title: JSON.stringify(dataDir) });
await joplin.data.post(['folders'], null, { title: JSON.stringify(dataDir) });
},
});
`);

View File

@@ -0,0 +1,4 @@
<body>
<a name="519"/>
<h1>Second note</h1>
</body>

View File

@@ -0,0 +1,2 @@
<img src="..\\..\\photo.jpg" />
<a href="..\\..\\sample.txt">Sample</a>

View File

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

View File

@@ -0,0 +1,19 @@
module.exports = {
'overrides': [
{
files: ['**/*.tsx', '**/*.js', '**/*.ts'],
rules: {
'no-restricted-globals': ['error',
...['alert', 'confirm', 'prompt'].map(alertLikeFunction => ({
'name': alertLikeFunction,
'message': [
'Avoid using alert()/confirm()/prompt() in the desktop app -- they break keyboard input on some systems.',
'Prefer shim.showMessageBox and shim.showConfirmationDialog.',
'See https://github.com/electron/electron/issues/19977.',
].join(' '),
})),
],
},
},
],
};

View File

@@ -25,3 +25,7 @@ build/7zip/7za
build/7zip/7za.exe
sentry.properties
downloads/
# Bundler output
*.js.meta.json
*.bundle.js

View File

@@ -6,7 +6,7 @@ const shim: typeof ShimType = require('@joplin/lib/shim').default;
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import { FileLocker } from '@joplin/utils/fs';
import { IpcMessageHandler, IpcServer, Message, newHttpError, sendMessage, SendMessageOptions, startServer, stopServer } from '@joplin/utils/ipc';
import { BrowserWindow, Tray, WebContents, screen, App } from 'electron';
import { BrowserWindow, Tray, WebContents, screen, App, nativeTheme } from 'electron';
import bridge from './bridge';
const url = require('url');
const path = require('path');
@@ -75,6 +75,7 @@ export default class ElectronAppWrapper {
private ipcStartPort_ = 2658;
private ipcLogger_: Logger;
private ipcLoggerFilePath_: string;
public constructor(electronApp: App, { env, profilePath, isDebugMode, initialCallbackUrl, isEndToEndTesting }: Options) {
this.electronApp_ = electronApp;
@@ -90,8 +91,9 @@ export default class ElectronAppWrapper {
// calls, either because it hasn't been set or other issue. So we set one here specifically
// for this.
this.ipcLogger_ = new Logger();
this.ipcLoggerFilePath_ = `${profilePath}/log-cross-app-ipc.txt`;
this.ipcLogger_.addTarget(TargetType.File, {
path: `${profilePath}/log-cross-app-ipc.txt`,
path: this.ipcLoggerFilePath_,
});
}
@@ -115,6 +117,14 @@ export default class ElectronAppWrapper {
return BrowserWindow.getFocusedWindow() ?? this.win_;
}
public ipcServerStarted() {
return !!this.ipcServer_;
}
public ipcLoggerFilePath() {
return this.ipcLoggerFilePath_;
}
public windowById(joplinId: string) {
if (joplinId === defaultWindowId) {
return this.mainWindow();
@@ -127,6 +137,24 @@ export default class ElectronAppWrapper {
return null;
}
private windowIdFromWebContents(webContents: WebContents): SecondaryWindowId|null {
const browserWindow = BrowserWindow.fromWebContents(webContents);
// Convert from electron IDs to Joplin IDs.
const targetElectronId = browserWindow.id;
if (this.win_?.id === targetElectronId) {
return 'default';
}
for (const [joplinId, { electronId }] of this.secondaryWindows_) {
if (electronId === targetElectronId) {
return joplinId;
}
}
return null;
}
public allAppWindows() {
const allWindowIds = [...this.secondaryWindows_.keys(), defaultWindowId];
return allWindowIds.map(id => this.windowById(id));
@@ -205,7 +233,10 @@ export default class ElectronAppWrapper {
height: windowState.height,
minWidth: 100,
minHeight: 100,
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
// A backgroundColor is needed to enable sub-pixel rendering.
// Based on https://www.electronjs.org/docs/latest/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do,
// this needs to be a non-transparent color:
backgroundColor: nativeTheme.shouldUseDarkColors ? '#333' : '#fff',
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
@@ -312,6 +343,14 @@ export default class ElectronAppWrapper {
}, 1000);
}
const sendWindowFocused = (focusedWebContents: WebContents) => {
const joplinId = this.windowIdFromWebContents(focusedWebContents);
if (joplinId !== null) {
this.win_.webContents.send('window-focused', joplinId);
}
};
const addWindowEventHandlers = (webContents: WebContents) => {
// will-frame-navigate is fired by clicking on a link within the BrowserWindow.
webContents.on('will-frame-navigate', event => {
@@ -344,6 +383,11 @@ export default class ElectronAppWrapper {
webContents.on('did-create-window', (event) => {
addWindowEventHandlers(event.webContents);
});
const onFocus = () => {
sendWindowFocused(webContents);
};
webContents.on('focus', onFocus);
};
addWindowEventHandlers(this.win_.webContents);
@@ -415,6 +459,10 @@ export default class ElectronAppWrapper {
this.win_.close();
}
});
if (window.isFocused()) {
sendWindowFocused(window.webContents);
}
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -582,14 +630,18 @@ export default class ElectronAppWrapper {
if (port === null) port = this.ipcStartPort_;
return await sendMessage(port, {
...message,
sourcePort: this.ipcServer_.port,
secretKey: this.ipcServer_.secretKey,
}, {
logger: this.ipcLogger_,
...options,
});
if (this.ipcServer_) {
return await sendMessage(port, {
...message,
sourcePort: this.ipcServer_.port,
secretKey: this.ipcServer_.secretKey,
}, {
logger: this.ipcLogger_,
...options,
});
} else {
return [];
}
}
public async ensureSingleInstance() {
@@ -675,32 +727,39 @@ export default class ElectronAppWrapper {
this.ipcLogger_.info('Starting server using secret key:', secretKeyFilePath);
this.ipcServer_ = await startServer(this.ipcStartPort_, secretKeyFilePath, async (message) => {
if (messageHandlers[message.action]) {
this.ipcLogger_.info('Got message:', message);
return messageHandlers[message.action](message);
}
try {
this.ipcServer_ = await startServer(this.ipcStartPort_, secretKeyFilePath, async (message) => {
if (messageHandlers[message.action]) {
this.ipcLogger_.info('Got message:', message);
return messageHandlers[message.action](message);
}
throw newHttpError(404);
}, {
logger: this.ipcLogger_,
});
throw newHttpError(404);
}, {
logger: this.ipcLogger_,
});
} catch (error) {
this.ipcLogger_.error('Could not start server:', error);
this.ipcServer_ = null;
}
// First check that no other app is running from that profile folder
const gotAppLock = await this.profileLocker_.lock();
if (gotAppLock) return false;
const message: Message = {
action: 'onSecondInstance',
data: {
senderPort: this.ipcServer_.port,
profilePath: this.profilePath_,
argv: process.argv,
},
secretKey: this.ipcServer_.secretKey,
};
if (this.ipcServer_) {
const message: Message = {
action: 'onSecondInstance',
data: {
senderPort: this.ipcServer_.port,
profilePath: this.profilePath_,
argv: process.argv,
},
secretKey: this.ipcServer_.secretKey,
};
await this.sendCrossAppIpcMessage(message);
await this.sendCrossAppIpcMessage(message);
}
this.quit();
if (this.env() === 'dev') console.warn(`Closing the application because another instance is already running, or the previous instance was force-quit within the last ${Math.round(this.profileLocker_.options.interval / Second)} seconds.`);

View File

@@ -111,8 +111,12 @@ export default class InteropServiceHelper {
// 2024-01-31: Printing with webContents.print still
// fails on Linux (even if run in the main process).
// As such, we use window.print(), which seems to work.
//
// 2025-05-03: Windows and MacOS also need the window.print() workaround.
// See https://github.com/electron/electron/pull/46937.
if (shim.isLinux()) {
const applyWorkaround = true;
if (applyWorkaround) {
await win.webContents.executeJavaScript(`
// Blocks while the print dialog is open
window.print();

View File

@@ -4,7 +4,7 @@ import appReducer, { createAppDefaultState } from './app.reducer';
describe('app.reducer', () => {
it('should handle DIALOG_OPEN', async () => {
const state: AppState = createAppDefaultState({}, {});
const state: AppState = createAppDefaultState({});
let newState = appReducer(state, {
type: 'DIALOG_OPEN',
@@ -49,7 +49,7 @@ describe('app.reducer', () => {
it('showing a dialog in one window should hide dialogs with the same ID in background windows', () => {
const state: AppState = {
...createAppDefaultState({}, {}),
...createAppDefaultState({}),
backgroundWindows: {
testWindow: {
...createAppDefaultWindowState(),

View File

@@ -54,8 +54,6 @@ export interface AppState extends State, AppWindowState {
route: AppStateRoute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
navHistory: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
windowContentSize: any;
watchedNoteFiles: string[];
lastEditorScrollPercents: EditorScrollPercents;
focusedField: string;
@@ -81,7 +79,7 @@ export const createAppDefaultWindowState = (): AppWindowState => {
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
export function createAppDefaultState(windowContentSize: any, resourceEditWatcherDefaultState: any): AppState {
export function createAppDefaultState(resourceEditWatcherDefaultState: any): AppState {
return {
...defaultState,
...createAppDefaultWindowState(),
@@ -91,7 +89,6 @@ export function createAppDefaultState(windowContentSize: any, resourceEditWatche
props: {},
},
navHistory: [],
windowContentSize, // bridge().windowContentSize(),
watchedNoteFiles: [],
lastEditorScrollPercents: {},
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
@@ -166,12 +163,6 @@ export default function(state: AppState, action: any) {
}
break;
case 'WINDOW_CONTENT_SIZE_SET':
newState = { ...state };
newState.windowContentSize = action.size;
break;
case 'NOTE_VISIBLE_PANES_TOGGLE':
{

View File

@@ -65,10 +65,7 @@ const pluginClasses = [
require('./plugins/GotoAnything').default,
];
const appDefaultState = createAppDefaultState(
bridge().windowContentSize(),
resourceEditWatcherDefaultState,
);
const appDefaultState = createAppDefaultState(resourceEditWatcherDefaultState);
class Application extends BaseApplication {
@@ -149,6 +146,10 @@ class Application extends BaseApplication {
await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE');
}
if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'featureFlag.autoUpdaterServiceEnabled' || action.type === 'SETTING_UPDATE_ALL') {
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) this.setupAutoUpdaterService();
}
const result = await super.generalMiddleware(store, next, action);
const newState = store.getState();
@@ -331,18 +332,6 @@ class Application extends BaseApplication {
}, 500);
}
public crashDetectionHandler() {
// This handler conflicts with the single instance behaviour, so it's
// not used for now.
// https://discourse.joplinapp.org/t/pre-release-v2-8-is-now-available-updated-27-april/25158/56?u=laurent
if (!Setting.value('wasClosedSuccessfully')) {
const answer = confirm(_('The application did not close properly. Would you like to start in safe mode?'));
Setting.setValue('isSafeMode', !!answer);
}
Setting.setValue('wasClosedSuccessfully', false);
}
private async setupOcrService() {
if (Setting.value('ocr.clearLanguageDataCache')) {
Setting.setValue('ocr.clearLanguageDataCache', false);
@@ -386,6 +375,8 @@ class Application extends BaseApplication {
}
private setupAutoUpdaterService() {
this.logger().info('Setting up auto-updater service...');
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
bridge().electronApp().initializeAutoUpdaterService(
Logger.create('AutoUpdaterService'),
@@ -412,6 +403,14 @@ class Application extends BaseApplication {
});
}
private async setupIntegrationTestUtils() {
// Events used by Playwright tests to quickly change the value of a setting.
ipcRenderer.on('testing--setSetting', (_event, key, value) => {
this.logger().info('Updating setting using testing API: %s = %s', key, JSON.stringify(value));
Setting.setValue(key, value);
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async start(argv: string[], startOptions: StartOptions = null): Promise<any> {
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
@@ -420,6 +419,8 @@ class Application extends BaseApplication {
argv = await super.start(argv, startOptions);
await this.setupIntegrationTestUtils();
bridge().setLogFilePath(Logger.globalLogger.logFilePath());
await this.applySettingsSideEffects();
@@ -664,13 +665,15 @@ class Application extends BaseApplication {
Setting.setValue('linking.extraAllowedExtensions', newExtensions);
});
window.addEventListener('focus', () => {
ipcRenderer.on('window-focused', (_event, newWindowId) => {
const currentWindowId = this.store().getState().windowId;
this.dispatch({
type: 'WINDOW_FOCUS',
windowId: 'default',
lastWindowId: currentWindowId,
});
if (newWindowId !== currentWindowId) {
this.dispatch({
type: 'WINDOW_FOCUS',
windowId: newWindowId,
lastWindowId: currentWindowId,
});
}
});
await this.initPluginService();

View File

@@ -1,7 +1,7 @@
import ElectronAppWrapper from './ElectronAppWrapper';
import shim, { MessageBoxType } from '@joplin/lib/shim';
import { _, setLocale } from '@joplin/lib/locale';
import { BrowserWindow, nativeTheme, nativeImage, shell, dialog, MessageBoxSyncOptions, safeStorage } from 'electron';
import { BrowserWindow, nativeTheme, nativeImage, shell, dialog, MessageBoxSyncOptions, safeStorage, Menu, MenuItemConstructorOptions, MenuItem } from 'electron';
import { dirname, toSystemSlashes } from '@joplin/lib/path-utils';
import { fileUriToPath } from '@joplin/utils/url';
import { urlDecode } from '@joplin/lib/string-utils';
@@ -246,7 +246,7 @@ export class Bridge {
// version of electron-context-menu.
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
public setupContextMenu(_spellCheckerMenuItemsHandler: Function) {
require('electron-context-menu')({
require('./services/electron-context-menu')({
allWindows: [this.mainWindow()],
electronApp: this.electronApp(),
@@ -313,13 +313,6 @@ export class Bridge {
return new BrowserWindow(options);
}
// Note: This provides the size of the main window. Prefer CSS where possible.
public windowContentSize() {
if (!this.mainWindow()) return { width: 0, height: 0 };
const s = this.mainWindow().getContentSize();
return { width: s[0], height: s[1] };
}
public windowSetSize(width: number, height: number) {
if (!this.mainWindow()) return;
return this.mainWindow().setSize(width, height);
@@ -526,16 +519,30 @@ export class Bridge {
}
}
public async launchAltAppInstance(env: string) {
const cmd = this.appLaunchCommand(env, 'alt1');
private async launchAppInstanceById(env: string, altInstanceId: string) {
if (this.electronApp().ipcServerStarted()) {
const cmd = this.appLaunchCommand(env, altInstanceId);
await execCommand([cmd.execPath].concat(cmd.args), { detached: true });
} else {
const buttonIndex = this.showErrorMessageBox('Cannot launch another instance because IPC server could not start.', {
buttons: [
_('OK'),
_('Open log'),
],
});
await execCommand([cmd.execPath].concat(cmd.args), { detached: true });
if (buttonIndex === 1) {
void this.openItem(this.electronApp().ipcLoggerFilePath());
}
}
}
public async launchAltAppInstance(env: string) {
await this.launchAppInstanceById(env, 'alt1');
}
public async launchMainAppInstance(env: string) {
const cmd = this.appLaunchCommand(env, '');
await execCommand([cmd.execPath].concat(cmd.args), { detached: true });
await this.launchAppInstanceById(env, '');
}
public async restart() {
@@ -588,6 +595,11 @@ export class Bridge {
return nativeImage.createFromPath(path);
}
public menuPopupFromTemplate(template: ((MenuItemConstructorOptions) | (MenuItem))[]) {
const menu = Menu.buildFromTemplate(template);
return menu.popup({ window: this.mainWindow() });
}
public safeStorage = {
isEncryptionAvailable() {
return safeStorage.isEncryptionAvailable();

View File

@@ -0,0 +1,15 @@
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
import { clipboard } from 'electron';
export const declaration: CommandDeclaration = {
name: 'copyToClipboard',
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context, content: string) => {
if (!content || (typeof content !== 'string')) return;
clipboard.writeText(content);
},
};
};

View File

@@ -38,7 +38,7 @@ describe('exportDeletionLog', () => {
let state: AppState = undefined;
beforeAll(() => {
state = createAppDefaultState({}, {});
state = createAppDefaultState({});
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-09-18T12:00:00Z').getTime());
});

View File

@@ -1,5 +1,6 @@
// AUTO-GENERATED using `gulp buildScriptIndexes`
import * as copyDevCommand from './copyDevCommand';
import * as copyToClipboard from './copyToClipboard';
import * as editProfileConfig from './editProfileConfig';
import * as emptyTrash from './emptyTrash';
import * as exportDeletionLog from './exportDeletionLog';
@@ -24,6 +25,7 @@ import * as toggleTabMovesFocus from './toggleTabMovesFocus';
const index: any[] = [
copyDevCommand,
copyToClipboard,
editProfileConfig,
emptyTrash,
exportDeletionLog,

View File

@@ -3,7 +3,7 @@ import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import Note from '@joplin/lib/models/Note';
const bridge = require('@electron/remote').require('./bridge').default;
import bridge from '../services/bridge';
export const declaration: CommandDeclaration = {
name: 'startExternalEditing',

View File

@@ -35,8 +35,8 @@ class ClipperConfigScreenComponent extends React.Component {
void shim.showMessageBox(_('Token has been copied to the clipboard!'), { type: MessageBoxType.Info });
}
private renewToken_click() {
if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
private async renewToken_click() {
if (await shim.showConfirmationDialog(_('Are you sure you want to renew the authorisation token?'))) {
void EncryptionService.instance()
.generateApiToken()
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied

View File

@@ -97,17 +97,17 @@ class ConfigScreenComponent extends React.Component<any, any> {
private async handleSettingButton(key: string) {
if (key === 'sync.clearLocalSyncStateButton') {
if (!confirm('This cannot be undone. Do you want to continue?')) return;
if (!await shim.showConfirmationDialog('This cannot be undone. Do you want to continue?')) return;
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalSyncState);
await Setting.saveAll();
await restart();
} else if (key === 'sync.clearLocalDataButton') {
if (!confirm('This cannot be undone. Do you want to continue?')) return;
if (!await shim.showConfirmationDialog('This cannot be undone. Do you want to continue?')) return;
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData);
await Setting.saveAll();
await restart();
} else if (key === 'ocr.clearLanguageDataCacheButton') {
if (!confirm(this.restartMessage())) return;
if (!await shim.showConfirmationDialog(this.restartMessage())) return;
Setting.setValue('ocr.clearLanguageDataCache', true);
await restart();
} else if (key === 'sync.openSyncWizard') {
@@ -258,6 +258,28 @@ class ConfigScreenComponent extends React.Component<any, any> {
);
}
if (settings['sync.target'] === SyncTargetRegistry.nameToId('joplinServerSaml')) {
const server = settings['sync.11.path'] as string;
const goToSamlLogin = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'JoplinServerSamlLogin',
});
};
settingComps.push(
<div key="connect_to_joplin_server_saml_button" style={this.rowStyle_}>
<Button
title={_('Connect using your organisation account')}
level={ButtonLevel.Primary}
onClick={goToSamlLogin}
disabled={!server || server?.trim().length === 0}
/>
</div>,
);
}
settingComps.push(
<div key="check_sync_config_button" style={this.rowStyle_}>
<Button

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import ButtonBar from './ConfigScreen/ButtonBar';
import { _ } from '@joplin/lib/locale';
import bridge from '../services/bridge';
const { connect } = require('react-redux');
const bridge = require('@electron/remote').require('./bridge').default;
const { themeStyle } = require('@joplin/lib/theme');
const Shared = require('@joplin/lib/components/shared/dropbox-login-shared');

View File

@@ -1,4 +1,4 @@
const React = require('react');
import * as React from 'react';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';

View File

@@ -3,9 +3,14 @@ import versionInfo, { PackageInfo } from '@joplin/lib/versionInfo';
import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';
import restart from '../services/restart';
import BannerContent from './NoteEditor/WarningBanner/BannerContent';
import { _ } from '@joplin/lib/locale';
import Logger from '@joplin/utils/Logger';
const packageInfo: PackageInfo = require('../packageInfo.js');
const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('ErrorBoundary');
interface ErrorInfo {
componentStack: string;
}
@@ -30,6 +35,28 @@ interface Props {
children: any;
}
interface BannerProps {
isVisible: boolean;
}
const SwitchToNewEditorBanner = (props: BannerProps) => {
const handleSwitchToNewEditor = () => {
Setting.setValue('editor.legacyMarkdown', false);
const message = _('You are now using the latest version of the Markdown editor.');
// eslint-disable-next-line no-restricted-globals
alert(message);
};
return <BannerContent
acceptMessage={_('Switch to the new editor')}
onAccept={handleSwitchToNewEditor}
visible={props.isVisible}
>
{_('The legacy Markdown editor appears to have crashed due to an incompatibility with a plugin. We recommend using the new editor.')}
</BannerContent>;
};
export default class ErrorBoundary extends React.Component<Props, State> {
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
@@ -59,6 +86,9 @@ export default class ErrorBoundary extends React.Component<Props, State> {
}
this.setState({ error, errorInfo, pluginInfos, plugins });
logger.error('The application encountered an error:', error);
logger.error('Component stack', errorInfo?.componentStack);
}
public componentDidMount() {
@@ -130,8 +160,11 @@ export default class ErrorBoundary extends React.Component<Props, State> {
}
}
const isLegacyEditorError = !!this.state.error.stack.includes('CodeMirror/v5');
return (
<div style={{ overflow: 'auto', fontFamily: 'sans-serif', padding: '5px 20px' }}>
<SwitchToNewEditorBanner isVisible={isLegacyEditorError} />
<h1>Error</h1>
{this.renderMessage()}
<p>To report the error, please copy the *entire content* of this page and post it on Joplin forum or GitHub.</p>

View File

@@ -1,3 +1,4 @@
import * as React from 'react';
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import EmojiBox from './EmojiBox';

View File

@@ -1,9 +1,9 @@
const { connect } = require('react-redux');
import * as React from 'react';
import { connect } from 'react-redux';
import { AppState } from '../app.reducer';
import { _ } from '@joplin/lib/locale';
import { clipboard } from 'electron';
import Button from './Button/Button';
import { Fragment } from 'react';
import { accountTypeToString } from '@joplin/lib/utils/joplinCloud/types';
import bridge from '../services/bridge';
@@ -47,10 +47,10 @@ const JoplinCloudConfigScreen = (props: JoplinCloudConfigScreenProps) => {
<h2>{_('Email to note')}</h2>
<p>{_('Any email sent to this address will be converted into a note and added to your collection. The note will be saved into the Inbox notebook')}</p>
{
isEmailToNoteAvailableInAccount ? <Fragment>
isEmailToNoteAvailableInAccount ? <>
<p className='inbox-email-value'>{props.inboxEmail}</p>
<Button onClick={copyToClipboard} title={_('Copy to clipboard')} />
</Fragment>
</>
: <div className='alert-warn'>
<p>{_('Your account doesn\'t have access to this feature')}</p>
</div>

View File

@@ -1,9 +1,9 @@
import { Fragment, useEffect, useMemo, useReducer, useState } from 'react';
import * as React from 'react';
import { useEffect, useMemo, useReducer, useState } from 'react';
import ButtonBar from './ConfigScreen/ButtonBar';
import { _ } from '@joplin/lib/locale';
import { clipboard } from 'electron';
import Button, { ButtonLevel } from './Button/Button';
const bridge = require('@electron/remote').require('./bridge').default;
import { uuidgen } from '@joplin/lib/uuid';
import { Dispatch } from 'redux';
import { reducer, defaultState, generateApplicationConfirmUrl, checkIfLoginWasSuccessful } from '@joplin/lib/services/joplinCloudUtils';
@@ -11,6 +11,7 @@ import { AppState } from '../app.reducer';
import Logger from '@joplin/utils/Logger';
import { reg } from '@joplin/lib/registry';
import JoplinCloudSignUpCallToAction from './JoplinCloudSignUpCallToAction';
import bridge from '../services/bridge';
const logger = Logger.create('JoplinCloudLoginScreen');
const { connect } = require('react-redux');
@@ -61,7 +62,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
const onAuthorizeClicked = async () => {
const url = await generateApplicationConfirmUrl(confirmUrl(applicationAuthId));
bridge().openExternal(url);
void bridge().openExternal(url);
onButtonUsed();
};
@@ -81,7 +82,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
<div className="login-page">
<div className="page-container">
{state.active !== 'COMPLETED' ? (
<Fragment>
<>
<p className="text">{_('To allow Joplin to synchronise with Joplin Cloud, please login using this URL:')}</p>
<div className="buttons-container">
<Button
@@ -98,7 +99,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
/>
</div>
</Fragment>
</>
) : null}
<p className={state.className}>{state.message()}
{state.active === 'ERROR' ? (

View File

@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import Setting from '@joplin/lib/models/Setting';
import { AppState, AppStateRoute } from '../app.reducer';
import bridge from '../services/bridge';
import { useContext, useEffect, useMemo, useRef } from 'react';
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { WindowIdContext } from './NewWindowOrIFrame';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactor of code from before rule was applied
@@ -55,26 +55,44 @@ const useWindowRefocusManager = (route: AppStateRoute) => {
}, [routeName, windowId]);
};
const useContainerSize = (container: HTMLElement|null) => {
const [size, setSize] = useState({ width: container?.clientWidth ?? 0, height: container?.clientHeight ?? 0 });
useEffect(() => {
if (!container) return () => {};
const observer = new ResizeObserver(() => {
setSize({
width: container.clientWidth,
height: container.clientHeight,
});
});
observer.observe(container);
return () => {
observer.disconnect();
};
}, [container]);
return size;
};
const NavigatorComponent: React.FC<Props> = props => {
const route = props.route;
const screenInfo = props.screens[route?.routeName];
const [container, setContainer] = useState<HTMLElement|null>(null);
useWindowTitleManager(screenInfo);
useWindowRefocusManager(route);
const size = useContainerSize(container);
if (!route) throw new Error('Route must not be null');
const screenProps = route.props ? route.props : {};
const Screen = screenInfo.screen;
const screenStyle = {
width: props.style.width,
height: props.style.height,
};
return (
<div style={props.style} className={props.className}>
<Screen style={screenStyle} {...screenProps} />
<div ref={setContainer} style={props.style} className={props.className}>
<Screen style={size} {...screenProps} />
</div>
);
};

View File

@@ -13,7 +13,6 @@ import { SecondaryWindowApi } from '../utils/window/types';
export const WindowIdContext = createContext(defaultWindowId);
type OnCloseCallback = ()=> void;
type OnFocusCallback = ()=> void;
export enum WindowMode {
Iframe, NewWindow,
@@ -27,7 +26,6 @@ interface Props {
mode: WindowMode;
windowId: string;
onClose: OnCloseCallback;
onFocus?: OnFocusCallback;
}
const useDocument = (
@@ -86,10 +84,7 @@ const useDocument = (
};
type OnSetLoaded = (loaded: boolean)=> void;
const useDocumentSetup = (doc: Document|null, setLoaded: OnSetLoaded, onFocus?: OnFocusCallback) => {
const onFocusRef = useRef(onFocus);
onFocusRef.current = onFocus;
const useDocumentSetup = (doc: Document|null, setLoaded: OnSetLoaded) => {
useEffect(() => {
if (!doc) return;
@@ -120,14 +115,6 @@ const useDocumentSetup = (doc: Document|null, setLoaded: OnSetLoaded, onFocus?:
doc.body.style.height = '100vh';
const containerWindow = doc.defaultView;
containerWindow.addEventListener('focus', () => {
onFocusRef.current?.();
});
if (doc.hasFocus()) {
onFocusRef.current?.();
}
setLoaded(true);
}, [doc, setLoaded]);
};
@@ -137,7 +124,7 @@ const NewWindowOrIFrame: React.FC<Props> = props => {
const [loaded, setLoaded] = useState(false);
const doc = useDocument(props.mode, iframeRef, props.onClose);
useDocumentSetup(doc, setLoaded, props.onFocus);
useDocumentSetup(doc, setLoaded);
useEffect(() => {
if (!doc) return;

View File

@@ -20,7 +20,6 @@ interface Props {
newWindow: boolean;
windowId: string;
activeWindowId: string;
startupPluginsLoaded: boolean;
}
@@ -57,22 +56,10 @@ const SecondaryWindow: React.FC<Props> = props => {
}
}, [props.dispatch, props.windowId, newWindow]);
const onWindowFocus = useCallback(() => {
// Verify that the window still has focus (e.g. to handle the case where the event was delayed).
if (containerRef.current?.ownerDocument.hasFocus()) {
props.dispatch({
type: 'WINDOW_FOCUS',
windowId: props.windowId,
lastWindowId: props.activeWindowId,
});
}
}, [props.dispatch, props.windowId, props.activeWindowId]);
return <NewWindowOrIFrame
mode={newWindow ? WindowMode.NewWindow : WindowMode.Iframe}
windowId={props.windowId}
onClose={onWindowClose}
onFocus={onWindowFocus}
title={windowTitle}
>
<LibraryStyleRoot>
@@ -122,7 +109,6 @@ export default connect((state: AppState, ownProps: ConnectProps) => {
isSafeMode: state.settings.isSafeMode,
codeView: windowState?.editorCodeView ?? state.settings['editor.codeView'],
legacyMarkdown: state.settings['editor.legacyMarkdown'],
activeWindowId: stateUtils.activeWindowId(state),
startupPluginsLoaded: state.startupPluginsLoaded,
};
})(SecondaryWindow);

View File

@@ -1,6 +1,4 @@
import { useEffect, useRef } from 'react';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
// eslint-disable-next-line @typescript-eslint/no-explicit-any, import/prefer-default-export -- Old code before rule was applied
export function cursorPositionToTextOffset(cursorPos: any, body: string) {
if (!body) return 0;
@@ -20,12 +18,3 @@ export function cursorPositionToTextOffset(cursorPos: any, body: string) {
return pos;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
export function usePrevious(value: any): any {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import * as CodeMirror from 'codemirror';
import CodeMirror from 'codemirror';
import 'codemirror/addon/comment/comment';
import 'codemirror/addon/dialog/dialog';
@@ -32,54 +32,41 @@ import Setting from '@joplin/lib/models/Setting';
// import eventManager from '@joplin/lib/eventManager';
import { reg } from '@joplin/lib/registry';
import { focus } from '@joplin/lib/utils/focusHandler';
// Based on http://pypl.github.io/PYPL.html
const topLanguages = [
'python',
'clike',
'javascript',
'jsx',
'php',
'r',
'swift',
'go',
'vb',
'vbscript',
'ruby',
'rust',
'dart',
'lua',
'groovy',
'perl',
'cobol',
'julia',
'haskell',
'pascal',
'css',
import 'codemirror/mode/python/python';
import 'codemirror/mode/clike/clike';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/jsx/jsx';
import 'codemirror/mode/php/php';
import 'codemirror/mode/r/r';
import 'codemirror/mode/swift/swift';
import 'codemirror/mode/go/go';
import 'codemirror/mode/vb/vb';
import 'codemirror/mode/vbscript/vbscript';
import 'codemirror/mode/ruby/ruby';
import 'codemirror/mode/rust/rust';
import 'codemirror/mode/dart/dart';
import 'codemirror/mode/lua/lua';
import 'codemirror/mode/groovy/groovy';
import 'codemirror/mode/perl/perl';
import 'codemirror/mode/cobol/cobol';
import 'codemirror/mode/julia/julia';
import 'codemirror/mode/haskell/haskell';
import 'codemirror/mode/pascal/pascal';
import 'codemirror/mode/css/css';
// Additional languages, not in the PYPL list
'xml', // For HTML too
'markdown',
'yaml',
'shell',
'dockerfile',
'diff',
'erlang',
'sql',
];
// Load Top Modes
for (let i = 0; i < topLanguages.length; i++) {
const mode = topLanguages[i];
// Additional languages, not in the PYPL lis;
import 'codemirror/mode/xml/xml'; // For HTML too
import 'codemirror/mode/markdown/markdown';
import 'codemirror/mode/yaml/yaml';
import 'codemirror/mode/shell/shell';
import 'codemirror/mode/dockerfile/dockerfile';
import 'codemirror/mode/diff/diff';
import 'codemirror/mode/erlang/erlang';
import 'codemirror/mode/sql/sql';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
if (CodeMirror.modeInfo.find((m: any) => m.mode === mode)) {
require(`codemirror/mode/${mode}/${mode}`);
} else {
reg.logger().error('Cannot find CodeMirror mode: ', mode);
}
}
export interface EditorProps {
value: string;

View File

@@ -26,7 +26,7 @@ interface Props extends EditorProps {
}
const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
const editorContainerRef = useRef<HTMLDivElement>();
const editorContainerRef = useRef<HTMLDivElement|null>(null);
const [editor, setEditor] = useState<CodeMirrorControl|null>(null);
// The editor will only be created once, so callbacks that could

View File

@@ -7,7 +7,7 @@ import { useEffect, useCallback, useRef, forwardRef, useImperativeHandle } from
import { NoteBodyEditorProps, NoteBodyEditorRef } from '../../utils/types';
const PlainEditor = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditorRef>) => {
const editorRef = useRef<HTMLTextAreaElement>();
const editorRef = useRef<HTMLTextAreaElement|null>(null);
useImperativeHandle(ref, () => {
return {

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle, useMemo } from 'react';
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps, ResourceInfos, HtmlToMarkdownHandler, ScrollToTextValue } from '../../utils/types';
import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle, useMemo, Ref } from 'react';
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps, ResourceInfos, HtmlToMarkdownHandler, ScrollToTextValue, NoteBodyEditorRef } from '../../utils/types';
import { resourcesStatus, commandAttachFileToBody, getResourcesFromPasteEvent, processPastedHtml } from '../../utils/resourceHandling';
import attachedResources from '@joplin/lib/utils/attachedResources';
import useScroll from './utils/useScroll';
@@ -44,7 +44,7 @@ import useDocument from '../../../hooks/useDocument';
import useEditDialog from './utils/useEditDialog';
import useEditDialogEventListeners from './utils/useEditDialogEventListeners';
import Setting from '@joplin/lib/models/Setting';
import useTextPatternsLookup from './utils/useTextPatternsLookup';
import useTextPatternsLookup, { TextPatternContext } from './utils/useTextPatternsLookup';
import { toFileProtocolPath } from '@joplin/utils/path';
import { RenderResultPluginAsset } from '@joplin/renderer/types';
@@ -95,8 +95,7 @@ interface LastOnChangeEventInfo {
let dispatchDidUpdateIID_: any = null;
let changeId_ = 1;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
const [editorContainer, setEditorContainer] = useState<HTMLDivElement|null>(null);
const editorContainerDom = useDocument(editorContainer);
const [editor, setEditor] = useState<Editor|null>(null);
@@ -765,7 +764,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
forecolor: { inline: 'span', styles: { color: '%value' }, remove_similar: true },
},
text_patterns: [],
text_patterns_lookup: () => textPatternsLookupRef.current(),
text_patterns_lookup: (ctx: TextPatternContext) => textPatternsLookupRef.current(ctx),
setup: (editor: Editor) => {
editor.addCommand('joplinMath', async () => {
@@ -1129,7 +1128,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const allAssetsOptions: NoteStyleOptions = {
contentMaxWidthTarget: '.mce-content-body',
contentWrapperSelector: '.mce-content-body',
scrollbarSize: props.scrollbarSize,
themeId: props.contentMarkupLanguage === MarkupLanguage.Html ? 1 : null,
whiteBackgroundNoteRendering: props.whiteBackgroundNoteRendering,
};
@@ -1146,7 +1144,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
cancelled = true;
};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [editor, props.noteId, props.themeId, props.scrollbarSize, props.markupToHtml, props.allAssets, props.content, props.resourceInfos, props.contentKey, props.contentMarkupLanguage, props.whiteBackgroundNoteRendering]);
}, [editor, props.noteId, props.themeId, props.markupToHtml, props.allAssets, props.content, props.resourceInfos, props.contentKey, props.contentMarkupLanguage, props.whiteBackgroundNoteRendering]);
useEffect(() => {
if (!editor) return () => {};
@@ -1200,11 +1198,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
// we call the current one from setTimeout.
// https://github.com/facebook/react/issues/14010#issuecomment-433788147
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
const props_onChangeRef = useRef<Function>();
const props_onChangeRef = useRef<Function>(null);
props_onChangeRef.current = props.onChange;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
const prop_htmlToMarkdownRef = useRef<HtmlToMarkdownHandler>();
const prop_htmlToMarkdownRef = useRef<HtmlToMarkdownHandler>(null);
prop_htmlToMarkdownRef.current = props.htmlToMarkdown;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@@ -17,6 +17,7 @@ import { EditDialogControl } from './useEditDialog';
import { Dispatch } from 'redux';
import { _ } from '@joplin/lib/locale';
import type { MenuItem as MenuItemType } from 'electron';
import isItemId from '@joplin/lib/models/utils/isItemId';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -38,15 +39,20 @@ export default function(editor: Editor, plugins: PluginStates, dispatch: Dispatc
const makeMainMenuItems = (element: Element) => {
let itemType: ContextMenuItemType = ContextMenuItemType.None;
let resourceId = '';
let linkToCopy = null;
let linkUrl = null;
const pathToId = (path: string) => {
const id = Resource.pathToId(path);
return isItemId(id) ? id : '';
};
if (element.nodeName === 'IMG') {
itemType = ContextMenuItemType.Image;
resourceId = Resource.pathToId((element as HTMLImageElement).src);
resourceId = pathToId((element as HTMLImageElement).src);
} else if (element.nodeName === 'A') {
resourceId = Resource.pathToId((element as HTMLAnchorElement).href);
resourceId = pathToId((element as HTMLAnchorElement).href);
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
linkToCopy = element.getAttribute('href') || '';
linkUrl = element.getAttribute('href') || '';
} else {
itemType = ContextMenuItemType.Text;
}
@@ -56,7 +62,8 @@ export default function(editor: Editor, plugins: PluginStates, dispatch: Dispatc
resourceId,
filename: null,
mime: null,
linkToCopy,
linkToCopy: linkUrl,
linkToOpen: linkUrl,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
insertContent: (content: string) => {

View File

@@ -1,12 +1,17 @@
import { useRef } from 'react';
export interface TextPatternContext {
readonly text: string;
readonly block: Element;
}
interface TextPatternOptions {
enabled: boolean;
enableMath: boolean;
}
const useTextPatternsLookup = ({ enabled, enableMath }: TextPatternOptions) => {
const getTextPatterns = () => {
const getTextPatterns = (ctx: TextPatternContext) => {
if (!enabled) return [];
return [
@@ -27,6 +32,8 @@ const useTextPatternsLookup = ({ enabled, enableMath }: TextPatternOptions) => {
{ start: '1.', cmd: 'InsertOrderedList' },
{ start: '*', cmd: 'InsertUnorderedList' },
{ start: '-', cmd: 'InsertUnorderedList' },
// To more closely match Markdown, only do <hr/> replacements if the full line matches the pattern.
['---', '***', '___'].includes(ctx.text) && { start: ctx.text, replacement: '<hr/>' },
].filter(pattern => !!pattern);
};

View File

@@ -14,7 +14,7 @@ import useFormNote, { OnLoadEvent, OnSetFormNote } from './utils/useFormNote';
import useEffectiveNoteId from './utils/useEffectiveNoteId';
import useFolder from './utils/useFolder';
import styles_ from './styles';
import { NoteEditorProps, FormNote, OnChangeEvent, NoteBodyEditorProps, AllAssetsOptions, NoteBodyEditorRef } from './utils/types';
import { NoteEditorProps, FormNote, OnChangeEvent, AllAssetsOptions, NoteBodyEditorRef, NoteBodyEditorPropsAndRef } from './utils/types';
import CommandService from '@joplin/lib/services/CommandService';
import Button, { ButtonLevel } from '../Button/Button';
import eventManager, { EventName } from '@joplin/lib/eventManager';
@@ -52,10 +52,10 @@ import Logger from '@joplin/utils/Logger';
import usePluginEditorView from './utils/usePluginEditorView';
import { stateUtils } from '@joplin/lib/reducer';
import { WindowIdContext } from '../NewWindowOrIFrame';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import EditorPluginHandler from '@joplin/lib/services/plugins/EditorPluginHandler';
import useResourceUnwatcher from './utils/useResourceUnwatcher';
import StatusBar from './StatusBar';
import useVisiblePluginEditorViewIds from '@joplin/lib/hooks/plugins/useVisiblePluginEditorViewIds';
import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
const debounce = require('debounce');
@@ -75,23 +75,17 @@ function NoteEditorContent(props: NoteEditorProps) {
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
const [isReadOnly, setIsReadOnly] = useState<boolean>(false);
const editorRef = useRef<NoteBodyEditorRef>();
const titleInputRef = useRef<HTMLInputElement>();
const editorRef = useRef<NoteBodyEditorRef|null>(null);
const titleInputRef = useRef<HTMLInputElement|null>(null);
const isMountedRef = useRef(true);
const noteSearchBarRef = useRef(null);
const editorPluginHandler = useMemo(() => {
return new EditorPluginHandler(PluginService.instance());
}, []);
const shownEditorViewIds = props['plugins.shownEditorViewIds'];
// Should be constant and unique to this instance of the editor.
const editorId = useMemo(() => {
return `editor-${editorIdCounter++}`;
}, []);
const setFormNoteRef = useRef<OnSetFormNote>();
const setFormNoteRef = useRef<OnSetFormNote>(null);
const { saveNoteIfWillChange, scheduleSaveNote } = useScheduleSaveCallbacks({
setFormNote: setFormNoteRef, dispatch: props.dispatch, editorRef, editorId,
});
@@ -105,18 +99,7 @@ function NoteEditorContent(props: NoteEditorProps) {
}, []);
const effectiveNoteId = useEffectiveNoteId(props);
useAsyncEffect(async (_event) => {
if (!props.startupPluginsLoaded) return;
await editorPluginHandler.emitActivationCheck();
}, [effectiveNoteId, editorPluginHandler, props.startupPluginsLoaded]);
useEffect(() => {
if (!props.startupPluginsLoaded) return;
editorPluginHandler.emitUpdate(shownEditorViewIds);
}, [effectiveNoteId, editorPluginHandler, shownEditorViewIds, props.startupPluginsLoaded]);
const { editorPlugin, editorView } = usePluginEditorView(props.plugins, shownEditorViewIds);
const { editorPlugin, editorView } = usePluginEditorView(props.plugins);
const builtInEditorVisible = !editorPlugin;
const { formNote, setFormNote, isNewNote, resourceInfos } = useFormNote({
@@ -130,11 +113,24 @@ function NoteEditorContent(props: NoteEditorProps) {
editorId,
});
setFormNoteRef.current = setFormNote;
const formNoteRef = useRef<FormNote>();
const formNoteRef = useRef<FormNote>(formNote);
formNoteRef.current = { ...formNote };
const formNoteFolder = useFolder({ folderId: formNote.parent_id });
const windowId = useContext(WindowIdContext);
const shownEditorViewIds = useVisiblePluginEditorViewIds(props.plugins, windowId);
useConnectToEditorPlugin({
startupPluginsLoaded: props.startupPluginsLoaded,
setFormNote,
scheduleSaveNote,
formNote,
effectiveNoteId,
shownEditorViewIds,
activeEditorView: editorView,
plugins: props.plugins,
});
const {
localSearch,
onChange: localSearch_change,
@@ -161,6 +157,7 @@ function NoteEditorContent(props: NoteEditorProps) {
customCss: props.customCss,
plugins: props.plugins,
scrollbarSize: props.scrollbarSize,
baseFontFamily: props.viewerFontFamily,
});
const allAssets = useCallback(async (markupLanguage: number, options: AllAssetsOptions = null) => {
@@ -180,9 +177,10 @@ function NoteEditorContent(props: NoteEditorProps) {
contentMaxWidth: props.contentMaxWidth,
contentMaxWidthTarget: options.contentMaxWidthTarget,
scrollbarSize: props.scrollbarSize,
baseFontFamily: props.viewerFontFamily,
whiteBackgroundNoteRendering: options.whiteBackgroundNoteRendering,
});
}, [props.plugins, props.themeId, props.scrollbarSize, props.customCss, props.contentMaxWidth]);
}, [props.plugins, props.themeId, props.scrollbarSize, props.viewerFontFamily, props.customCss, props.contentMaxWidth]);
const handleProvisionalFlag = useCallback(() => {
if (props.isProvisional) {
@@ -336,7 +334,6 @@ function NoteEditorContent(props: NoteEditorProps) {
lastEditorScrollPercents: props.lastEditorScrollPercents,
editorRef,
});
const windowId = useContext(WindowIdContext);
const onMessage = useMessageHandler(scrollWhenReady, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
useResourceUnwatcher({ noteId: formNote.id, windowId });
@@ -423,7 +420,7 @@ function NoteEditorContent(props: NoteEditorProps) {
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
const markupLanguage = formNote.markup_language;
const editorProps: NoteBodyEditorProps = {
const editorProps: NoteBodyEditorPropsAndRef = {
ref: editorRef,
contentKey: formNote.id,
style: styles.tinyMCE,
@@ -462,6 +459,7 @@ function NoteEditorContent(props: NoteEditorProps) {
fontSize: Setting.value('style.editor.fontSize'),
contentMaxWidth: props.contentMaxWidth,
scrollbarSize: props.scrollbarSize,
baseFontFamily: props.viewerFontFamily,
isSafeMode: props.isSafeMode,
useCustomPdfViewer: props.useCustomPdfViewer,
// We need it to identify the context for which media is rendered.
@@ -706,7 +704,6 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
highlightedWords: state.highlightedWords,
plugins: state.pluginService.plugins,
pluginHtmlContents: state.pluginService.pluginHtmlContents,
'plugins.shownEditorViewIds': state.settings['plugins.shownEditorViewIds'] || [],
toolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons([
'historyBackward',
'historyForward',
@@ -718,6 +715,7 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
], whenClauseContext)[0] as ToolbarButtonInfo,
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
scrollbarSize: state.settings['style.scrollbarSize'],
viewerFontFamily: state.settings['style.viewer.fontFamily'],
tabMovesFocus: state.settings['editor.tabMovesFocus'],
isSafeMode: state.settings.isSafeMode,
useCustomPdfViewer: false,

View File

@@ -5,7 +5,7 @@ interface Props {
children: React.ReactNode;
acceptMessage: string;
onAccept: ()=> void;
onDismiss: ()=> void;
onDismiss?: ()=> void;
visible: boolean;
}
@@ -17,7 +17,7 @@ const BannerContent: React.FC<Props> = props => {
return <div className='warning-banner'>
{props.children}
&nbsp;&nbsp;<a onClick={props.onAccept} className='warning-banner-link' href="#">[ {props.acceptMessage} ]</a>
&nbsp;&nbsp;<a onClick={props.onDismiss} className='warning-banner-link' href="#">[ {_('Dismiss')} ]</a>
&nbsp;&nbsp;{ props.onDismiss ? <a onClick={props.onDismiss} className='warning-banner-link' href="#">[ {_('Dismiss')} ]</a> : null }
</div>;
};

View File

@@ -15,6 +15,7 @@ import Setting from '@joplin/lib/models/Setting';
import ItemChange from '@joplin/lib/models/ItemChange';
import shim from '@joplin/lib/shim';
import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils';
import CommandService from '@joplin/lib/services/CommandService';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
@@ -84,9 +85,18 @@ export function menuItems(dispatch: Function): ContextMenuItems {
open: {
label: _('Open...'),
onAction: async (options: ContextMenuOptions) => {
await openItemById(options.resourceId, dispatch);
if (options.resourceId) {
await openItemById(options.resourceId, dispatch);
} else if (options.linkToOpen) {
await CommandService.instance().execute('openItem', options.linkToOpen);
} else {
await shim.showErrorDialog('No link found');
}
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => (
(!options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource))
|| (!!options.linkToOpen && itemType === ContextMenuItemType.Link)
),
},
saveAs: {
label: _('Save as...'),

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