1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-02 20:46:21 +02:00

Compare commits

..

255 Commits

Author SHA1 Message Date
palerdot
3dc78cad61 restoring pdf-viewer ignore changes 2023-04-24 11:36:47 +05:30
palerdot
f7d14c7afb Revert "restore pdf package ignore files"
This reverts commit c442abd65f.
2023-04-24 11:33:41 +05:30
palerdot
4ff78b9b57 restoring pdf-viewer ignore changes 2023-04-24 11:32:54 +05:30
palerdot
c442abd65f restore pdf package ignore files 2023-04-24 11:28:57 +05:30
palerdot
0e2e867140 Merge remote-tracking branch 'origin/dev' into remove-pdf-viewer 2023-04-24 11:27:05 +05:30
github-actions[bot]
b83165f9e5 @DeeJayLSP has signed the CLA in laurent22/joplin#8077 2023-04-24 04:57:05 +00:00
Laurent Cozic
7706f9058b Chore: Trying to fix CI error "TypeError: Cannot redefine property: performance" 2023-04-23 22:58:43 +01:00
Laurent Cozic
03222ba1b2 Revert "Chore: Trying to fix CI error "TypeError: Cannot redefine property: performance""
This reverts commit d4f49db342.

Wrong fix
2023-04-23 22:56:24 +01:00
Laurent Cozic
d4f49db342 Chore: Trying to fix CI error "TypeError: Cannot redefine property: performance" 2023-04-23 22:43:10 +01:00
Laurent Cozic
40e1b0559e Doc: Allow translating documentation 2023-04-23 22:33:15 +01:00
Laurent Cozic
738f1decbb Chore: Add types to search engine 2023-04-23 10:07:38 +01:00
jcgurango
e5a364d052 Chore: Mobile: Convert note-list.js to NoteList.tsx (#8064) 2023-04-23 10:07:28 +01:00
Laurent Cozic
357a3e2e7b Chore: Add types to search engine 2023-04-23 10:05:13 +01:00
Joplin Bot
af91fd99cc Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-22 00:39:14 +00:00
Joplin Bot
3855f60a0d Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-21 18:18:40 +00:00
Arun Kumar
079b379e7a Desktop: Resolves #8028: Compress installer to reduce size (#8068) 2023-04-20 09:02:04 +01:00
palerdot
476906849b adding back pdf-viewer package 2023-04-20 11:13:03 +05:30
github-actions[bot]
e23e036677 @Wladefant has signed the CLA in laurent22/joplin#8069 2023-04-19 12:28:56 +00:00
palerdot
8fd54d41cb remove pdf-viewer package 2023-04-19 12:26:45 +05:30
Arun Kumar
b824ff5457 Mobile: Fixes #8017: Fixed sync crash (#8056) 2023-04-17 15:17:15 +03:00
renovate[bot]
3669a1b5d6 Update dependency react-native-paper to v5.5.2 (#8054)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-16 23:41:05 +00:00
Laurent Cozic
b93f9aaf01 Update terminal.md 2023-04-16 13:13:06 +03:00
renovate[bot]
8679290206 Update dependency react-native-paper to v5.5.1 (#8037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-14 22:36:08 +00:00
Joplin Bot
4acec5c6c7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-14 18:18:49 +00:00
Joplin Bot
f1b03453a4 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-14 12:20:07 +00:00
github-actions[bot]
7972dd5556 @simonla has signed the CLA in laurent22/joplin#8042 2023-04-11 19:03:07 +00:00
Laurent Cozic
4842500f0a Tools: Increase renovate stability days 2023-04-10 13:00:07 +02:00
renovate[bot]
84c7f28ec5 Update dependency react-native-paper to v5.5.0 (#8035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-10 12:57:53 +02:00
github-actions[bot]
f3eea43d24 @tbjers has signed the CLA in laurent22/joplin#8036 2023-04-10 02:35:07 +00:00
Joplin Bot
8babaddbcb Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-09 12:21:18 +00:00
Laurent Cozic
13cdaabb17 Android 2.11.2 2023-04-09 14:07:14 +02:00
renovate[bot]
a94aa21088 Update dependency slugify to v1.6.6 (#8019)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-04-09 12:56:05 +01:00
Henry Heino
6116bed4e3 Mobile: Resolve #8022: Editor syntax highlighting was broken (#8023) 2023-04-09 12:55:47 +01:00
Arun Kumar
fabd0b4dda Desktop, Mobile: Fixes #7940: Removed MasterKey from Sync Status report (#8026) 2023-04-09 12:54:48 +01:00
Laurent Cozic
6b72f86e7b Mobile: Security: Prevent bypassing fingerprint lock on certain devices 2023-04-09 11:29:33 +02:00
Joplin Bot
02cf546124 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-09 06:17:13 +00:00
renovate[bot]
eecb012d64 Update dependency @react-native-community/netinfo to v9.3.8 (#8033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-09 03:02:24 +00:00
Laurent Cozic
04e9b40769 Android 2.11.1 2023-04-08 10:51:00 +02:00
Laurent Cozic
efdbaeb397 Mobile: Add log info for biometrics feature 2023-04-08 10:40:53 +02:00
Laurent Cozic
46425b920c Doc: Added some doc about Joplin Server items 2023-04-07 16:49:54 +02:00
Laurent Cozic
f5be43c2ac Doc: Add info about synchronisation process 2023-04-07 16:36:59 +02:00
github-actions[bot]
080541a2fe @Letty has signed the CLA in laurent22/joplin#8029 2023-04-07 10:05:21 +00:00
github-actions[bot]
7dc638edf4 @gitstart has signed the CLA in laurent22/joplin#8024 2023-04-06 21:46:34 +00:00
Laurent Cozic
3b686194d8 Tools: Pass APPLE_APP_SPECIFIC_PASSWORD to CI 2023-04-06 14:02:45 +02:00
Laurent Cozic
5c2640f88f Tools: Simplify root workspace build 2023-04-06 12:11:45 +02:00
Laurent Cozic
eca0f92dff Disable again 2023-04-06 11:46:06 +02:00
Laurent Cozic
260fa6c038 Clipper release v2.11.2 2023-04-06 11:44:55 +02:00
Laurent Cozic
8ec6bc9138 Tools: Simplify dependencies of root package 2023-04-06 11:29:59 +02:00
Laurent Cozic
93fa92369b Tools: Removing packages/tools dependency from root workspace 2023-04-06 11:02:22 +02:00
Laurent Cozic
bc6c5ab7a7 Chore: Make utils package a fixed version again (too many problems otherwise) 2023-04-06 10:02:49 +02:00
Laurent Cozic
1826625e4f lock file 2023-04-05 23:19:25 +02:00
Laurent Cozic
20b8fb2719 Missing package 2023-04-05 22:48:17 +02:00
Laurent Cozic
f813e71b29 Chore: Access utils lib with relative path 2023-04-05 22:35:47 +02:00
Laurent Cozic
02422a6e31 Chore: Access utils lib with relative path 2023-04-05 22:35:46 +02:00
renovate[bot]
69a34e87f3 Update dependency nanoid to v3.3.6 (#7973)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-05 21:16:55 +01:00
Joplin Bot
cbeaa16b61 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-05 18:18:02 +00:00
Joplin Bot
05917ac142 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-05 12:24:26 +00:00
Laurent Cozic
0c8de68b80 Desktop: Fixed issue with text disappearing within plugin-created zones when searching for text
Ref: https://github.com/joplin/plugin-abc-sheet-music/issues/5
2023-04-04 21:09:58 +02:00
Joplin Bot
44d93d52d3 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-04 18:30:09 +00:00
Laurent Cozic
073bec9e8c CI: Trying to fix website builder 2023-04-04 20:16:35 +02:00
Laurent Cozic
e6a8c2bea5 CI: Trying to fix website builder 2023-04-04 19:47:54 +02:00
Laurent Cozic
81c316cd2c CI: Trying to fix website builder 2023-04-04 19:24:03 +02:00
Laurent Cozic
659c851960 CI: Trying to fix website builder 2023-04-04 17:01:34 +02:00
Laurent Cozic
572701d9a0 Tools: Fix website builder 2023-04-04 12:04:42 +02:00
Laurent Cozic
66ef37bd4e Chore: Disable flaky test 2023-04-03 20:42:31 +02:00
Laurent Cozic
9ddf75604d Doc: Remove sponsor 2023-04-03 20:39:31 +02:00
Laurent Cozic
3ed7e1d7e8 Doc: Fix sponsor thumbnails 2023-04-03 20:39:31 +02:00
Laurent Cozic
b2b412105a Merge branch 'release-2.10' into dev 2023-04-03 18:30:55 +02:00
Laurent Cozic
60a3c4f65e lock file 2023-04-03 18:26:04 +02:00
Laurent Cozic
9645414c17 Desktop release v2.10.13 2023-04-03 18:10:54 +02:00
Henry Heino
af0136ef39 All: Fixes #7986: Fix OneDrive sync attempting to call method on null variable (#7987) 2023-04-03 18:09:52 +02:00
Self Not Found
b76586c4fd All: Fixes #7851: Encode the non-ASCII characters in OneDrive URI (#7868) 2023-04-03 18:09:10 +02:00
Laurent Cozic
376e4ebde0 Desktop: Fixed display of installed plugins in About box 2023-04-03 18:01:06 +02:00
renovate[bot]
1439b8787f Update dependency react-native-image-picker to v5.3.1 (#8015)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 11:51:02 +01:00
renovate[bot]
b8854a99be Update dependency @lezer/highlight to v1.1.4 (#8014)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 10:46:19 +00:00
renovate[bot]
6cf02173dc Update dependency gettext-extractor to v3.7.0 (#8012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 10:56:44 +01:00
renovate[bot]
4d8a53d8c9 Update dependency react-native-image-picker to v5.3.0 (#8010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 10:56:25 +01:00
renovate[bot]
7f43718e1d Update dependency react-select to v5.7.2 (#8009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-02 07:09:26 +00:00
renovate[bot]
690ce637b1 Update dependency nodemon to v2.0.22 (#8008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-02 03:42:02 +00:00
renovate[bot]
4d023e679e Update dependency turndown to v7.1.2 (#8006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 16:46:25 +00:00
renovate[bot]
6e220a978f Update dependency jsdom to v21.1.1 (#8007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 16:00:34 +02:00
renovate[bot]
39757cd90e Update dependency sass to v1.59.3 (#8005)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 05:46:20 +00:00
renovate[bot]
5ccbbea757 Update dependency react-native-paper to v5.4.1 (#8004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-31 22:56:15 +00:00
Arun Kumar
309222c082 Desktop: Fixes #8000: Fixed Linux tag display issues (#8002) 2023-03-31 23:13:59 +02:00
renovate[bot]
50f5fe2c91 Update dependency sass to v1.59.2 (#8001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-31 23:12:25 +02:00
renovate[bot]
eacae83182 Update dependency react-native-paper to v5.4.0 (#7997)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-31 23:12:05 +02:00
Laurent Cozic
403d770b1d Tools: Trying to prevent CI from running on forks 2023-03-30 22:57:58 +01:00
Laurent Cozic
a481bf1b53 Tools: Trying to prevent CI from running on forks 2023-03-30 22:55:14 +01:00
renovate[bot]
0d32570c9e Update dependency fs-extra to v11.1.1 (#7996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-30 20:11:29 +00:00
Fejby
f017e99b02 All: Translation: Update cs_CZ.po (#7999) 2023-03-30 14:40:05 -04:00
github-actions[bot]
a89d64d435 @Fejby has signed the CLA in laurent22/joplin#7999 2023-03-30 18:21:47 +00:00
github-actions[bot]
3a27086534 @TahaNw has signed the CLA in laurent22/joplin#7998 2023-03-30 17:33:50 +00:00
Laurent Cozic
413c1e41b5 Revert "Chore: Mac binary signing tweaks for embedded plugins (#7988)"
This reverts commit cf4008951d.

Not working.

Ref: https://github.com/laurent22/joplin/issues/7934#issuecomment-1490632038
2023-03-30 18:24:25 +01:00
Henry Heino
8b879464b8 All: Fixes #7986: Fix OneDrive sync attempting to call method on null variable (#7987) 2023-03-30 17:41:29 +01:00
renovate[bot]
97c9bbc1fe Update dependency react-native-paper to v5.3.1 (#7977) 2023-03-30 17:38:18 +01:00
renovate[bot]
e5bebef7b2 Update dependency lint-staged to v13.2.0 (#7994) 2023-03-30 17:38:06 +01:00
renovate[bot]
73752c4b3f Update dependency pg to v8.10.0 (#7978) 2023-03-30 17:37:46 +01:00
Laurent Cozic
dcf7c9838d Desktop release v2.11.1 2023-03-30 17:13:20 +01:00
Laurent Cozic
f325e7694b Chore: Prepare v2.11 2023-03-30 17:13:06 +01:00
Arun Kumar
75d204c9ca Desktop: Resolves #6101: Added export graph button for Mermaid (#7958) 2023-03-30 16:58:48 +01:00
Arun Kumar
cf4008951d Chore: Mac binary signing tweaks for embedded plugins (#7988) 2023-03-30 16:57:22 +01:00
renovate[bot]
d67818d096 Update dependency react-select to v5.7.1 (#7993)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-30 14:30:20 +00:00
Laurent Cozic
6aaea8ad4f Renovate conf 2023-03-26 16:58:29 +01:00
github-actions[bot]
de41278096 @HelpdeskThisIsJohn has signed the CLA in laurent22/joplin#7969 2023-03-26 03:41:02 +00:00
renovate[bot]
f01ab70907 Update dependency sqlite3 to v5.1.6 (#7967) 2023-03-25 14:27:19 +00:00
renovate[bot]
bbdb221a67 Update dependency react-native-document-picker to v8.1.4 (#7966)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 18:21:01 +00:00
renovate[bot]
7d053f8c79 Update dependency styled-components to v5.3.9 (#7965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 14:17:40 +00:00
renovate[bot]
fcad0bf3ca Update dependency style-loader to v3.3.2 (#7964)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 10:04:32 +00:00
renovate[bot]
58f929f6b5 Update dependency sqlite3 to v5.1.5 (#7963)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 05:40:51 +00:00
renovate[bot]
943198c56e Update dependency node-mocks-http to v1.12.2 (#7962)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 01:51:21 +00:00
renovate[bot]
2112ad4004 Update dependency markdown-it-multimd-table to v4.2.1 (#7928) 2023-03-23 21:07:41 +00:00
Laurent Cozic
5995dc81f3 Doc: Added info about data escaping in coding style 2023-03-23 16:10:52 +00:00
Laurent Cozic
104e752634 Merge branch 'release-2.10' into dev 2023-03-23 11:35:21 +00:00
Laurent Cozic
fc335cd15d Desktop release v2.10.12 2023-03-23 11:34:42 +00:00
Julien
45923ba0d3 Desktop: Adjusted New Note and New to-do buttons' breakpoints to happen earlier (#7961) 2023-03-23 11:32:36 +00:00
Julien
8fefa99d81 Desktop: Adjusted New Note and New to-do buttons' breakpoints to happen earlier (#7961) 2023-03-23 11:31:22 +00:00
Laurent Cozic
85d652cd67 Removed unused conf 2023-03-23 11:05:15 +00:00
github-actions[bot]
88e41e9c7d @chenqy9 has signed the CLA in laurent22/joplin#7955 2023-03-23 07:04:32 +00:00
Laurent Cozic
26750488d0 Merge branch 'release-2.10' into dev 2023-03-22 18:46:42 +00:00
Laurent Cozic
a0f582b2b9 Android 2.10.9 2023-03-22 18:45:52 +00:00
Laurent Cozic
917b53bec2 lock file 2023-03-22 18:33:19 +00:00
Laurent Cozic
e44a93422a Mobile: Mark biometrics feature as beta and ensure no call is made if it is not enabled 2023-03-22 18:24:10 +00:00
Laurent Cozic
e115ef4259 Mobile: Mark biometrics feature as beta and ensure no call is made if it is not enabled 2023-03-22 18:22:58 +00:00
Laurent Cozic
bcec699124 Doc: Fixed sponsor avatars 2023-03-21 17:31:43 +00:00
github-actions[bot]
d23c728a1a @jcgurango has signed the CLA in laurent22/joplin#7953 2023-03-21 17:20:42 +00:00
Milo Ivir
0a2d507dec All: Translation: Update hr_HR.po (#7947) 2023-03-20 17:32:52 -04:00
Laurent Cozic
0c08617606 Fix CI 2023-03-20 13:43:25 +00:00
Laurent Cozic
29fba45c33 Fix CI 2023-03-20 12:24:17 +00:00
Laurent Cozic
1071a455b6 Revert "Update Croatian translation (#7937)"
This reverts commit ffeeff260f.

Ref: https://github.com/laurent22/joplin/pull/7937#issuecomment-1475058291
2023-03-20 10:20:46 +00:00
Laurent Cozic
57e4b36fd7 Revert "Revert "All: Translation: Update fi_FI.po (#7945)""
This reverts commit d18a4be31f.

Restore FI translation.

Ref: d18a4be31f (commitcomment-105121959)
2023-03-20 10:19:56 +00:00
Joplin Bot
f08fa92294 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-19 18:19:35 +00:00
Laurent Cozic
3a8d87d292 Tools: Export rootDir from utils 2023-03-19 17:31:37 +00:00
Laurent Cozic
53302c9e90 lock file 2023-03-19 17:05:31 +00:00
Laurent Cozic
28a24d8c03 Chore: Fixed build 2023-03-19 17:03:04 +00:00
Laurent Cozic
3e52411bc4 Tools: Moved some utility functions to @joplin/utils to reduce dependencies between packages 2023-03-19 15:38:09 +00:00
Joplin Bot
1548ea18e1 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-19 12:21:16 +00:00
Laurent Cozic
f8cd1ba8e5 Revert "Tools: Switch Joplin Server image location to laurent22/joplin-server"
This reverts commit 6729a3d51f.

Ref: https://discourse.joplinapp.org/t/docker-image-doesnt-exist/30103/5
2023-03-19 09:07:19 +00:00
Laurent Cozic
d18a4be31f Revert "All: Translation: Update fi_FI.po (#7945)"
This reverts commit c56f270ed6.

Ref: https://github.com/laurent22/joplin/pull/7937#issuecomment-1475058291
2023-03-19 08:59:16 +00:00
mrkaato0
c56f270ed6 All: Translation: Update fi_FI.po (#7945) 2023-03-18 21:16:42 -04:00
github-actions[bot]
2bca3d1032 @mrkaato0 has signed the CLA in laurent22/joplin#7945 2023-03-18 18:25:42 +00:00
renovate[bot]
9f81d69c5e Update dependency jquery to v3.6.4 (#7944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 16:33:08 +00:00
Laurent Cozic
815419260d Server v2.10.11 2023-03-17 17:50:34 +00:00
Laurent Cozic
6729a3d51f Tools: Switch Joplin Server image location to laurent22/joplin-server 2023-03-17 17:49:34 +00:00
Joplin Bot
6d8ce280dd Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-17 12:20:34 +00:00
Laurent Cozic
9e5b455065 Merge branch 'release-2.10' into dev 2023-03-17 08:56:39 +00:00
Laurent Cozic
09cbac3019 Desktop release v2.10.11 2023-03-17 08:55:09 +00:00
Julien
5354ad3934 Desktop: Fixes text wrap on new buttons (#7938) 2023-03-17 08:53:19 +00:00
Julien
7754048b80 Desktop: Fixes text wrap on new buttons (#7938) 2023-03-17 08:51:46 +00:00
Milo Ivir
ffeeff260f Update Croatian translation (#7937) 2023-03-17 08:51:22 +00:00
Julien
71ea74d273 Desktop: Fixes #4801: Do not allow update for plugins incompatible with current version (#7936) 2023-03-17 08:50:51 +00:00
Julien
3a744c79ae Desktop: Fixes #7920: List enabled plugins only in About Joplin and in alphabetical order (#7923) 2023-03-17 08:41:33 +00:00
renovate[bot]
d9ba27a1ec Update dependency lint-staged to v13.1.4 (#7935)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 20:26:38 +00:00
github-actions[bot]
0a3540049c @milotype has signed the CLA in laurent22/joplin#7937 2023-03-16 18:51:53 +00:00
Henry Heino
ab50ca9bbd Mobile: Add setting to enable/disable the markdown toolbar (#7929) 2023-03-16 11:12:56 +00:00
renovate[bot]
0bee793ab8 Update dependency lint-staged to v13.1.3 (#7927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-15 20:36:26 +00:00
Arun Kumar
89fc5e19d9 Desktop: Fixes #7881: Fixed icon when note is dragged across notebooks (#7924) 2023-03-15 14:33:59 +00:00
Arun Kumar
6a3bf51084 Desktop: Resolves #7889: Add support for --safe-mode command line flag (#7919) 2023-03-14 11:46:21 +00:00
Laurent Cozic
df1e298c84 Doc: Mentioned Safe Mode in debugging doc 2023-03-14 11:41:30 +00:00
Joplin Bot
b9c706324b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-14 06:18:40 +00:00
Laurent Cozic
ab7e2de1d9 Update renovate.json5 2023-03-14 01:29:44 +00:00
Joplin Bot
7ef8753b94 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-14 00:40:45 +00:00
Laurent Cozic
d48a5efa03 Doc: Update coding style 2023-03-13 19:31:22 +00:00
Laurent Cozic
0804b62ffb Merge branch 'release-2.10' into dev 2023-03-13 18:48:05 +00:00
Laurent Cozic
37995b9ec7 Desktop release v2.10.10 2023-03-13 18:46:58 +00:00
Julien
7a3e6fde7f Desktop: Fixes #7907: Fixed height when controls are on a single row (#7912) 2023-03-13 16:26:56 +00:00
Laurent Cozic
bd4291462e Desktop: Fix issue where search bar can randomly lose focus while searching 2023-03-13 12:19:22 +00:00
Laurent Cozic
489d6778db Desktop: Fix issue where search bar can randomly lose focus while searching 2023-03-13 12:18:47 +00:00
renovate[bot]
538a1413d9 Update dependency nodemon to v2.0.21 (#7908)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-12 22:39:35 +00:00
Joplin Bot
3c9b755045 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-12 18:16:33 +00:00
Laurent Cozic
fd7b345efa Merge branch 'release-2.10' into dev 2023-03-12 15:44:02 +00:00
Laurent Cozic
c96468149a Desktop release v2.10.9 2023-03-12 15:37:38 +00:00
pedr
d6d4897e1c Desktop: Resolves #7880: Paste as Text only working on hotkeys on Windows (#7886) 2023-03-12 15:33:16 +00:00
Julien
e07e248fea Desktop: Always show new note buttons (Regression) (#7850) 2023-03-12 15:30:41 +00:00
Julien
b561460307 Desktop: Resolves #7848: Made note list controls responsive (#7884) 2023-03-12 15:26:15 +00:00
Self Not Found
f6d1a27f51 All: Fixes #7851: Encode the non-ASCII characters in OneDrive URI (#7868) 2023-03-12 15:21:31 +00:00
Tao Klerks
80a1500634 Desktop: Fixes #7741: With Custom Sort, new notes appear at bottom and later randomly "pop" to the top (#7765) 2023-03-12 15:16:45 +00:00
Laurent Cozic
bcb578c933 Doc: Update sponsors 2023-03-12 12:38:08 +00:00
Laurent Cozic
75ad454971 Doc: Make website work on more resolutions and added LinkedIn link 2023-03-12 11:53:44 +00:00
renovate[bot]
e90b7f2d81 Update dependency styled-components to v5.3.8 (#7906)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-12 10:17:05 +00:00
renovate[bot]
ffca11ca8a Update dependency react-native-share to v8.2.1 (#7904)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-11 13:28:41 +00:00
renovate[bot]
bd98951d32 Update dependency react-native-image-picker to v5.1.0 (#7896) 2023-03-10 12:54:53 +00:00
pedr
9106fb82f3 Chore: Desktop: Resolves #7879: Paste as Text shortcut pasting content twice (#7885) 2023-03-10 12:53:48 +00:00
Arun Kumar
5c6e17bc89 Desktop: Fixes #4124: Fix note list blank space display problems (#7888) 2023-03-10 12:06:30 +00:00
Arun Kumar
538e9e9b4e Desktop: Fixes #7506: Linux notebook display bug (#7897) 2023-03-10 12:01:35 +00:00
Laurent Cozic
d871b3c7d6 Tools: Include packages that have been updated by Renovate in changelog 2023-03-09 17:50:57 +00:00
Laurent Cozic
99c6c9b411 Tools: Add more eslint/jest rules 2023-03-09 17:50:56 +00:00
Laurent Cozic
3eca4ada5a Tools: Add eslint rule "jest/require-top-level-describe" 2023-03-09 17:50:56 +00:00
Laurent Cozic
19431abc73 Server v2.10.10 2023-03-09 14:37:27 +00:00
renovate[bot]
3e299f1ab9 Update dependency yargs to v17.7.1 (#7892)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-09 01:14:55 +00:00
renovate[bot]
42873d3829 Update dependency yargs to v17.7.0 (#7890) 2023-03-08 22:17:54 +00:00
renovate[bot]
4983327f90 Update dependency node-persist to v3.1.3 (#7887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-07 21:28:30 +00:00
Mr-Kanister
fddbe6cf6c All: Translation: Update de_DE.po (#7883) 2023-03-07 13:22:43 -05:00
Laurent Cozic
57f00c612d Desktop: Resolves #7867: Cache code blocks in notes to speed up rendering 2023-03-07 17:55:20 +00:00
Joplin Bot
235288e903 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-07 12:23:39 +00:00
Laurent Cozic
38be744c3e Tools: Update stats script so that it runs at the beginning of the month 2023-03-07 10:01:24 +00:00
Laurent Cozic
2384ec8792 Chore: Disable flaky test 2023-03-06 17:54:01 +00:00
Laurent Cozic
85f7caa0eb Merge branch 'release-2.10' into dev 2023-03-06 15:48:49 +00:00
Laurent Cozic
ad0f0414c4 iOS 12.10.5 2023-03-06 15:48:23 +00:00
Laurent Cozic
437320f90c lock file 2023-03-06 14:24:14 +00:00
Laurent Cozic
c1db7182ac Tools: Add class member accessibility modifiers and converted rule @typescript-eslint/explicit-member-accessibility to an error 2023-03-06 14:22:37 +00:00
renovate[bot]
aa4af69afc Update dependency yeoman-generator to v5.8.0 (#7878) 2023-03-06 14:00:03 +00:00
renovate[bot]
21a39af97b Update dependency node-persist to v3.1.2 (#7876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-06 00:10:07 +00:00
Laurent Cozic
0812cc5944 Doc: Typo 2023-03-04 12:19:23 +00:00
renovate[bot]
1fb5d2c6c5 Update dependency raw-body to v2.5.2 (#7869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-04 08:26:49 +00:00
Laurent Cozic
1b9901d232 Doc: Update coding style 2023-03-03 12:09:45 +00:00
Laurent Cozic
f15d2793cc Doc: Update coding style
Removed rules that are not enforced by esling,
and added new ones
2023-03-03 11:45:01 +00:00
Self Not Found
ad4d71dbe1 Desktop: Fixes #7831: Skip the resources which haven't been downloaded yet when exporting (#7843) 2023-03-03 11:31:49 +00:00
Julien
4bee6ffc90 Desktop: Always show new note buttons (Regression) (#7850) 2023-03-03 11:28:12 +00:00
Julien
01f63b3d97 Desktop: Fix lines alignment when sort order buttons are disabled (Regression) (#7849) 2023-03-03 11:27:11 +00:00
renovate[bot]
b19b590efc Update dependency open to v8.4.2 (#7860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-03 01:06:42 +00:00
Joplin Bot
6b7577f94d Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-02 15:19:57 +00:00
Laurent Cozic
4d09b14522 Doc: Added new for JdLL 2023 2023-03-02 13:27:02 +00:00
Laurent Cozic
9f1e95324d Doc: Added new for JdLL 2023 2023-03-02 13:25:46 +00:00
renovate[bot]
f98314346d Update contributor-assistant/github-action action to v2.3.0 (#7857) 2023-03-01 12:57:31 +00:00
Arun Kumar
fa659b615a Doc: Fix minor typo in technical spec documentation (#7856) 2023-03-01 05:42:30 -05:00
github-actions[bot]
e4aafa7edb @palerdot has signed the CLA from Pull Request #7856 2023-03-01 10:00:41 +00:00
renovate[bot]
811c40b074 Update dependency sass to v1.58.3 (#7852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-28 23:41:07 +00:00
Laurent Cozic
405c528ef0 Android 2.10.8 2023-02-28 18:19:09 +00:00
Laurent Cozic
a176216374 Update translations 2023-02-28 18:02:09 +00:00
renovate[bot]
8d0b090f66 Update dependency madge to v6 (#7853) 2023-02-28 15:06:57 +00:00
Joplin Bot
337c5ed61c Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-28 00:45:05 +00:00
Joplin Bot
b32d39860b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-27 18:19:06 +00:00
renovate[bot]
a091608f72 Update dependency react-native-share to v8.2.0 (#7845) 2023-02-27 12:22:55 +00:00
renovate[bot]
9686ee7833 Update dependency react-native-paper to v5.2.0 (#7844) 2023-02-27 12:22:38 +00:00
renovate[bot]
3b55dcac65 Update dependency sass to v1.58.2 (#7846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-27 06:00:02 +00:00
Joplin Bot
d524d11d8a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-26 18:16:41 +00:00
Tao Klerks
9c080ec631 Desktop: Fixes #7776: Drag-dropping notes to top or bottom, in custom sort, is finicky (#7777) 2023-02-26 15:40:13 +00:00
Laurent Cozic
da11476fd7 CLI v2.10.3 2023-02-26 13:04:03 +00:00
Laurent Cozic
d157b9cfc7 Cli: Fixed "sync" command when calling it in non-interactive mode 2023-02-26 13:02:50 +00:00
Laurent Cozic
bc8c61748a CLI v2.10.2 2023-02-26 12:41:43 +00:00
Laurent Cozic
3ad727889b Lock file 2023-02-26 12:40:57 +00:00
Laurent Cozic
8df128bb7a Releasing sub-packages 2023-02-26 12:37:29 +00:00
Laurent Cozic
5a4568f4db CI: Fixed publishall 2023-02-26 12:33:59 +00:00
Laurent Cozic
170295919a CI: Fixed publishall 2023-02-26 12:17:17 +00:00
Laurent Cozic
2262cbfdfd Desktop release v2.10.8 2023-02-26 12:15:07 +00:00
Andrej Lifinzew
43e40bcf5a CLI: Resolves #1728: Create subnotebooks (#6722) 2023-02-26 12:13:45 +00:00
Helmut K. C. Tessarek
e60595f0de All: Translation: Update da_DK.po (thanks ERYpTION) 2023-02-25 16:54:55 -05:00
renovate[bot]
527a7c115d Update jest monorepo to v29.4.3 (#7841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 20:42:10 +00:00
Laurent Cozic
c22f910500 Merge branch 'release-2.10' into dev 2023-02-25 16:50:09 +00:00
Laurent Cozic
667b7969ff CLI v2.10.1 2023-02-25 16:49:54 +00:00
Laurent Cozic
1bbf065142 Releasing sub-packages 2023-02-25 16:47:37 +00:00
renovate[bot]
606cad3c3a Update dependency react-native-image-picker to v5.0.2 (#7829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 07:42:17 +00:00
renovate[bot]
c9612dc8b8 Update dependency lint-staged to v13.1.2 (#7832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 04:15:09 +00:00
renovate[bot]
6ee30e22a6 Update dependency @react-native-community/datetimepicker to v6.7.5 (#7835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 02:11:33 +00:00
Laurent Cozic
a6536e1ef9 Chore: Improve logging on api/notes 2023-02-24 18:50:04 +00:00
Laurent Cozic
92cf5abd08 Desktop: Fixed clipping certain pages that contain images within links 2023-02-24 18:49:27 +00:00
Laurent Cozic
d1e545ac2c Desktop: Note background does not change when theme automatically updated via system 2023-02-24 18:00:14 +00:00
Laurent Cozic
ec6567c68d Update translations 2023-02-24 15:30:26 +00:00
Joplin Bot
3b236307f7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-24 12:23:01 +00:00
Laurent Cozic
14ac54bf3d Update translations 2023-02-24 12:07:30 +00:00
Laurent Cozic
e53b796523 iOS 12.10.4 2023-02-24 11:50:40 +00:00
Laurent Cozic
9d189b2b34 iOS 12.10.4 2023-02-24 11:50:16 +00:00
renovate[bot]
84545cf26d Update dependency sass to v1.58.1 (#7818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-24 10:39:20 +00:00
Joplin Bot
c427a6d0a5 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-24 06:18:07 +00:00
Laurent Cozic
42dc6e1ea6 Desktop release v2.10.7 2023-02-23 13:52:29 +00:00
351 changed files with 25772 additions and 25216 deletions

View File

@@ -66,6 +66,7 @@ packages/lib/welcomeAssets.js
packages/plugins/**/api packages/plugins/**/api
packages/plugins/**/dist packages/plugins/**/dist
packages/server/dist/ packages/server/dist/
packages/utils/dist/
packages/tools/node_modules packages/tools/node_modules
packages/tools/PortableAppsLauncher packages/tools/PortableAppsLauncher
packages/turndown-plugin-gfm/ packages/turndown-plugin-gfm/
@@ -79,6 +80,8 @@ packages/app-cli/app/LinkSelector.js
packages/app-cli/app/base-command.js packages/app-cli/app/base-command.js
packages/app-cli/app/command-done.test.js packages/app-cli/app/command-done.test.js
packages/app-cli/app/command-e2ee.js packages/app-cli/app/command-e2ee.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-settingschema.js packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-sync.js packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js packages/app-cli/app/command-testing.js
@@ -393,6 +396,7 @@ packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js packages/app-mobile/components/NoteEditor/SearchPanel.js
packages/app-mobile/components/NoteEditor/SelectionFormatting.js packages/app-mobile/components/NoteEditor/SelectionFormatting.js
packages/app-mobile/components/NoteEditor/types.js packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteList.js
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
@@ -402,6 +406,7 @@ packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js packages/app-mobile/components/TextInput.js
packages/app-mobile/components/app-nav.js packages/app-mobile/components/app-nav.js
packages/app-mobile/components/biometrics/BiometricPopup.js packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/getResponsiveValue.test.js packages/app-mobile/components/getResponsiveValue.test.js
@@ -707,6 +712,7 @@ packages/lib/services/searchengine/SearchFilter.test.js
packages/lib/services/searchengine/filterParser.js packages/lib/services/searchengine/filterParser.js
packages/lib/services/searchengine/filterParser.test.js packages/lib/services/searchengine/filterParser.test.js
packages/lib/services/searchengine/gotoAnythingStyleQuery.js packages/lib/services/searchengine/gotoAnythingStyleQuery.js
packages/lib/services/searchengine/gotoAnythingStyleQuery.test.js
packages/lib/services/searchengine/queryBuilder.js packages/lib/services/searchengine/queryBuilder.js
packages/lib/services/share/ShareService.js packages/lib/services/share/ShareService.js
packages/lib/services/share/ShareService.test.js packages/lib/services/share/ShareService.test.js
@@ -833,6 +839,7 @@ packages/renderer/index.js
packages/renderer/noteStyle.js packages/renderer/noteStyle.js
packages/renderer/pathUtils.js packages/renderer/pathUtils.js
packages/renderer/utils.js packages/renderer/utils.js
packages/tools/build-release-stats.js
packages/tools/buildServerDocker.js packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.test.js packages/tools/buildServerDocker.test.js
packages/tools/bundleDefaultPlugins.js packages/tools/bundleDefaultPlugins.js
@@ -843,6 +850,7 @@ packages/tools/convertThemesToCss.js
packages/tools/generate-database-types.js packages/tools/generate-database-types.js
packages/tools/generate-images.js packages/tools/generate-images.js
packages/tools/git-changelog.js packages/tools/git-changelog.js
packages/tools/git-changelog.test.js
packages/tools/licenseChecker.js packages/tools/licenseChecker.js
packages/tools/release-android.js packages/tools/release-android.js
packages/tools/release-cli.js packages/tools/release-cli.js
@@ -858,6 +866,7 @@ packages/tools/update-readme-download.js
packages/tools/update-readme-sponsors.js packages/tools/update-readme-sponsors.js
packages/tools/updateMarkdownDoc.js packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.js packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/translation.js packages/tools/utils/translation.js
packages/tools/website/build.js packages/tools/website/build.js
packages/tools/website/buildTranslations.js packages/tools/website/buildTranslations.js
@@ -865,6 +874,8 @@ packages/tools/website/updateDownloadPage.js
packages/tools/website/updateNews.js packages/tools/website/updateNews.js
packages/tools/website/utils/applyTranslations.js packages/tools/website/utils/applyTranslations.js
packages/tools/website/utils/applyTranslations.test.js packages/tools/website/utils/applyTranslations.test.js
packages/tools/website/utils/convertLinksToLocale.js
packages/tools/website/utils/convertLinksToLocale.test.js
packages/tools/website/utils/frontMatter.js packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/news.js packages/tools/website/utils/news.js
packages/tools/website/utils/openGraph.js packages/tools/website/utils/openGraph.js

View File

@@ -91,6 +91,10 @@ module.exports = {
// Disable because of this: https://github.com/facebook/react/issues/16265 // Disable because of this: https://github.com/facebook/react/issues/16265
// "react-hooks/exhaustive-deps": "warn", // "react-hooks/exhaustive-deps": "warn",
'jest/require-top-level-describe': ['error', { 'maxNumberOfTopLevelDescribes': 1 }],
'jest/no-identical-title': ['error'],
'jest/prefer-lowercase-title': ['error', { 'ignoreTopLevelDescribe': true }],
'promise/prefer-await-to-then': 'error', 'promise/prefer-await-to-then': 'error',
'no-unneeded-ternary': 'error', 'no-unneeded-ternary': 'error',
@@ -155,6 +159,7 @@ module.exports = {
// 'react-hooks', // 'react-hooks',
'import', 'import',
'promise', 'promise',
'jest',
], ],
'overrides': [ 'overrides': [
{ {
@@ -175,9 +180,7 @@ module.exports = {
'project': './tsconfig.eslint.json', 'project': './tsconfig.eslint.json',
}, },
'rules': { 'rules': {
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we '@typescript-eslint/explicit-member-accessibility': ['error'],
// make everything public which is not great. New code however should specify member accessibility.
'@typescript-eslint/explicit-member-accessibility': ['warn'],
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }], '@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }], '@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
'@typescript-eslint/comma-dangle': ['error', { '@typescript-eslint/comma-dangle': ['error', {

View File

@@ -6,6 +6,7 @@ on: [push, pull_request]
jobs: jobs:
pre_job: pre_job:
if: github.repository == 'laurent22/joplin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }} should_skip: ${{ steps.skip_check.outputs.should_skip }}
@@ -16,6 +17,7 @@ jobs:
concurrent_skipping: 'same_content_newer' concurrent_skipping: 'same_content_newer'
BuildAndroidDebug: BuildAndroidDebug:
if: github.repository == 'laurent22/joplin'
needs: pre_job needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true' if: needs.pre_job.outputs.should_skip != 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

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

View File

@@ -6,6 +6,7 @@ permissions:
issues: write issues: write
jobs: jobs:
ProcessStaleIssues: ProcessStaleIssues:
if: github.repository == 'laurent22/joplin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v4 - uses: actions/stale@v4

View File

@@ -2,6 +2,7 @@ name: Joplin Continuous Integration
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
pre_job: pre_job:
if: github.repository == 'laurent22/joplin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }} should_skip: ${{ steps.skip_check.outputs.should_skip }}
@@ -14,7 +15,7 @@ jobs:
Main: Main:
needs: pre_job needs: pre_job
# We always process server or desktop release tags, because they also publish the release # We always process server or desktop release tags, because they also publish the release
if: needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/server-v') || startsWith(github.ref, 'refs/tags/v') if: github.repository == 'laurent22/joplin' && (needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/server-v') || startsWith(github.ref, 'refs/tags/v'))
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
@@ -70,7 +71,9 @@ jobs:
- uses: olegtarasov/get-tag@v2.1 - uses: olegtarasov/get-tag@v2.1
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '18' # 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'
- name: Install Yarn - name: Install Yarn
run: | run: |
@@ -92,6 +95,7 @@ jobs:
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }} APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }} CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }} GH_TOKEN: ${{ secrets.GH_TOKEN }}
@@ -129,7 +133,7 @@ jobs:
ServerDockerImage: ServerDockerImage:
needs: pre_job needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true' if: github.repository == 'laurent22/joplin' && needs.pre_job.outputs.should_skip != 'true'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:

10
.gitignore vendored
View File

@@ -67,6 +67,8 @@ packages/app-cli/app/LinkSelector.js
packages/app-cli/app/base-command.js packages/app-cli/app/base-command.js
packages/app-cli/app/command-done.test.js packages/app-cli/app/command-done.test.js
packages/app-cli/app/command-e2ee.js packages/app-cli/app/command-e2ee.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-settingschema.js packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-sync.js packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js packages/app-cli/app/command-testing.js
@@ -381,6 +383,7 @@ packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js packages/app-mobile/components/NoteEditor/SearchPanel.js
packages/app-mobile/components/NoteEditor/SelectionFormatting.js packages/app-mobile/components/NoteEditor/SelectionFormatting.js
packages/app-mobile/components/NoteEditor/types.js packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteList.js
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
@@ -390,6 +393,7 @@ packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js packages/app-mobile/components/TextInput.js
packages/app-mobile/components/app-nav.js packages/app-mobile/components/app-nav.js
packages/app-mobile/components/biometrics/BiometricPopup.js packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/getResponsiveValue.test.js packages/app-mobile/components/getResponsiveValue.test.js
@@ -695,6 +699,7 @@ packages/lib/services/searchengine/SearchFilter.test.js
packages/lib/services/searchengine/filterParser.js packages/lib/services/searchengine/filterParser.js
packages/lib/services/searchengine/filterParser.test.js packages/lib/services/searchengine/filterParser.test.js
packages/lib/services/searchengine/gotoAnythingStyleQuery.js packages/lib/services/searchengine/gotoAnythingStyleQuery.js
packages/lib/services/searchengine/gotoAnythingStyleQuery.test.js
packages/lib/services/searchengine/queryBuilder.js packages/lib/services/searchengine/queryBuilder.js
packages/lib/services/share/ShareService.js packages/lib/services/share/ShareService.js
packages/lib/services/share/ShareService.test.js packages/lib/services/share/ShareService.test.js
@@ -821,6 +826,7 @@ packages/renderer/index.js
packages/renderer/noteStyle.js packages/renderer/noteStyle.js
packages/renderer/pathUtils.js packages/renderer/pathUtils.js
packages/renderer/utils.js packages/renderer/utils.js
packages/tools/build-release-stats.js
packages/tools/buildServerDocker.js packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.test.js packages/tools/buildServerDocker.test.js
packages/tools/bundleDefaultPlugins.js packages/tools/bundleDefaultPlugins.js
@@ -831,6 +837,7 @@ packages/tools/convertThemesToCss.js
packages/tools/generate-database-types.js packages/tools/generate-database-types.js
packages/tools/generate-images.js packages/tools/generate-images.js
packages/tools/git-changelog.js packages/tools/git-changelog.js
packages/tools/git-changelog.test.js
packages/tools/licenseChecker.js packages/tools/licenseChecker.js
packages/tools/release-android.js packages/tools/release-android.js
packages/tools/release-cli.js packages/tools/release-cli.js
@@ -846,6 +853,7 @@ packages/tools/update-readme-download.js
packages/tools/update-readme-sponsors.js packages/tools/update-readme-sponsors.js
packages/tools/updateMarkdownDoc.js packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.js packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/translation.js packages/tools/utils/translation.js
packages/tools/website/build.js packages/tools/website/build.js
packages/tools/website/buildTranslations.js packages/tools/website/buildTranslations.js
@@ -853,6 +861,8 @@ packages/tools/website/updateDownloadPage.js
packages/tools/website/updateNews.js packages/tools/website/updateNews.js
packages/tools/website/utils/applyTranslations.js packages/tools/website/utils/applyTranslations.js
packages/tools/website/utils/applyTranslations.test.js packages/tools/website/utils/applyTranslations.test.js
packages/tools/website/utils/convertLinksToLocale.js
packages/tools/website/utils/convertLinksToLocale.test.js
packages/tools/website/utils/frontMatter.js packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/news.js packages/tools/website/utils/news.js
packages/tools/website/utils/openGraph.js packages/tools/website/utils/openGraph.js

View File

@@ -14,7 +14,8 @@
"@joplin/turndown-plugin-gfm", "@joplin/turndown-plugin-gfm",
"@joplin/tools", "@joplin/tools",
"@joplin/react-native-saf-x", "@joplin/react-native-saf-x",
"@joplin/react-native-alarm-notification" "@joplin/react-native-alarm-notification",
"@joplin/utils"
] ]
} }
] ]

View File

@@ -0,0 +1,30 @@
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
index a8abd71833879201e3438b2fa51d712a311c4551..ffe9c2c6dfa5c703ba76b65d94d5dd6784102c19 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
@@ -591,7 +591,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
// ignored.printStackTrace();
}
- RNFetchBlobFileResp rnFetchBlobFileResp = (RNFetchBlobFileResp) responseBody;
+ RNFetchBlobFileResp rnFetchBlobFileResp = new RNFetchBlobFileResp(responseBody);
if(rnFetchBlobFileResp != null && !rnFetchBlobFileResp.isDownloadComplete()){
callback.invoke("Download interrupted.", null);
diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
index 2470eef612308c15a89dfea5a1f16937469be29f..965f8becc195965907699182c764ec9e51811450 100644
--- a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
+++ b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
@@ -35,6 +35,12 @@ public class RNFetchBlobFileResp extends ResponseBody {
FileOutputStream ofStream;
boolean isEndMarkerReceived;
+ // ref: https://github.com/joltup/rn-fetch-blob/issues/490#issuecomment-990899440
+ public RNFetchBlobFileResp(ResponseBody body) {
+ super();
+ this.originalBody = body;
+ }
+
public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path, boolean overwrite) throws IOException {
super();
this.rctContext = ctx;

View File

@@ -657,6 +657,16 @@ footer .bottom-links-row p {
font-size: 1.1em; font-size: 1.1em;
} }
.language-switcher {
display: inline;
}
.language-switcher > button {
border: none;
background-color: transparent;
color: #0557ba;
}
/***************************************************************** /*****************************************************************
WHAT'S NEW PAGE WHAT'S NEW PAGE
*****************************************************************/ *****************************************************************/
@@ -780,6 +790,7 @@ footer .bottom-links-row p {
#menu-mobile .social-links .social-link-mastodon, #menu-mobile .social-links .social-link-mastodon,
#menu-mobile .social-links .social-link-reddit, #menu-mobile .social-links .social-link-reddit,
#menu-mobile .social-links .social-link-linkedin,
#menu-mobile .social-links .social-link-patreon { #menu-mobile .social-links .social-link-patreon {
display: none; display: none;
} }
@@ -947,6 +958,41 @@ footer .bottom-links-row p {
} }
} }
/*****************************************************************
MORE NARROW VIEW
eg for Galaxy S9
*****************************************************************/
@media (max-width: 580px) {
#nav-section .plans-button {
display: none;
}
}
/*****************************************************************
MORE NARROW VIEW
eg for Galaxy S9
*****************************************************************/
@media (max-width: 400px) {
#nav-section .navbar-mobile-content a.sponsor-button .sponsor-button-label {
font-size: 12px;
}
#nav-section .navbar-mobile-content a.sponsor-button {
padding: 2px 6px;
margin-right: 0.2em;
}
#nav-section a {
margin-left: 5px;
}
}
/***************************************************************** /*****************************************************************
VERY NARROW VIEW VERY NARROW VIEW
eg for Galaxy Fold eg for Galaxy Fold
@@ -968,6 +1014,15 @@ footer .bottom-links-row p {
margin-left: 4px; margin-left: 4px;
} }
div.navbar-mobile-content a.sponsor-button {
margin-right: 10px;
}
#nav-section .button-link {
padding-left: 0;
padding-right: 0;
}
} }
/***************************************************************** /*****************************************************************

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 16 Jan 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in &quot;real time&quot;... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</p> <?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Thu, 02 Mar 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Thu, 02 Mar 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin will participate in JdLL 2023!]]></title><description><![CDATA[<p>On 1 and 2 April 2023, we will have a stand for Joplin at the <a href="https://www.jdll.org/">Journées du Logiciel Libre</a> in Lyon, France. The JdLL has been taking place in Lyon for 24 years and is a popular open source conference in France. We had a stand in 2020 and 2021 but that was cancelled due to Covid, so this year is a first for Joplin!</p>
<p>Admission is free, so don't hesitate to come and meet us, exchange ideas and learn more about Joplin!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230202-jdll.jpg" alt="Joplin at JdLL 2023"></p>
]]></description><link>https://joplinapp.org/news/20230302-jdll-2023/</link><guid isPermaLink="false">20230302-jdll-2023</guid><pubDate>Thu, 02 Mar 2023 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in &quot;real time&quot;... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</p>
<p>Thankfully GitHub provides an alternative access: the raw logs. This is much better because they will open as plain text, without any styling or JS magic, which means you can use the browser native search and it will be fast.</p> <p>Thankfully GitHub provides an alternative access: the raw logs. This is much better because they will open as plain text, without any styling or JS magic, which means you can use the browser native search and it will be fast.</p>
<p>But now the problem is that raw logs look like this:</p> <p>But now the problem is that raw logs look like this:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png" alt="Raw log without extension"></p> <p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png" alt="Raw log without extension"></p>
@@ -294,9 +297,4 @@
<p>This release also includes about 30 various bug fixes and improvements.</p> <p>This release also includes about 30 various bug fixes and improvements.</p>
<p>A notable one is a fix for GotoAnything, which recently wasn't working on first try.</p> <p>A notable one is a fix for GotoAnything, which recently wasn't working on first try.</p>
<p>The plugin screen has also been improved so that search works even when GitHub is down or blocked, as it is in China in particular.</p> <p>The plugin screen has also been improved so that search works even when GitHub is down or blocked, as it is in China in particular.</p>
]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing recommended plugins in the next Joplin version]]></title><description><![CDATA[<p>A common request from new users is how to know which plugin is safe to install or not. In fact probably all of them are safe but as a new user that's not necessarily easy to know. So to help with this, the next version of Joplin will support recommended plugins - those will be plugins that meet our standards of quality and performance, and they will be indicated by a small crown tag inside the plugin box. Recommended plugins will also appear on top when searching.</p> ]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210901-113415_0.png" alt=""></p>
<p>For now, since we don't have a review process, the recommended plugins are those developed by the Joplin team and frequent contributors, because we know those are safe to use.</p>
<p>Later we might have a review process and add more recommended plugins. That being said, in the meantime even if a plugin is not marked as recommended, there's a good chance it is still safe and have good performance too. Often you can search for it on the forum and if it's active with many users commenting, you're most likely good to go.</p>
<p>But if there's any doubt, the recommended tag is a good way to be sure.</p>
]]></description><link>https://joplinapp.org/news/20210901-113415/</link><guid isPermaLink="false">20210901-113415</guid><pubDate>Wed, 01 Sep 2021 11:34:15 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>

View File

@@ -421,7 +421,7 @@
</div> </div>
<script <script
src="{{jsBaseUrl}}/bootstrap5.0.2.min.js" src="{{jsBaseUrl}}/bootstrap5.0.2.bundle.min.js"
rel="preload" rel="preload"
as="script" as="script"
></script> ></script>

View File

@@ -85,6 +85,11 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
{{> footer}} {{> footer}}
</div> </div>
<script
src="{{jsBaseUrl}}/bootstrap5.0.2.bundle.min.js"
rel="preload"
as="script"
></script>
<script src="{{{assetUrls.js.script}}}"></script> <script src="{{{assetUrls.js.script}}}"></script>
{{> analytics}} {{> analytics}}

View File

@@ -17,6 +17,21 @@
<a href="{{baseUrl}}/help/" class="fw500">Help</a> <a href="{{baseUrl}}/help/" class="fw500">Help</a>
<a href="{{forumUrl}}" class="fw500">Forum</a> <a href="{{forumUrl}}" class="fw500">Forum</a>
<a href="{{baseUrl}}/cn/" class="fw500">中文</a> <a href="{{baseUrl}}/cn/" class="fw500">中文</a>
<!--
<div class="dropdown language-switcher">
<button class="fw500" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
Language
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</div>
-->
{{#showJoplinCloudLinks}} {{#showJoplinCloudLinks}}
{{> joplinCloudButton}} {{> joplinCloudButton}}
{{/showJoplinCloudLinks}} {{/showJoplinCloudLinks}}
@@ -24,7 +39,8 @@
</div> </div>
<div class="col-9 text-right d-block d-md-none navbar-mobile-content"> <div class="col-9 text-right d-block d-md-none navbar-mobile-content">
{{> twitterLink}} {{> twitterLink}}
<a href="{{baseUrl}}/cn/" class="fw500">中文</a> <a href="{{baseUrl}}/cn/" class="fw500 chinese-page-link">中文</a>
{{> joplinCloudButton}}
{{> supportButton}} {{> supportButton}}
<span class="pointer" <span class="pointer"

View File

@@ -4,6 +4,7 @@
<a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a> <a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
<a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a> <a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
<a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a> <a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
<a class="social-link-linkedin" href="https://www.linkedin.com/company/joplin" title="Joplin LinkedIn Feed"><i class="fab fa-linkedin"></i></a>
<a class="social-link-reddit" href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a> <a class="social-link-reddit" href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
<a class="social-link-github" href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a> <a class="social-link-github" href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
</div> </div>

View File

@@ -1 +1 @@
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500"><i class="fab fa-twitter"></i></a> <a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500 twitter-link"><i class="fab fa-twitter"></i></a>

View File

@@ -30,6 +30,7 @@ COPY packages/fork-uslug ./packages/fork-uslug
COPY packages/htmlpack ./packages/htmlpack COPY packages/htmlpack ./packages/htmlpack
COPY packages/renderer ./packages/renderer COPY packages/renderer ./packages/renderer
COPY packages/tools ./packages/tools COPY packages/tools ./packages/tools
COPY packages/utils ./packages/utils
COPY packages/lib ./packages/lib COPY packages/lib ./packages/lib
COPY packages/server ./packages/server COPY packages/server ./packages/server

View File

@@ -64,7 +64,7 @@ A community maintained list of these distributions can be found here: [Unofficia
# Sponsors # Sponsors
<!-- SPONSORS-ORG --> <!-- 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://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://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://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a>
<!-- SPONSORS-ORG --> <!-- SPONSORS-ORG -->
* * * * * *
@@ -72,14 +72,11 @@ A community maintained list of these distributions can be found here: [Unofficia
<!-- SPONSORS-GITHUB --> <!-- SPONSORS-GITHUB -->
| | | | | | | | | |
| :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) | | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) | <img width="50" src="https://avatars2.githubusercontent.com/u/2793530?s=96&v=4"/></br>[CyberXZT](https://github.com/CyberXZT) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/82579431?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl) | <img width="50" src="https://avatars2.githubusercontent.com/u/808091?s=96&v=4"/></br>[cuongtransc](https://github.com/cuongtransc) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) | | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) | <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) | | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | | <img width="50" src="https://avatars2.githubusercontent.com/u/126279083?s=96&v=4"/></br>[matmoly](https://github.com/matmoly) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/333944?s=96&v=4"/></br>[tateisu](https://github.com/tateisu) | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/77214738?s=96&v=4"/></br>[Polymathic-Company](https://github.com/Polymathic-Company) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/54626606?s=96&v=4"/></br>[skyrunner15](https://github.com/skyrunner15) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | | |
<!-- SPONSORS-GITHUB --> <!-- SPONSORS-GITHUB -->
<!-- TOC --> <!-- TOC -->
@@ -129,6 +126,7 @@ A community maintained list of these distributions can be found here: [Unofficia
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md) - [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md) - [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md) - [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
- [Synchronisation spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync.md)
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md) - [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md) - [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md) - [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
@@ -505,6 +503,7 @@ Name | Description
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon [Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there [Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
[Discord server](https://discord.gg/VSj7AFHvpq) | Our chat server [Discord server](https://discord.gg/VSj7AFHvpq) | Our chat server
[LinkedIn](https://www.linkedin.com/company/joplin) | Our LinkedIn page
[Sub-reddit](https://www.reddit.com/r/joplinapp/) | Also a good place to get help [Sub-reddit](https://www.reddit.com/r/joplinapp/) | Also a good place to get help
# Contributing # Contributing
@@ -530,47 +529,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED --> <!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done &nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|--- ---|---|---|---|---
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 81% <img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 80%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 23% <img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 23%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 58% <img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 58%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 45% <img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 45%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 90% <img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 78% <img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 77%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 97% <img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 98% <img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 45% <img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 44%
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100% <img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100% <img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 89% <img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Villaverde](mailto:teko.gr@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 26% <img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 25%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 96% <img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 95%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100% <img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 29% <img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 29%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 81% <img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78% <img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 79% <img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 79%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 89% <img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 89% <img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 56% <img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 55%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 89% <img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 73% <img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 51% <img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 51%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 81% <img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 80%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 97% <img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 37% <img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 36%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79% <img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 73% <img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 72%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89% <img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 81% <img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Dmitriy Q](mailto:krotesk@mail.ru) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 66% <img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 65%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [KaneGreen](mailto:737445366KG@Gmail.com) | 95% <img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [wh201906](mailto:wh201906@yandex.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 90% <img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 89%
<!-- LOCALE-TABLE-AUTO-GENERATED --> <!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors # Contributors

7
bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +1,52 @@
const gulp = require('gulp'); const gulp = require('gulp');
const utils = require('./packages/tools/gulp/utils'); const execa = require('execa');
const { stdout } = require('process');
const execCommand = async (executableName, args, options = null) => {
options = {
showInput: true,
showStdout: true,
showStderr: true,
quiet: false,
...options,
};
if (options.quiet) {
options.showInput = false;
options.showStdout = false;
options.showStderr = false;
}
if (options.showInput) {
stdout.write(`> ${executableName} ${args.join(' ')}\n`);
}
const promise = execa(executableName, args);
if (options.showStdout && promise.stdout) promise.stdout.pipe(process.stdout);
if (options.showStderr && promise.stderr) promise.stderr.pipe(process.stderr);
const result = await promise;
return result.stdout.trim();
};
const tasks = { const tasks = {
updateIgnoredTypeScriptBuild: require('./packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
completePublishAll: { completePublishAll: {
fn: async () => { fn: async () => {
await execCommand('git', ['add', '-A']);
await utils.execCommandVerbose('git', ['add', '-A']); await execCommand('git', ['commit', '-m', 'Releasing sub-packages']);
await utils.execCommandVerbose('git', ['commit', '-m', 'Releasing sub-packages']);
// Lerna does some unnecessary auth check that doesn't work with // Lerna does some unnecessary auth check that doesn't work with
// automation tokens, thus the --no-verify-access. Automation token // automation tokens, thus the --no-verify-access. Automation token
// is still used for access when publishing even with this flag // is still used for access when publishing even with this flag
// (publishing would fail otherwise). // (publishing would fail otherwise).
// https://github.com/lerna/lerna/issues/2788 // https://github.com/lerna/lerna/issues/2788
await utils.execCommandVerbose('lerna', ['publish', 'from-package', '-y', '--no-verify-access']); await execCommand('lerna', ['publish', 'from-package', '-y', '--no-verify-access']);
await utils.execCommandVerbose('git', ['push']); await execCommand('yarn', ['install']);
await execCommand('git', ['add', '-A']);
await execCommand('git', ['commit', '-m', 'Lock file']);
await execCommand('git', ['push']);
}, },
}, },
build: { build: {
@@ -30,12 +59,14 @@ const tasks = {
// faster, especially when having to rebuild after adding a // faster, especially when having to rebuild after adding a
// dependency. // dependency.
if (process.env.BUILD_SEQUENCIAL === '1') { if (process.env.BUILD_SEQUENCIAL === '1') {
await utils.execCommandVerbose('yarn', ['run', 'buildSequential']); await execCommand('yarn', ['run', 'buildSequential']);
} else { } else {
await utils.execCommandVerbose('yarn', ['run', 'buildParallel']); await execCommand('yarn', ['run', 'buildParallel']);
} }
}, },
}, },
}; };
utils.registerGulpTasks(gulp, tasks); for (const taskName in tasks) {
gulp.task(taskName, tasks[taskName].fn);
}

View File

@@ -329,6 +329,7 @@
"packages/renderer/MdToHtml/rules/sanitize_html.js": true, "packages/renderer/MdToHtml/rules/sanitize_html.js": true,
"packages/server/db-*.sqlite": true, "packages/server/db-*.sqlite": true,
"packages/server/dist/": true, "packages/server/dist/": true,
"packages/utils/dist/": true,
"packages/server/temp": true, "packages/server/temp": true,
"packages/server/test.pid": true, "packages/server/test.pid": true,
"phpunit.xml": true, "phpunit.xml": true,

View File

@@ -15,7 +15,7 @@
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn run tsc", "buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn run tsc",
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn run tsc", "buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn run tsc",
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md", "buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
"buildCommandIndex": "gulp buildCommandIndex", "buildCommandIndex": "node packages/tools/gulp/tasks/buildCommandIndexRun.js",
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../joplin-website/docs/api/references/plugin_api packages/lib/services/plugins/api/", "buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../joplin-website/docs/api/references/plugin_api packages/lib/services/plugins/api/",
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc", "updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
"updateNews": "node ./packages/tools/website/updateNews", "updateNews": "node ./packages/tools/website/updateNews",
@@ -53,7 +53,7 @@
"test-ci": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test-ci", "test-ci": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test-ci",
"test": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test", "test": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test",
"tsc": "yarn workspaces foreach --parallel --verbose --interlaced run tsc", "tsc": "yarn workspaces foreach --parallel --verbose --interlaced run tsc",
"updateIgnored": "gulp updateIgnoredTypeScriptBuild", "updateIgnored": "node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js",
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh", "updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
"watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch", "watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch",
"watchWebsite": "nodemon --verbose --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --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\"" "watchWebsite": "nodemon --verbose --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --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\""
@@ -64,6 +64,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@joplin/utils": "~2.11",
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0", "@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "5.48.2", "@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2", "@typescript-eslint/parser": "5.48.2",
@@ -71,15 +72,17 @@
"eslint": "8.31.0", "eslint": "8.31.0",
"eslint-interactive": "10.3.0", "eslint-interactive": "10.3.0",
"eslint-plugin-import": "2.27.4", "eslint-plugin-import": "2.27.4",
"eslint-plugin-jest": "27.2.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.32.0", "eslint-plugin-react": "7.32.0",
"fs-extra": "11.1.0", "execa": "5.1.1",
"fs-extra": "11.1.1",
"glob": "8.1.0", "glob": "8.1.0",
"gulp": "4.0.2", "gulp": "4.0.2",
"husky": "3.1.0", "husky": "3.1.0",
"lerna": "3.22.1", "lerna": "3.22.1",
"lint-staged": "13.1.1", "lint-staged": "13.2.0",
"madge": "5.0.2", "madge": "6.0.0",
"npm-package-json-lint": "6.4.0", "npm-package-json-lint": "6.4.0",
"typedoc": "0.17.8", "typedoc": "0.17.8",
"typescript": "4.9.4" "typescript": "4.9.4"
@@ -88,10 +91,11 @@
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"http-server": "14.1.1", "http-server": "14.1.1",
"node-gyp": "9.3.1", "node-gyp": "9.3.1",
"nodemon": "2.0.20" "nodemon": "2.0.22"
}, },
"packageManager": "yarn@3.3.1", "packageManager": "yarn@3.3.1",
"resolutions": { "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-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
"rn-fetch-blob@0.12.0": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch"
} }
} }

View File

@@ -6,14 +6,14 @@ interface LinkStoreEntry {
} }
class LinkSelector { class LinkSelector {
noteId_: string; private noteId_: string;
scrollTop_: number; private scrollTop_: number;
renderedText_: string; private renderedText_: string;
currentLinkIndex_: number; private currentLinkIndex_: number;
linkStore_: LinkStoreEntry[]; private linkStore_: LinkStoreEntry[];
linkRegex_: RegExp; private linkRegex_: RegExp;
constructor() { public constructor() {
this.noteId_ = null; this.noteId_ = null;
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
this.renderedText_ = null; this.renderedText_ = null;
@@ -22,22 +22,22 @@ class LinkSelector {
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g; this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
} }
get link(): string | null { public get link(): string | null {
if (this.currentLinkIndex_ === null) return null; if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].link; return this.linkStore_[this.currentLinkIndex_].link;
} }
get noteX(): number | null { public get noteX(): number | null {
if (this.currentLinkIndex_ === null) return null; if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteX; return this.linkStore_[this.currentLinkIndex_].noteX;
} }
get noteY(): number | null { public get noteY(): number | null {
if (this.currentLinkIndex_ === null) return null; if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteY; return this.linkStore_[this.currentLinkIndex_].noteY;
} }
findLinks(renderedText: string): LinkStoreEntry[] { public findLinks(renderedText: string): LinkStoreEntry[] {
const newLinkStore: LinkStoreEntry[] = []; const newLinkStore: LinkStoreEntry[] = [];
const lines: string[] = renderedText.split('\n'); const lines: string[] = renderedText.split('\n');
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
@@ -56,19 +56,19 @@ class LinkSelector {
return newLinkStore; return newLinkStore;
} }
updateText(renderedText: string): void { public updateText(renderedText: string): void {
this.currentLinkIndex_ = null; this.currentLinkIndex_ = null;
this.renderedText_ = renderedText; this.renderedText_ = renderedText;
this.linkStore_ = this.findLinks(this.renderedText_); this.linkStore_ = this.findLinks(this.renderedText_);
} }
updateNote(textWidget: any): void { public updateNote(textWidget: any): void {
this.noteId_ = textWidget.noteId; this.noteId_ = textWidget.noteId;
this.scrollTop_ = textWidget.scrollTop_; this.scrollTop_ = textWidget.scrollTop_;
this.updateText(textWidget.renderedText_); this.updateText(textWidget.renderedText_);
} }
scrollWidget(textWidget: any): void { public scrollWidget(textWidget: any): void {
if (this.currentLinkIndex_ === null) return; if (this.currentLinkIndex_ === null) return;
const noteY = this.linkStore_[this.currentLinkIndex_].noteY; const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
@@ -93,7 +93,7 @@ class LinkSelector {
return; return;
} }
changeLink(textWidget: any, offset: number): void | null { public changeLink(textWidget: any, offset: number): void | null {
if (textWidget.noteId !== this.noteId_) { if (textWidget.noteId !== this.noteId_) {
this.updateNote(textWidget); this.updateNote(textWidget);
this.changeLink(textWidget, offset); this.changeLink(textWidget, offset);
@@ -123,7 +123,7 @@ class LinkSelector {
return; return;
} }
openLink(textWidget: any): void { public openLink(textWidget: any): void {
if (textWidget.noteId !== this.noteId_) return; if (textWidget.noteId !== this.noteId_) return;
if (textWidget.renderedText_ !== this.renderedText_) return; if (textWidget.renderedText_ !== this.renderedText_) return;
if (textWidget.scrollTop_ !== this.scrollTop_) return; if (textWidget.scrollTop_ !== this.scrollTop_) return;

View File

@@ -8,7 +8,7 @@ const Resource = require('@joplin/lib/models/Resource').default;
const Setting = require('@joplin/lib/models/Setting').default; const Setting = require('@joplin/lib/models/Setting').default;
const reducer = require('@joplin/lib/reducer').default; const reducer = require('@joplin/lib/reducer').default;
const { defaultState } = require('@joplin/lib/reducer'); const { defaultState } = require('@joplin/lib/reducer');
const { splitCommandString } = require('@joplin/lib/string-utils.js'); const { splitCommandString } = require('@joplin/utils');
const { reg } = require('@joplin/lib/registry.js'); const { reg } = require('@joplin/lib/registry.js');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const shim = require('@joplin/lib/shim').default; const shim = require('@joplin/lib/shim').default;

View File

@@ -9,7 +9,8 @@ const Tag = require('@joplin/lib/models/Tag').default;
const Setting = require('@joplin/lib/models/Setting').default; const Setting = require('@joplin/lib/models/Setting').default;
const { reg } = require('@joplin/lib/registry.js'); const { reg } = require('@joplin/lib/registry.js');
const { fileExtension } = require('@joplin/lib/path-utils'); const { fileExtension } = require('@joplin/lib/path-utils');
const { splitCommandString, splitCommandBatch } = require('@joplin/lib/string-utils'); const { splitCommandString } = require('@joplin/utils');
const { splitCommandBatch } = require('@joplin/lib/string-utils');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { cliUtils } = require('./cli-utils.js'); const { cliUtils } = require('./cli-utils.js');

View File

@@ -1,97 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const locale_1 = require("@joplin/lib/locale");
const registry_js_1 = require("@joplin/lib/registry.js");
class BaseCommand {
constructor() {
this.stdout_ = null;
this.prompt_ = null;
}
usage() {
throw new Error('Usage not defined');
}
encryptionCheck(item) {
if (item && item.encryption_applied)
throw new Error((0, locale_1._)('Cannot change encrypted item'));
}
description() {
throw new Error('Description not defined');
}
action(_args) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error('Action not defined');
});
}
compatibleUis() {
return ['cli', 'gui'];
}
supportsUi(ui) {
return this.compatibleUis().indexOf(ui) >= 0;
}
options() {
return [];
}
hidden() {
return false;
}
enabled() {
return true;
}
cancellable() {
return false;
}
cancel() {
return __awaiter(this, void 0, void 0, function* () { });
}
name() {
const r = this.usage().split(' ');
return r[0];
}
setDispatcher(fn) {
this.dispatcher_ = fn;
}
dispatch(action) {
if (!this.dispatcher_)
throw new Error('Dispatcher not defined');
return this.dispatcher_(action);
}
setStdout(fn) {
this.stdout_ = fn;
}
stdout(text) {
if (this.stdout_)
this.stdout_(text);
}
setPrompt(fn) {
this.prompt_ = fn;
}
prompt(message, options = null) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.prompt_)
throw new Error('Prompt is undefined');
return yield this.prompt_(message, options);
});
}
metadata() {
return {
name: this.name(),
usage: this.usage(),
options: this.options(),
hidden: this.hidden(),
};
}
logger() {
return registry_js_1.reg.logger();
}
}
exports.default = BaseCommand;
//# sourceMappingURL=base-command.js.map

View File

@@ -7,80 +7,80 @@ export default class BaseCommand {
protected prompt_: any = null; protected prompt_: any = null;
protected dispatcher_: any; protected dispatcher_: any;
usage(): string { public usage(): string {
throw new Error('Usage not defined'); throw new Error('Usage not defined');
} }
encryptionCheck(item: any) { public encryptionCheck(item: any) {
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item')); if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
} }
description() { public description() {
throw new Error('Description not defined'); throw new Error('Description not defined');
} }
async action(_args: any) { public async action(_args: any) {
throw new Error('Action not defined'); throw new Error('Action not defined');
} }
compatibleUis() { public compatibleUis() {
return ['cli', 'gui']; return ['cli', 'gui'];
} }
supportsUi(ui: string) { public supportsUi(ui: string) {
return this.compatibleUis().indexOf(ui) >= 0; return this.compatibleUis().indexOf(ui) >= 0;
} }
options(): any[] { public options(): any[] {
return []; return [];
} }
hidden() { public hidden() {
return false; return false;
} }
enabled() { public enabled() {
return true; return true;
} }
cancellable() { public cancellable() {
return false; return false;
} }
async cancel() {} public async cancel() {}
name() { public name() {
const r = this.usage().split(' '); const r = this.usage().split(' ');
return r[0]; return r[0];
} }
setDispatcher(fn: Function) { public setDispatcher(fn: Function) {
this.dispatcher_ = fn; this.dispatcher_ = fn;
} }
dispatch(action: any) { public dispatch(action: any) {
if (!this.dispatcher_) throw new Error('Dispatcher not defined'); if (!this.dispatcher_) throw new Error('Dispatcher not defined');
return this.dispatcher_(action); return this.dispatcher_(action);
} }
setStdout(fn: Function) { public setStdout(fn: Function) {
this.stdout_ = fn; this.stdout_ = fn;
} }
stdout(text: string) { public stdout(text: string) {
if (this.stdout_) this.stdout_(text); if (this.stdout_) this.stdout_(text);
} }
setPrompt(fn: Function) { public setPrompt(fn: Function) {
this.prompt_ = fn; this.prompt_ = fn;
} }
async prompt(message: string, options: any = null) { public async prompt(message: string, options: any = null) {
if (!this.prompt_) throw new Error('Prompt is undefined'); if (!this.prompt_) throw new Error('Prompt is undefined');
return await this.prompt_(message, options); return await this.prompt_(message, options);
} }
metadata() { public metadata() {
return { return {
name: this.name(), name: this.name(),
usage: this.usage(), usage: this.usage(),
@@ -89,7 +89,7 @@ export default class BaseCommand {
}; };
} }
logger() { public logger() {
return reg.logger(); return reg.logger();
} }
} }

View File

@@ -12,15 +12,15 @@ const imageType = require('image-type');
const readChunk = require('read-chunk'); const readChunk = require('read-chunk');
class Command extends BaseCommand { class Command extends BaseCommand {
usage() { public usage() {
return 'e2ee <command> [path]'; return 'e2ee <command> [path]';
} }
description() { public description() {
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.'); // `generate-ppk` return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.'); // `generate-ppk`
} }
options() { public options() {
return [ return [
// This is here mostly for testing - shouldn't be used // This is here mostly for testing - shouldn't be used
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'], ['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
@@ -30,7 +30,7 @@ class Command extends BaseCommand {
]; ];
} }
async action(args: any) { public async action(args: any) {
const options = args.options; const options = args.options;
const askForMasterKey = async (error: any) => { const askForMasterKey = async (error: any) => {

View File

@@ -1,6 +1,6 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const BaseCommand = require('./base-command').default; const BaseCommand = require('./base-command').default;
const { splitCommandString } = require('@joplin/lib/string-utils.js'); const { splitCommandString } = require('@joplin/utils');
const uuid = require('@joplin/lib/uuid').default; const uuid = require('@joplin/lib/uuid').default;
const { app } = require('./app.js'); const { app } = require('./app.js');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');

View File

@@ -1,21 +0,0 @@
const BaseCommand = require('./base-command').default;
const { app } = require('./app.js');
const { _ } = require('@joplin/lib/locale');
const Folder = require('@joplin/lib/models/Folder').default;
class Command extends BaseCommand {
usage() {
return 'mkbook <new-notebook>';
}
description() {
return _('Creates a new notebook.');
}
async action(args) {
const folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
app().switchCurrentFolder(folder);
}
}
module.exports = Command;

View File

@@ -0,0 +1,50 @@
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import { setupCommandForTesting, setupApplication } from './utils/testUtils';
import Folder from '@joplin/lib/models/Folder';
const Command = require('./command-mkbook');
describe('command-mkbook', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
});
it('should create a subfolder in first folder', async () => {
const command = setupCommandForTesting(Command);
await command.action({ 'new-notebook': 'folder1', options: {} });
await command.action({ 'new-notebook': 'folder1_1', options: { parent: 'folder1' } });
const folder1 = await Folder.loadByTitle('folder1');
const folder1_1 = await Folder.loadByTitle('folder1_1');
expect(folder1.title).toBe('folder1');
expect(folder1_1.parent_id).toBe(folder1.id);
});
it('should not be possible to create a subfolder without an argument.', async () => {
const command = setupCommandForTesting(Command);
await command.action({ 'new-notebook': 'folder2', options: {} });
await expect(command.action({ 'new-notebook': 'folder2_1', options: { parent: true } })).rejects.toThrowError();
});
it('should not be possible to create subfolder in ambiguous destination folder', async () => {
const command = setupCommandForTesting(Command);
await command.action({ 'new-notebook': 'folder3', options: {} });
await command.action({ 'new-notebook': 'folder3', options: {} }); // ambiguous folder
await expect(command.action({ 'new-notebook': 'folder3_1', options: { parent: 'folder3' } })).rejects.toThrowError();
// check if duplicate entries have been created.
const folderAll = await Folder.all();
const folders3 = folderAll.filter(x => x.title === 'folder3');
expect(folders3.length).toBe(2);
// check if something has been created in one of the duplicate entries.
expect(await Folder.childrenIds(folders3[0].id)).toEqual([]);
expect(await Folder.childrenIds(folders3[1].id)).toEqual([]);
});
});

View File

@@ -0,0 +1,65 @@
const BaseCommand = require('./base-command').default;
const { app } = require('./app.js');
import { _ } from '@joplin/lib/locale';
import BaseModel from '@joplin/lib/BaseModel';
import Folder from '@joplin/lib/models/Folder';
import { FolderEntity } from '@joplin/lib/services/database/types';
class Command extends BaseCommand {
public usage() {
return 'mkbook <new-notebook>';
}
public description() {
return _('Creates a new notebook.');
}
public options() {
return [
['-p, --parent <parent-notebook>', _('Create a new notebook under a parent notebook.')],
];
}
// validDestinationFolder check for presents and ambiguous folders
public async validDestinationFolder(targetFolder: string) {
const destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, targetFolder);
if (!destinationFolder) {
throw new Error(_('Cannot find: "%s"', targetFolder));
}
const destinationDups = await Folder.search({ titlePattern: targetFolder, limit: 2 });
if (destinationDups.length > 1) {
throw new Error(_('Ambiguous notebook "%s". Please use short notebook id instead - press "ti" to see the short notebook id', targetFolder));
}
return destinationFolder;
}
public async saveAndSwitchFolder(newFolder: FolderEntity) {
const folder = await Folder.save(newFolder, { userSideValidation: true });
app().switchCurrentFolder(folder);
}
public async action(args: any) {
const targetFolder = args.options.parent;
const newFolder: FolderEntity = {
title: args['new-notebook'],
};
if (targetFolder) {
const destinationFolder = await this.validDestinationFolder(targetFolder);
newFolder.parent_id = destinationFolder.id;
await this.saveAndSwitchFolder(newFolder);
} else {
await this.saveAndSwitchFolder(newFolder);
}
}
}
module.exports = Command;

View File

@@ -23,19 +23,19 @@ function settingTypeToSchemaType(type: SettingItemType): string {
} }
class Command extends BaseCommand { class Command extends BaseCommand {
usage() { public usage() {
return 'settingschema <file>'; return 'settingschema <file>';
} }
description() { public description() {
return 'Build the setting schema file'; return 'Build the setting schema file';
} }
enabled() { public enabled() {
return false; return false;
} }
async action(args: any) { public async action(args: any) {
const schema: Record<string, any> = { const schema: Record<string, any> = {
title: 'JSON schema for Joplin setting files', title: 'JSON schema for Joplin setting files',
'$id': Setting.schemaUrl, '$id': Setting.schemaUrl,

View File

@@ -9,11 +9,11 @@ import { appTypeToLockType } from '@joplin/lib/services/synchronizer/LockHandler
const BaseCommand = require('./base-command').default; const BaseCommand = require('./base-command').default;
const { app } = require('./app.js'); const { app } = require('./app.js');
const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js'); const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js');
const { reg } = require('@joplin/lib/registry.js'); import { reg } from '@joplin/lib/registry';
const { cliUtils } = require('./cli-utils.js'); const { cliUtils } = require('./cli-utils.js');
const md5 = require('md5'); const md5 = require('md5');
const locker = require('proper-lockfile'); import * as locker from 'proper-lockfile';
const fs = require('fs-extra'); import { pathExists, writeFile } from 'fs-extra';
class Command extends BaseCommand { class Command extends BaseCommand {
@@ -21,15 +21,15 @@ class Command extends BaseCommand {
private releaseLockFn_: Function = null; private releaseLockFn_: Function = null;
private oneDriveApiUtils_: any = null; private oneDriveApiUtils_: any = null;
usage() { public usage() {
return 'sync'; return 'sync';
} }
description() { public description() {
return _('Synchronises with remote storage.'); return _('Synchronises with remote storage.');
} }
options() { public options() {
return [ return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')], ['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
['--upgrade', _('Upgrade the sync target to the latest version.')], ['--upgrade', _('Upgrade the sync target to the latest version.')],
@@ -37,24 +37,15 @@ class Command extends BaseCommand {
]; ];
} }
static async lockFile(filePath: string): Promise<Function> { private static async lockFile(filePath: string) {
return locker.lock(filePath, { stale: 1000 * 60 * 5 }); return locker.lock(filePath, { stale: 1000 * 60 * 5 });
} }
static isLocked(filePath: string) { private static async isLocked(filePath: string) {
return new Promise((resolve, reject) => { return locker.check(filePath);
locker.check(filePath, (error: any, isLocked: boolean) => {
if (error) {
reject(error);
return;
}
resolve(isLocked);
});
});
} }
async doAuth() { public async doAuth() {
const syncTarget = reg.syncTarget(this.syncTargetId_); const syncTarget = reg.syncTarget(this.syncTargetId_);
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_); const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
@@ -98,23 +89,23 @@ class Command extends BaseCommand {
return false; return false;
} }
cancelAuth() { public cancelAuth() {
if (this.oneDriveApiUtils_) { if (this.oneDriveApiUtils_) {
this.oneDriveApiUtils_.cancelOAuthDance(); this.oneDriveApiUtils_.cancelOAuthDance();
return; return;
} }
} }
doingAuth() { public doingAuth() {
return !!this.oneDriveApiUtils_; return !!this.oneDriveApiUtils_;
} }
async action(args: any) { public async action(args: any) {
this.releaseLockFn_ = null; this.releaseLockFn_ = null;
// Lock is unique per profile/database // Lock is unique per profile/database
const lockFilePath = `${require('os').tmpdir()}/synclock_${md5(escape(Setting.value('profileDir')))}`; // https://github.com/pvorb/node-md5/issues/41 const lockFilePath = `${require('os').tmpdir()}/synclock_${md5(escape(Setting.value('profileDir')))}`; // https://github.com/pvorb/node-md5/issues/41
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock'); if (!(await pathExists(lockFilePath))) await writeFile(lockFilePath, 'synclock');
const useLock = args.options.useLock !== 0; const useLock = args.options.useLock !== 0;
@@ -247,7 +238,7 @@ class Command extends BaseCommand {
cleanUp(); cleanUp();
} }
async cancel() { public async cancel() {
if (this.doingAuth()) { if (this.doingAuth()) {
this.cancelAuth(); this.cancelAuth();
return; return;
@@ -272,7 +263,7 @@ class Command extends BaseCommand {
this.syncTargetId_ = null; this.syncTargetId_ = null;
} }
cancellable() { public cancellable() {
return true; return true;
} }
} }

View File

@@ -18,19 +18,19 @@ function itemCount(args: any) {
} }
class Command extends BaseCommand { class Command extends BaseCommand {
usage() { public usage() {
return 'testing <command> [arg0]'; return 'testing <command> [arg0]';
} }
description() { public description() {
return 'testing'; return 'testing';
} }
enabled() { public enabled() {
return false; return false;
} }
options(): any[] { public options(): any[] {
return [ return [
['--folder-count <count>', 'Folders to create'], ['--folder-count <count>', 'Folders to create'],
['--note-count <count>', 'Notes to create'], ['--note-count <count>', 'Notes to create'],
@@ -40,7 +40,7 @@ class Command extends BaseCommand {
]; ];
} }
async action(args: any) { public async action(args: any) {
const { command, options } = args; const { command, options } = args;
if (command === 'populate') { if (command === 'populate') {

View File

@@ -5,7 +5,7 @@ const stripAnsi = require('strip-ansi');
const { handleAutocompletion } = require('../autocompletion.js'); const { handleAutocompletion } = require('../autocompletion.js');
export default class StatusBarWidget extends BaseWidget { export default class StatusBarWidget extends BaseWidget {
constructor() { public constructor() {
super(); super();
this.promptState_ = null; this.promptState_ = null;
@@ -14,20 +14,20 @@ export default class StatusBarWidget extends BaseWidget {
this.items_ = []; this.items_ = [];
} }
get name() { public get name() {
return 'statusBar'; return 'statusBar';
} }
get canHaveFocus() { public get canHaveFocus() {
return false; return false;
} }
setItemAt(index: number, text: string) { public setItemAt(index: number, text: string) {
this.items_[index] = stripAnsi(text).trim(); this.items_[index] = stripAnsi(text).trim();
this.invalidate(); this.invalidate();
} }
async prompt(initialText = '', promptString: any = null, options: any = null) { public async prompt(initialText = '', promptString: any = null, options: any = null) {
if (this.promptState_) throw new Error('Another prompt already active'); if (this.promptState_) throw new Error('Another prompt already active');
if (promptString === null) promptString = ':'; if (promptString === null) promptString = ':';
if (options === null) options = {}; if (options === null) options = {};
@@ -53,15 +53,15 @@ export default class StatusBarWidget extends BaseWidget {
return this.promptState_.promise; return this.promptState_.promise;
} }
get promptActive() { public get promptActive() {
return !!this.promptState_; return !!this.promptState_;
} }
get history() { public get history() {
return this.history_; return this.history_;
} }
resetCursor() { public resetCursor() {
if (!this.promptActive) return; if (!this.promptActive) return;
if (!this.inputEventEmitter_) return; if (!this.inputEventEmitter_) return;
@@ -70,7 +70,7 @@ export default class StatusBarWidget extends BaseWidget {
this.term.moveTo(this.absoluteInnerX + termutils.textLength(this.promptState_.promptString) + this.inputEventEmitter_.getInput().length, this.absoluteInnerY); this.term.moveTo(this.absoluteInnerX + termutils.textLength(this.promptState_.promptString) + this.inputEventEmitter_.getInput().length, this.absoluteInnerY);
} }
render() { public render() {
super.render(); super.render();
const doSaveCursor = !this.promptActive; const doSaveCursor = !this.promptActive;

View File

@@ -35,7 +35,7 @@ export default class PluginRunner extends BasePluginRunner {
private eventHandlers_: EventHandlers = {}; private eventHandlers_: EventHandlers = {};
private activeSandboxCalls_: any = {}; private activeSandboxCalls_: any = {};
constructor() { public constructor() {
super(); super();
this.eventHandler = this.eventHandler.bind(this); this.eventHandler = this.eventHandler.bind(this);
@@ -64,7 +64,7 @@ export default class PluginRunner extends BasePluginRunner {
}; };
} }
async run(plugin: Plugin, sandbox: Global): Promise<void> { public async run(plugin: Plugin, sandbox: Global): Promise<void> {
return new Promise((resolve: Function, reject: Function) => { return new Promise((resolve: Function, reject: Function) => {
const onStarted = () => { const onStarted = () => {
plugin.off('started', onStarted); plugin.off('started', onStarted);

View File

@@ -30,34 +30,36 @@
2019, 2019,
2020, 2020,
2021, 2021,
2022 2022,
2023
], ],
"owner": "Laurent Cozic" "owner": "Laurent Cozic"
}, },
"version": "2.10.0", "version": "2.11.0",
"bin": "./main.js", "bin": "./main.js",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
"dependencies": { "dependencies": {
"@joplin/lib": "~2.10", "@joplin/lib": "~2.11",
"@joplin/renderer": "~2.10", "@joplin/renderer": "~2.11",
"@joplin/utils": "~2.11",
"aws-sdk": "2.1290.0", "aws-sdk": "2.1290.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"compare-version": "0.1.2", "compare-version": "0.1.2",
"fs-extra": "11.1.0", "fs-extra": "11.1.1",
"html-entities": "1.4.0", "html-entities": "1.4.0",
"image-type": "3.1.0", "image-type": "3.1.0",
"keytar": "7.9.0", "keytar": "7.9.0",
"md5": "2.3.0", "md5": "2.3.0",
"node-rsa": "1.1.1", "node-rsa": "1.1.1",
"open": "8.4.1", "open": "8.4.2",
"proper-lockfile": "4.1.2", "proper-lockfile": "4.1.2",
"read-chunk": "2.1.0", "read-chunk": "2.1.0",
"server-destroy": "1.0.1", "server-destroy": "1.0.1",
"sharp": "0.31.3", "sharp": "0.31.3",
"sprintf-js": "1.1.2", "sprintf-js": "1.1.2",
"sqlite3": "5.1.4", "sqlite3": "5.1.6",
"string-padding": "1.0.2", "string-padding": "1.0.2",
"strip-ansi": "6.0.1", "strip-ansi": "6.0.1",
"tcp-port-used": "1.0.2", "tcp-port-used": "1.0.2",
@@ -68,12 +70,13 @@
"yargs-parser": "21.1.1" "yargs-parser": "21.1.1"
}, },
"devDependencies": { "devDependencies": {
"@joplin/tools": "~2.10", "@joplin/tools": "~2.11",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/jest": "29.2.6", "@types/jest": "29.2.6",
"@types/node": "18.11.18", "@types/node": "18.11.18",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2", "gulp": "4.0.2",
"jest": "29.4.2", "jest": "29.4.3",
"temp": "0.9.4", "temp": "0.9.4",
"typescript": "4.9.4" "typescript": "4.9.4"
} }

View File

@@ -1,3 +1,5 @@
/* eslint-disable jest/require-top-level-describe */
import KeychainService from '@joplin/lib/services/keychain/KeychainService'; import KeychainService from '@joplin/lib/services/keychain/KeychainService';
import shim from '@joplin/lib/shim'; import shim from '@joplin/lib/shim';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';

View File

@@ -47,9 +47,11 @@ describe('services_plugins_RepositoryApi', () => {
it('should tell if a plugin can be updated', (async () => { it('should tell if a plugin can be updated', (async () => {
const api = await newRepoApi(); const api = await newRepoApi();
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.0')).toBe(true);
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.2')).toBe(false); expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.0', '3.0.0')).toBe(true);
expect(await api.pluginCanBeUpdated('does.not.exist', '1.0.0')).toBe(false); expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.0', '1.0.0')).toBe(false);
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.2', '3.0.0')).toBe(false);
expect(await api.pluginCanBeUpdated('does.not.exist', '1.0.0', '3.0.0')).toBe(false);
})); }));
}); });

View File

@@ -19,7 +19,7 @@
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import { homedir } from 'os'; import { homedir } from 'os';
import { execCommand2 } from '@joplin/tools/tool-utils'; import { execCommand } from '@joplin/utils';
import { chdir } from 'process'; import { chdir } from 'process';
const minUserNum = 1; const minUserNum = 1;
@@ -66,7 +66,7 @@ const processUser = async (userNum: number) => {
await chdir(cliDir); await chdir(cliDir);
await execCommand2(['yarn', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]); await execCommand(['yarn', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]);
} catch (error) { } catch (error) {
console.error(`Could not process user ${userNum}:`, error); console.error(`Could not process user ${userNum}:`, error);
} finally { } finally {
@@ -90,7 +90,7 @@ const main = async () => {
// Build the app once before starting, because we'll use start-no-build to // Build the app once before starting, because we'll use start-no-build to
// run the scripts (faster) // run the scripts (faster)
await execCommand2(['yarn', 'run', 'build']); await execCommand(['yarn', 'run', 'build']);
const focusUserNum = 0; const focusUserNum = 0;

View File

@@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Joplin Web Clipper [DEV]", "name": "Joplin Web Clipper [DEV]",
"version": "2.10.0", "version": "2.11.2",
"description": "Capture and save web pages and screenshots from your browser to Joplin.", "description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org", "homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'", "content_security_policy": "script-src 'self'; object-src 'self'",

View File

@@ -126,4 +126,4 @@
"react-app" "react-app"
] ]
} }
} }

View File

@@ -33,7 +33,7 @@ export default class ElectronAppWrapper {
private pluginWindows_: PluginWindows = {}; private pluginWindows_: PluginWindows = {};
private initialCallbackUrl_: string = null; private initialCallbackUrl_: string = null;
constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) { public constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
this.electronApp_ = electronApp; this.electronApp_ = electronApp;
this.env_ = env; this.env_ = env;
this.isDebugMode_ = isDebugMode; this.isDebugMode_ = isDebugMode;
@@ -41,31 +41,31 @@ export default class ElectronAppWrapper {
this.initialCallbackUrl_ = initialCallbackUrl; this.initialCallbackUrl_ = initialCallbackUrl;
} }
electronApp() { public electronApp() {
return this.electronApp_; return this.electronApp_;
} }
setLogger(v: Logger) { public setLogger(v: Logger) {
this.logger_ = v; this.logger_ = v;
} }
logger() { public logger() {
return this.logger_; return this.logger_;
} }
window() { public window() {
return this.win_; return this.win_;
} }
env() { public env() {
return this.env_; return this.env_;
} }
initialCallbackUrl() { public initialCallbackUrl() {
return this.initialCallbackUrl_; return this.initialCallbackUrl_;
} }
createWindow() { public createWindow() {
// Set to true to view errors if the application does not start // Set to true to view errors if the application does not start
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_; const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
@@ -236,11 +236,11 @@ export default class ElectronAppWrapper {
} }
} }
registerPluginWindow(pluginId: string, window: any) { public registerPluginWindow(pluginId: string, window: any) {
this.pluginWindows_[pluginId] = window; this.pluginWindows_[pluginId] = window;
} }
async waitForElectronAppReady() { public async waitForElectronAppReady() {
if (this.electronApp().isReady()) return Promise.resolve(); if (this.electronApp().isReady()) return Promise.resolve();
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
@@ -253,25 +253,25 @@ export default class ElectronAppWrapper {
}); });
} }
quit() { public quit() {
this.electronApp_.quit(); this.electronApp_.quit();
} }
exit(errorCode = 0) { public exit(errorCode = 0) {
this.electronApp_.exit(errorCode); this.electronApp_.exit(errorCode);
} }
trayShown() { public trayShown() {
return !!this.tray_; return !!this.tray_;
} }
// This method is used in macOS only to hide the whole app (and not just the main window) // This method is used in macOS only to hide the whole app (and not just the main window)
// including the menu bar. This follows the macOS way of hiding an app. // including the menu bar. This follows the macOS way of hiding an app.
hide() { public hide() {
this.electronApp_.hide(); this.electronApp_.hide();
} }
buildDir() { public buildDir() {
if (this.buildDir_) return this.buildDir_; if (this.buildDir_) return this.buildDir_;
let dir = `${__dirname}/build`; let dir = `${__dirname}/build`;
if (!fs.pathExistsSync(dir)) { if (!fs.pathExistsSync(dir)) {
@@ -283,7 +283,7 @@ export default class ElectronAppWrapper {
return dir; return dir;
} }
trayIconFilename_() { private trayIconFilename_() {
let output = ''; let output = '';
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
@@ -298,7 +298,7 @@ export default class ElectronAppWrapper {
} }
// Note: this must be called only after the "ready" event of the app has been dispatched // Note: this must be called only after the "ready" event of the app has been dispatched
createTray(contextMenu: any) { public createTray(contextMenu: any) {
try { try {
this.tray_ = new Tray(`${this.buildDir()}/icons/${this.trayIconFilename_()}`); this.tray_ = new Tray(`${this.buildDir()}/icons/${this.trayIconFilename_()}`);
this.tray_.setToolTip(this.electronApp_.name); this.tray_.setToolTip(this.electronApp_.name);
@@ -312,13 +312,13 @@ export default class ElectronAppWrapper {
} }
} }
destroyTray() { public destroyTray() {
if (!this.tray_) return; if (!this.tray_) return;
this.tray_.destroy(); this.tray_.destroy();
this.tray_ = null; this.tray_ = null;
} }
ensureSingleInstance() { public ensureSingleInstance() {
if (this.env_ === 'dev') return false; if (this.env_ === 'dev') return false;
const gotTheLock = this.electronApp_.requestSingleInstanceLock(); const gotTheLock = this.electronApp_.requestSingleInstanceLock();
@@ -347,7 +347,7 @@ export default class ElectronAppWrapper {
return false; return false;
} }
async start() { public async start() {
// Since we are doing other async things before creating the window, we might miss // Since we are doing other async things before creating the window, we might miss
// the "ready" event. So we use the function below to make sure that the app is ready. // the "ready" event. So we use the function below to make sure that the app is ready.
await this.waitForElectronAppReady(); await this.waitForElectronAppReady();
@@ -375,7 +375,7 @@ export default class ElectronAppWrapper {
}); });
} }
async openCallbackUrl(url: string) { public async openCallbackUrl(url: string) {
this.win_.webContents.send('asynchronous-message', 'openCallbackUrl', { this.win_.webContents.send('asynchronous-message', 'openCallbackUrl', {
url: url, url: url,
}); });

View File

@@ -3,7 +3,7 @@ import appReducer, { createAppDefaultState } from './app.reducer';
describe('app.reducer', () => { describe('app.reducer', () => {
it('DIALOG_OPEN', async () => { it('should handle DIALOG_OPEN', async () => {
const state: AppState = createAppDefaultState({}, {}); const state: AppState = createAppDefaultState({}, {});
let newState = appReducer(state, { let newState = appReducer(state, {

View File

@@ -21,7 +21,7 @@ export class Bridge {
private electronWrapper_: ElectronAppWrapper; private electronWrapper_: ElectronAppWrapper;
private lastSelectedPaths_: LastSelectedPath; private lastSelectedPaths_: LastSelectedPath;
constructor(electronWrapper: ElectronAppWrapper) { public constructor(electronWrapper: ElectronAppWrapper) {
this.electronWrapper_ = electronWrapper; this.electronWrapper_ = electronWrapper;
this.lastSelectedPaths_ = { this.lastSelectedPaths_ = {
file: null, file: null,
@@ -29,11 +29,11 @@ export class Bridge {
}; };
} }
electronApp() { public electronApp() {
return this.electronWrapper_; return this.electronWrapper_;
} }
electronIsDev() { public electronIsDev() {
return !this.electronApp().electronApp().isPackaged; return !this.electronApp().electronApp().isPackaged;
} }
@@ -60,11 +60,11 @@ export class Bridge {
return `${__dirname}/vendor`; return `${__dirname}/vendor`;
} }
env() { public env() {
return this.electronWrapper_.env(); return this.electronWrapper_.env();
} }
processArgv() { public processArgv() {
return process.argv; return process.argv;
} }
@@ -114,44 +114,44 @@ export class Bridge {
}); });
} }
window() { public window() {
return this.electronWrapper_.window(); return this.electronWrapper_.window();
} }
showItemInFolder(fullPath: string) { public showItemInFolder(fullPath: string) {
return require('electron').shell.showItemInFolder(toSystemSlashes(fullPath)); return require('electron').shell.showItemInFolder(toSystemSlashes(fullPath));
} }
newBrowserWindow(options: any) { public newBrowserWindow(options: any) {
return new BrowserWindow(options); return new BrowserWindow(options);
} }
windowContentSize() { public windowContentSize() {
if (!this.window()) return { width: 0, height: 0 }; if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getContentSize(); const s = this.window().getContentSize();
return { width: s[0], height: s[1] }; return { width: s[0], height: s[1] };
} }
windowSize() { public windowSize() {
if (!this.window()) return { width: 0, height: 0 }; if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getSize(); const s = this.window().getSize();
return { width: s[0], height: s[1] }; return { width: s[0], height: s[1] };
} }
windowSetSize(width: number, height: number) { public windowSetSize(width: number, height: number) {
if (!this.window()) return; if (!this.window()) return;
return this.window().setSize(width, height); return this.window().setSize(width, height);
} }
openDevTools() { public openDevTools() {
return this.window().webContents.openDevTools(); return this.window().webContents.openDevTools();
} }
closeDevTools() { public closeDevTools() {
return this.window().webContents.closeDevTools(); return this.window().webContents.closeDevTools();
} }
async showSaveDialog(options: any) { public async showSaveDialog(options: any) {
const { dialog } = require('electron'); const { dialog } = require('electron');
if (!options) options = {}; if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPaths_.file) options.defaultPath = this.lastSelectedPaths_.file; if (!('defaultPath' in options) && this.lastSelectedPaths_.file) options.defaultPath = this.lastSelectedPaths_.file;
@@ -162,7 +162,7 @@ export class Bridge {
return filePath; return filePath;
} }
async showOpenDialog(options: OpenDialogOptions = null) { public async showOpenDialog(options: OpenDialogOptions = null) {
const { dialog } = require('electron'); const { dialog } = require('electron');
if (!options) options = {}; if (!options) options = {};
let fileType = 'file'; let fileType = 'file';
@@ -177,13 +177,13 @@ export class Bridge {
} }
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead // Don't use this directly - call one of the showXxxxxxxMessageBox() instead
showMessageBox_(window: any, options: any): number { private showMessageBox_(window: any, options: any): number {
const { dialog } = require('electron'); const { dialog } = require('electron');
if (!window) window = this.window(); if (!window) window = this.window();
return dialog.showMessageBoxSync(window, options); return dialog.showMessageBoxSync(window, options);
} }
showErrorMessageBox(message: string) { public showErrorMessageBox(message: string) {
return this.showMessageBox_(this.window(), { return this.showMessageBox_(this.window(), {
type: 'error', type: 'error',
message: message, message: message,
@@ -191,7 +191,7 @@ export class Bridge {
}); });
} }
showConfirmMessageBox(message: string, options: any = null) { public showConfirmMessageBox(message: string, options: any = null) {
options = { options = {
buttons: [_('OK'), _('Cancel')], buttons: [_('OK'), _('Cancel')],
...options, ...options,
@@ -208,7 +208,7 @@ export class Bridge {
} }
/* returns the index of the clicked button */ /* returns the index of the clicked button */
showMessageBox(message: string, options: any = null) { public showMessageBox(message: string, options: any = null) {
if (options === null) options = {}; if (options === null) options = {};
const result = this.showMessageBox_(this.window(), Object.assign({}, { const result = this.showMessageBox_(this.window(), Object.assign({}, {
@@ -220,7 +220,7 @@ export class Bridge {
return result; return result;
} }
showInfoMessageBox(message: string, options: any = {}) { public showInfoMessageBox(message: string, options: any = {}) {
const result = this.showMessageBox_(this.window(), Object.assign({}, { const result = this.showMessageBox_(this.window(), Object.assign({}, {
type: 'info', type: 'info',
message: message, message: message,
@@ -229,35 +229,35 @@ export class Bridge {
return result === 0; return result === 0;
} }
setLocale(locale: string) { public setLocale(locale: string) {
setLocale(locale); setLocale(locale);
} }
get Menu() { public get Menu() {
return require('electron').Menu; return require('electron').Menu;
} }
get MenuItem() { public get MenuItem() {
return require('electron').MenuItem; return require('electron').MenuItem;
} }
openExternal(url: string) { public openExternal(url: string) {
return require('electron').shell.openExternal(url); return require('electron').shell.openExternal(url);
} }
async openItem(fullPath: string) { public async openItem(fullPath: string) {
return require('electron').shell.openPath(toSystemSlashes(fullPath)); return require('electron').shell.openPath(toSystemSlashes(fullPath));
} }
screen() { public screen() {
return require('electron').screen; return require('electron').screen;
} }
shouldUseDarkColors() { public shouldUseDarkColors() {
return nativeTheme.shouldUseDarkColors; return nativeTheme.shouldUseDarkColors;
} }
addEventListener(name: string, fn: Function) { public addEventListener(name: string, fn: Function) {
if (name === 'nativeThemeUpdated') { if (name === 'nativeThemeUpdated') {
nativeTheme.on('updated', fn); nativeTheme.on('updated', fn);
} else { } else {
@@ -265,7 +265,7 @@ export class Bridge {
} }
} }
restart(linuxSafeRestart = true) { public restart(linuxSafeRestart = true) {
// Note that in this case we are not sending the "appClose" event // Note that in this case we are not sending the "appClose" event
// to notify services and component that the app is about to close // to notify services and component that the app is about to close
// but for the current use-case it's not really needed. // but for the current use-case it's not really needed.

View File

@@ -2,7 +2,6 @@ const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { clipboard } = require('electron'); const { clipboard } = require('electron');
import ExtensionBadge from './ExtensionBadge'; import ExtensionBadge from './ExtensionBadge';
import bridge from '../services/bridge';
import { themeStyle } from '@joplin/lib/theme'; import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import ClipperServer from '@joplin/lib/ClipperServer'; import ClipperServer from '@joplin/lib/ClipperServer';
@@ -11,37 +10,29 @@ import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { AppState } from '../app.reducer'; import { AppState } from '../app.reducer';
class ClipperConfigScreenComponent extends React.Component { class ClipperConfigScreenComponent extends React.Component {
constructor() { public constructor() {
super(); super();
this.copyToken_click = this.copyToken_click.bind(this); this.copyToken_click = this.copyToken_click.bind(this);
} }
disableClipperServer_click() { private disableClipperServer_click() {
Setting.setValue('clipperServer.autoStart', false); Setting.setValue('clipperServer.autoStart', false);
void ClipperServer.instance().stop(); void ClipperServer.instance().stop();
} }
enableClipperServer_click() { private enableClipperServer_click() {
Setting.setValue('clipperServer.autoStart', true); Setting.setValue('clipperServer.autoStart', true);
void ClipperServer.instance().start(); void ClipperServer.instance().start();
} }
chromeButton_click() { private copyToken_click() {
void bridge().openExternal('https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek');
}
firefoxButton_click() {
void bridge().openExternal('https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/');
}
copyToken_click() {
clipboard.writeText(this.props.apiToken); clipboard.writeText(this.props.apiToken);
alert(_('Token has been copied to the clipboard!')); alert(_('Token has been copied to the clipboard!'));
} }
renewToken_click() { private renewToken_click() {
if (confirm(_('Are you sure you want to renew the authorisation token?'))) { if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
void EncryptionService.instance() void EncryptionService.instance()
.generateApiToken() .generateApiToken()
@@ -52,7 +43,7 @@ class ClipperConfigScreenComponent extends React.Component {
} }
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const containerStyle = Object.assign({}, theme.containerStyle, { const containerStyle = Object.assign({}, theme.containerStyle, {

View File

@@ -26,9 +26,9 @@ const settingKeyToControl: any = {
class ConfigScreenComponent extends React.Component<any, any> { class ConfigScreenComponent extends React.Component<any, any> {
rowStyle_: any = null; private rowStyle_: any = null;
constructor(props: any) { public constructor(props: any) {
super(props); super(props);
shared.init(this, reg); shared.init(this, reg);
@@ -55,15 +55,15 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.handleSettingButton = this.handleSettingButton.bind(this); this.handleSettingButton = this.handleSettingButton.bind(this);
} }
async checkSyncConfig_() { private async checkSyncConfig_() {
await shared.checkSyncConfig(this, this.state.settings); await shared.checkSyncConfig(this, this.state.settings);
} }
UNSAFE_componentWillMount() { public UNSAFE_componentWillMount() {
this.setState({ settings: this.props.settings }); this.setState({ settings: this.props.settings });
} }
componentDidMount() { public componentDidMount() {
if (this.props.defaultSection) { if (this.props.defaultSection) {
this.setState({ selectedSectionName: this.props.defaultSection }, () => { this.setState({ selectedSectionName: this.props.defaultSection }, () => {
this.switchSection(this.props.defaultSection); this.switchSection(this.props.defaultSection);
@@ -93,7 +93,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
} }
} }
sectionByName(name: string) { public sectionByName(name: string) {
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings }); const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
for (const section of sections) { for (const section of sections) {
if (section.name === name) return section; if (section.name === name) return section;
@@ -102,7 +102,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
throw new Error(`Invalid section name: ${name}`); throw new Error(`Invalid section name: ${name}`);
} }
screenFromName(screenName: string) { public screenFromName(screenName: string) {
if (screenName === 'encryption') return <EncryptionConfigScreen/>; if (screenName === 'encryption') return <EncryptionConfigScreen/>;
if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>; if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>;
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>; if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>;
@@ -110,7 +110,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
throw new Error(`Invalid screen name: ${screenName}`); throw new Error(`Invalid screen name: ${screenName}`);
} }
switchSection(name: string) { public switchSection(name: string) {
const section = this.sectionByName(name); const section = this.sectionByName(name);
let screenName = ''; let screenName = '';
if (section.isScreen) { if (section.isScreen) {
@@ -125,11 +125,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.setState({ selectedSectionName: section.name, screenName: screenName }); this.setState({ selectedSectionName: section.name, screenName: screenName });
} }
sidebar_selectionChange(event: any) { private sidebar_selectionChange(event: any) {
this.switchSection(event.section.name); this.switchSection(event.section.name);
} }
renderSectionDescription(section: any) { public renderSectionDescription(section: any) {
const description = Setting.sectionDescription(section.name); const description = Setting.sectionDescription(section.name);
if (!description) return null; if (!description) return null;
@@ -141,7 +141,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
); );
} }
sectionToComponent(key: string, section: any, settings: any, selected: boolean) { public sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const createSettingComponents = (advanced: boolean) => { const createSettingComponents = (advanced: boolean) => {
@@ -284,7 +284,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null; return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
} }
settingToComponent(key: string, value: any) { public settingToComponent(key: string, value: any) {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const output: any = null; const output: any = null;
@@ -657,26 +657,26 @@ class ConfigScreenComponent extends React.Component<any, any> {
} }
} }
async onApplyClick() { public async onApplyClick() {
shared.saveSettings(this); shared.saveSettings(this);
await this.checkNeedRestart(); await this.checkNeedRestart();
} }
async onSaveClick() { public async onSaveClick() {
shared.saveSettings(this); shared.saveSettings(this);
await this.checkNeedRestart(); await this.checkNeedRestart();
this.props.dispatch({ type: 'NAV_BACK' }); this.props.dispatch({ type: 'NAV_BACK' });
} }
onCancelClick() { public onCancelClick() {
this.props.dispatch({ type: 'NAV_BACK' }); this.props.dispatch({ type: 'NAV_BACK' });
} }
hasChanges() { public hasChanges() {
return !!this.state.changedSettingKeys.length; return !!this.state.changedSettingKeys.length;
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, const style = Object.assign({},

View File

@@ -143,7 +143,7 @@ export default function(props: Props) {
let cancelled = false; let cancelled = false;
async function fetchPluginIds() { async function fetchPluginIds() {
const pluginIds = await repoApi().canBeUpdatedPlugins(pluginItems.map(p => p.manifest)); const pluginIds = await repoApi().canBeUpdatedPlugins(pluginItems.map(p => p.manifest), pluginService.appVersion);
if (cancelled) return; if (cancelled) return;
const conv: Record<string, boolean> = {}; const conv: Record<string, boolean> = {};
pluginIds.forEach(id => conv[id] = true); pluginIds.forEach(id => conv[id] = true);
@@ -155,7 +155,7 @@ export default function(props: Props) {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [manifestsLoaded, pluginItems]); }, [manifestsLoaded, pluginItems, pluginService.appVersion]);
const onDelete = useCallback(async (event: ItemEvent) => { const onDelete = useCallback(async (event: ItemEvent) => {
const item = event.item; const item = event.item;

View File

@@ -13,19 +13,19 @@ interface Props {
class DropboxLoginScreenComponent extends React.Component<any, any> { class DropboxLoginScreenComponent extends React.Component<any, any> {
shared_: any; private shared_: any;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.shared_ = new Shared(this, (msg: string) => bridge().showInfoMessageBox(msg), (msg: string) => bridge().showErrorMessageBox(msg)); this.shared_ = new Shared(this, (msg: string) => bridge().showInfoMessageBox(msg), (msg: string) => bridge().showErrorMessageBox(msg));
} }
UNSAFE_componentWillMount() { public UNSAFE_componentWillMount() {
this.shared_.refreshUrl(); this.shared_.refreshUrl();
} }
render() { public render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);

View File

@@ -32,7 +32,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} }; public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
componentDidCatch(error: any, errorInfo: ErrorInfo) { public componentDidCatch(error: any, errorInfo: ErrorInfo) {
if (typeof error === 'string') error = { message: error }; if (typeof error === 'string') error = { message: error };
const pluginInfos: PluginInfo[] = []; const pluginInfos: PluginInfo[] = [];
@@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
this.setState({ error, errorInfo, pluginInfos, plugins }); this.setState({ error, errorInfo, pluginInfos, plugins });
} }
componentDidMount() { public componentDidMount() {
const onAppClose = () => { const onAppClose = () => {
ipcRenderer.send('asynchronous-message', 'appCloseReply', { ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: true, canClose: true,
@@ -68,12 +68,12 @@ export default class ErrorBoundary extends React.Component<Props, State> {
ipcRenderer.on('appClose', onAppClose); ipcRenderer.on('appClose', onAppClose);
} }
renderMessage() { public renderMessage() {
const message = this.props.message || 'Joplin encountered a fatal error and could not continue.'; const message = this.props.message || 'Joplin encountered a fatal error and could not continue.';
return <p>{message}</p>; return <p>{message}</p>;
} }
render() { public render() {
if (this.state.error) { if (this.state.error) {
const safeMode_click = async () => { const safeMode_click = async () => {
Setting.setValue('isSafeMode', true); Setting.setValue('isSafeMode', true);

View File

@@ -11,17 +11,17 @@ interface Props {
} }
class HelpButtonComponent extends React.Component<Props> { class HelpButtonComponent extends React.Component<Props> {
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.onClick = this.onClick.bind(this); this.onClick = this.onClick.bind(this);
} }
onClick() { public onClick() {
if (this.props.onClick) this.props.onClick(); if (this.props.onClick) this.props.onClick();
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' }); const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 }; const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };

View File

@@ -9,7 +9,7 @@ interface Props {
} }
class IconButton extends React.Component<Props> { class IconButton extends React.Component<Props> {
render() { public render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const iconStyle = { const iconStyle = {

View File

@@ -24,7 +24,7 @@ interface State {
} }
class ImportScreenComponent extends React.Component<Props, State> { class ImportScreenComponent extends React.Component<Props, State> {
UNSAFE_componentWillMount() { public UNSAFE_componentWillMount() {
this.setState({ this.setState({
doImport: true, doImport: true,
filePath: this.props.filePath, filePath: this.props.filePath,
@@ -32,7 +32,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
}); });
} }
UNSAFE_componentWillReceiveProps(newProps: Props) { public UNSAFE_componentWillReceiveProps(newProps: Props) {
if (newProps.filePath) { if (newProps.filePath) {
this.setState( this.setState(
{ {
@@ -47,13 +47,13 @@ class ImportScreenComponent extends React.Component<Props, State> {
} }
} }
componentDidMount() { public componentDidMount() {
if (this.state.filePath && this.state.doImport) { if (this.state.filePath && this.state.doImport) {
void this.doImport(); void this.doImport();
} }
} }
addMessage(key: string, text: string) { public addMessage(key: string, text: string) {
const messages = this.state.messages.slice(); const messages = this.state.messages.slice();
messages.push({ key: key, text: text }); messages.push({ key: key, text: text });
@@ -61,7 +61,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
this.setState({ messages: messages }); this.setState({ messages: messages });
} }
uniqueMessages() { public uniqueMessages() {
const output = []; const output = [];
const messages = this.state.messages.slice(); const messages = this.state.messages.slice();
const foundKeys = []; const foundKeys = [];
@@ -74,7 +74,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
return output; return output;
} }
async doImport() { public async doImport() {
const filePath = this.props.filePath; const filePath = this.props.filePath;
const folderTitle = await Folder.findUniqueItemTitle(filename(filePath)); const folderTitle = await Folder.findUniqueItemTitle(filename(filePath));
@@ -109,7 +109,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
this.setState({ doImport: false }); this.setState({ doImport: false });
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const messages = this.uniqueMessages(); const messages = this.uniqueMessages();

View File

@@ -1,4 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('ItemList');
interface Props { interface Props {
style: any; style: any;
@@ -8,6 +11,7 @@ interface Props {
onKeyDown?: Function; onKeyDown?: Function;
itemRenderer: Function; itemRenderer: Function;
className?: string; className?: string;
onNoteDrop?: Function;
} }
interface State { interface State {
@@ -20,7 +24,7 @@ class ItemList extends React.Component<Props, State> {
private scrollTop_: number; private scrollTop_: number;
private listRef: any; private listRef: any;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.scrollTop_ = 0; this.scrollTop_ = 0;
@@ -29,14 +33,15 @@ class ItemList extends React.Component<Props, State> {
this.onScroll = this.onScroll.bind(this); this.onScroll = this.onScroll.bind(this);
this.onKeyDown = this.onKeyDown.bind(this); this.onKeyDown = this.onKeyDown.bind(this);
this.onDrop = this.onDrop.bind(this);
} }
visibleItemCount(props: Props = undefined) { public visibleItemCount(props: Props = undefined) {
if (typeof props === 'undefined') props = this.props; if (typeof props === 'undefined') props = this.props;
return Math.ceil(props.style.height / props.itemHeight); return Math.ceil(props.style.height / props.itemHeight);
} }
updateStateItemIndexes(props: Props = undefined) { public updateStateItemIndexes(props: Props = undefined) {
if (typeof props === 'undefined') props = this.props; if (typeof props === 'undefined') props = this.props;
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight); const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
@@ -45,38 +50,66 @@ class ItemList extends React.Component<Props, State> {
let bottomItemIndex = topItemIndex + (visibleItemCount - 1); let bottomItemIndex = topItemIndex + (visibleItemCount - 1);
if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1; if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1;
// EDGE CASE:
// ref: https://github.com/laurent22/joplin/issues/4124
// when the note list is hidden, visibleItemCount is negative, and scroll top is positive when a note is selected
if (visibleItemCount < 0 && this.scrollTop_ > 0) {
logger.warn('Resetting scrollTop to 0. visibleItemCount is negative, scrollTop is positive.');
// we will reset the scroll top so that there is no blank space at the top of note list
this.scrollTop_ = 0;
}
this.setState({ this.setState({
topItemIndex: topItemIndex, topItemIndex: topItemIndex,
bottomItemIndex: bottomItemIndex, bottomItemIndex: bottomItemIndex,
}); });
} }
offsetTop() { public offsetTop() {
return this.listRef.current ? this.listRef.current.offsetTop : 0; return this.listRef.current ? this.listRef.current.offsetTop : 0;
} }
offsetScroll() { public offsetScroll() {
return this.scrollTop_; return this.scrollTop_;
} }
UNSAFE_componentWillMount() { public UNSAFE_componentWillMount() {
this.updateStateItemIndexes(); this.updateStateItemIndexes();
} }
UNSAFE_componentWillReceiveProps(newProps: Props) { public UNSAFE_componentWillReceiveProps(newProps: Props) {
this.updateStateItemIndexes(newProps); this.updateStateItemIndexes(newProps);
} }
onScroll(event: any) { public componentDidUpdate(): void {
// EDGE CASE
// scroll top is not updated when item list visibility is toggled
// if the user was at the bottom of the item list before hiding, blank spaces are added at the bottom of the item list
if (this.offsetScroll() !== this.listRef.current?.scrollTop) {
logger.warn(`scrollTop mismatch. Updating scrollTop with current listRef scrollTop(${this.listRef.current.scrollTop})`);
// update scroll postion once if there is a mismatch in scroll position after showing item list
this.onScroll({
target: {
scrollTop: this.listRef.current.scrollTop,
},
});
}
}
public onScroll(event: any) {
this.scrollTop_ = event.target.scrollTop; this.scrollTop_ = event.target.scrollTop;
this.updateStateItemIndexes(); this.updateStateItemIndexes();
} }
onKeyDown(event: any) { public onKeyDown(event: any) {
if (this.props.onKeyDown) this.props.onKeyDown(event); if (this.props.onKeyDown) this.props.onKeyDown(event);
} }
makeItemIndexVisible(itemIndex: number) { public onDrop(event: any) {
if (this.props.onNoteDrop) this.props.onNoteDrop(event);
}
public makeItemIndexVisible(itemIndex: number) {
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex); const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
const bottom = Math.max(0, this.state.bottomItemIndex); const bottom = Math.max(0, this.state.bottomItemIndex);
@@ -113,7 +146,7 @@ class ItemList extends React.Component<Props, State> {
// return true; // return true;
// } // }
render() { public render() {
const items = this.props.items; const items = this.props.items;
const style = Object.assign({}, this.props.style, { const style = Object.assign({}, this.props.style, {
overflowX: 'hidden', overflowX: 'hidden',
@@ -141,7 +174,7 @@ class ItemList extends React.Component<Props, State> {
if (this.props.className) classes.push(this.props.className); if (this.props.className) classes.push(this.props.className);
return ( return (
<div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown}> <div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown} onDrop={this.onDrop}>
{itemComps} {itemComps}
</div> </div>
); );

View File

@@ -123,7 +123,7 @@ class MainScreenComponent extends React.Component<Props, State> {
private styles_: any; private styles_: any;
private promptOnClose_: Function; private promptOnClose_: Function;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@@ -250,11 +250,11 @@ class MainScreenComponent extends React.Component<Props, State> {
return this.updateLayoutPluginViews(output, plugins); return this.updateLayoutPluginViews(output, plugins);
} }
window_resize() { private window_resize() {
this.updateRootLayoutSize(); this.updateRootLayoutSize();
} }
setupAppCloseHandling() { public setupAppCloseHandling() {
this.waitForNotesSavedIID_ = null; this.waitForNotesSavedIID_ = null;
// This event is dispached from the main process when the app is about // This event is dispached from the main process when the app is about
@@ -289,11 +289,11 @@ class MainScreenComponent extends React.Component<Props, State> {
}); });
} }
notePropertiesDialog_close() { private notePropertiesDialog_close() {
this.setState({ notePropertiesDialogOptions: {} }); this.setState({ notePropertiesDialogOptions: {} });
} }
noteContentPropertiesDialog_close() { private noteContentPropertiesDialog_close() {
this.setState({ noteContentPropertiesDialogOptions: {} }); this.setState({ noteContentPropertiesDialogOptions: {} });
} }
@@ -305,14 +305,14 @@ class MainScreenComponent extends React.Component<Props, State> {
this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } }); this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } });
} }
updateMainLayout(layout: LayoutItem) { public updateMainLayout(layout: LayoutItem) {
this.props.dispatch({ this.props.dispatch({
type: 'MAIN_LAYOUT_SET', type: 'MAIN_LAYOUT_SET',
value: layout, value: layout,
}); });
} }
updateRootLayoutSize() { public updateRootLayoutSize() {
this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => { this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => {
const s = this.rootLayoutSize(); const s = this.rootLayoutSize();
draft.width = s.width; draft.width = s.width;
@@ -320,7 +320,7 @@ class MainScreenComponent extends React.Component<Props, State> {
})); }));
} }
componentDidUpdate(prevProps: Props, prevState: State) { public componentDidUpdate(prevProps: Props, prevState: State) {
if (prevProps.style.width !== this.props.style.width || if (prevProps.style.width !== this.props.style.width ||
prevProps.style.height !== this.props.style.height || prevProps.style.height !== this.props.style.height ||
this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props) this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props)
@@ -383,24 +383,24 @@ class MainScreenComponent extends React.Component<Props, State> {
} }
} }
layoutModeListenerKeyDown(event: any) { public layoutModeListenerKeyDown(event: any) {
if (event.key !== 'Escape') return; if (event.key !== 'Escape') return;
if (!this.props.layoutMoveMode) return; if (!this.props.layoutMoveMode) return;
void CommandService.instance().execute('toggleLayoutMoveMode'); void CommandService.instance().execute('toggleLayoutMoveMode');
} }
componentDidMount() { public componentDidMount() {
window.addEventListener('keydown', this.layoutModeListenerKeyDown); window.addEventListener('keydown', this.layoutModeListenerKeyDown);
} }
componentWillUnmount() { public componentWillUnmount() {
this.unregisterCommands(); this.unregisterCommands();
window.removeEventListener('resize', this.window_resize); window.removeEventListener('resize', this.window_resize);
window.removeEventListener('keydown', this.layoutModeListenerKeyDown); window.removeEventListener('keydown', this.layoutModeListenerKeyDown);
} }
async waitForNoteToSaved(noteId: string) { public async waitForNoteToSaved(noteId: string) {
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') { while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses); console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
@@ -408,7 +408,7 @@ class MainScreenComponent extends React.Component<Props, State> {
} }
} }
async printTo_(target: string, options: any) { public async printTo_(target: string, options: any) {
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion // Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
if (this.isPrinting_) { if (this.isPrinting_) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@@ -449,23 +449,23 @@ class MainScreenComponent extends React.Component<Props, State> {
this.isPrinting_ = false; this.isPrinting_ = false;
} }
rootLayoutSize() { public rootLayoutSize() {
return { return {
width: window.innerWidth, width: window.innerWidth,
height: this.rowHeight(), height: this.rowHeight(),
}; };
} }
rowHeight() { public rowHeight() {
if (!this.props) return 0; if (!this.props) return 0;
return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0); return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0);
} }
messageBoxHeight() { public messageBoxHeight() {
return 50; return 50;
} }
styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) { public styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
const styleKey = [themeId, width, height, messageBoxVisible].join('_'); const styleKey = [themeId, width, height, messageBoxVisible].join('_');
if (styleKey === this.styleKey_) return this.styles_; if (styleKey === this.styleKey_) return this.styles_;
@@ -539,7 +539,7 @@ class MainScreenComponent extends React.Component<Props, State> {
); );
} }
renderNotification(theme: any, styles: any) { public renderNotification(theme: any, styles: any) {
if (!this.messageBoxVisible()) return null; if (!this.messageBoxVisible()) return null;
const onViewStatusScreen = () => { const onViewStatusScreen = () => {
@@ -658,33 +658,33 @@ class MainScreenComponent extends React.Component<Props, State> {
); );
} }
messageBoxVisible(props: Props = null) { public messageBoxVisible(props: Props = null) {
if (!props) props = this.props; if (!props) props = this.props;
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin; return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin;
} }
registerCommands() { public registerCommands() {
for (const command of commands) { for (const command of commands) {
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this)); CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this));
} }
} }
unregisterCommands() { public unregisterCommands() {
for (const command of commands) { for (const command of commands) {
CommandService.instance().unregisterRuntime(command.declaration.name); CommandService.instance().unregisterRuntime(command.declaration.name);
} }
} }
resizableLayout_resize(event: any) { private resizableLayout_resize(event: any) {
this.updateMainLayout(event.layout); this.updateMainLayout(event.layout);
} }
resizableLayout_moveButtonClick(event: MoveButtonClickEvent) { private resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
const newLayout = move(this.props.mainLayout, event.itemKey, event.direction); const newLayout = move(this.props.mainLayout, event.itemKey, event.direction);
this.updateMainLayout(newLayout); this.updateMainLayout(newLayout);
} }
resizableLayout_renderItem(key: string, event: any) { private resizableLayout_renderItem(key: string, event: any) {
// Key should never be undefined but somehow it can happen, also not // Key should never be undefined but somehow it can happen, also not
// clear how. For now in this case render nothing so that the app // clear how. For now in this case render nothing so that the app
// doesn't crash. // doesn't crash.
@@ -770,7 +770,7 @@ class MainScreenComponent extends React.Component<Props, State> {
} }
} }
renderPluginDialogs() { public renderPluginDialogs() {
const output = []; const output = [];
const infos = pluginUtils.viewInfosByType(this.props.plugins, 'webview'); const infos = pluginUtils.viewInfosByType(this.props.plugins, 'webview');
@@ -801,7 +801,7 @@ class MainScreenComponent extends React.Component<Props, State> {
); );
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = Object.assign( const style = Object.assign(
{ {

View File

@@ -21,7 +21,7 @@ import checkForUpdates from '../checkForUpdates';
const { connect } = require('react-redux'); const { connect } = require('react-redux');
import { reg } from '@joplin/lib/registry'; import { reg } from '@joplin/lib/registry';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types'; import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import PluginService from '@joplin/lib/services/plugins/PluginService'; import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
const packageInfo = require('../packageInfo.js'); const packageInfo = require('../packageInfo.js');
const { clipboard } = require('electron'); const { clipboard } = require('electron');
const Menu = bridge().Menu; const Menu = bridge().Menu;
@@ -128,6 +128,7 @@ interface Props {
customCss: string; customCss: string;
locale: string; locale: string;
profileConfig: ProfileConfig; profileConfig: ProfileConfig;
pluginSettings: PluginSettings;
} }
const commandNames: string[] = menuCommandNames(); const commandNames: string[] = menuCommandNames();
@@ -487,8 +488,7 @@ function useMenu(props: Props) {
} }
function _showAbout() { function _showAbout() {
const v = versionInfo(packageInfo, PluginService.instance().plugins); const v = versionInfo(packageInfo, PluginService.instance().enabledPlugins(props.pluginSettings));
const copyToClipboard = bridge().showMessageBox(v.message, { const copyToClipboard = bridge().showMessageBox(v.message, {
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`, icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,
@@ -931,6 +931,7 @@ function useMenu(props: Props) {
props['spellChecker.languages'], props['spellChecker.languages'],
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
props['spellChecker.enabled'], props['spellChecker.enabled'],
props.pluginSettings,
props.customCss, props.customCss,
props.locale, props.locale,
props.profileConfig, props.profileConfig,
@@ -986,6 +987,7 @@ const mapStateToProps = (state: AppState) => {
['folders.sortOrder.field']: state.settings['folders.sortOrder.field'], ['folders.sortOrder.field']: state.settings['folders.sortOrder.field'],
['notes.sortOrder.reverse']: state.settings['notes.sortOrder.reverse'], ['notes.sortOrder.reverse']: state.settings['notes.sortOrder.reverse'],
['folders.sortOrder.reverse']: state.settings['folders.sortOrder.reverse'], ['folders.sortOrder.reverse']: state.settings['folders.sortOrder.reverse'],
pluginSettings: state.settings['plugins.states'],
showNoteCounts: state.settings.showNoteCounts, showNoteCounts: state.settings.showNoteCounts,
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop, uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
showCompletedTodos: state.settings.showCompletedTodos, showCompletedTodos: state.settings.showCompletedTodos,

View File

@@ -9,7 +9,7 @@ interface Props {
} }
class NavigatorComponent extends React.Component<Props> { class NavigatorComponent extends React.Component<Props> {
UNSAFE_componentWillReceiveProps(newProps: Props) { public UNSAFE_componentWillReceiveProps(newProps: Props) {
if (newProps.route) { if (newProps.route) {
const screenInfo = this.props.screens[newProps.route.routeName]; const screenInfo = this.props.screens[newProps.route.routeName];
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : ''; const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
@@ -21,7 +21,7 @@ class NavigatorComponent extends React.Component<Props> {
} }
} }
updateWindowTitle(title: string) { public updateWindowTitle(title: string) {
try { try {
if (bridge().window()) bridge().window().setTitle(title); if (bridge().window()) bridge().window().setTitle(title);
} catch (error) { } catch (error) {
@@ -29,7 +29,7 @@ class NavigatorComponent extends React.Component<Props> {
} }
} }
render() { public render() {
if (!this.props.route) throw new Error('Route must not be null'); if (!this.props.route) throw new Error('Route must not be null');
const route = this.props.route; const route = this.props.route;

View File

@@ -641,8 +641,8 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
resourceInfos: props.resourceInfos, resourceInfos: props.resourceInfos,
contentMaxWidth: props.contentMaxWidth, contentMaxWidth: props.contentMaxWidth,
mapsToLine: true, mapsToLine: true,
// Always using useCustomPdfViewer for now, we can add a new setting for it in future if we need to. // turning off custom pdf viewer; ref: https://github.com/laurent22/joplin/issues/8028
useCustomPdfViewer: props.useCustomPdfViewer, useCustomPdfViewer: false,
noteId: props.noteId, noteId: props.noteId,
vendorDir: bridge().vendorDir(), vendorDir: bridge().vendorDir(),
})); }));

View File

@@ -77,16 +77,6 @@ function stripMarkup(markupLanguage: number, markup: string, options: any = null
return markupToHtml_.stripMarkup(markupLanguage, markup, options); return markupToHtml_.stripMarkup(markupLanguage, markup, options);
} }
function createSyntheticClipboardEventWithoutHTML(): ClipboardEvent {
const clipboardData = new DataTransfer();
for (const format of clipboard.availableFormats()) {
if (format !== 'text/html') {
clipboardData.setData(format, clipboard.read(format));
}
}
return new ClipboardEvent('paste', { clipboardData });
}
interface TinyMceCommand { interface TinyMceCommand {
name: string; name: string;
value?: any; value?: any;
@@ -1076,24 +1066,24 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
} }
} }
function onKeyDown(event: any) { async function onKeyDown(event: any) {
// It seems "paste as text" is handled automatically on Windows and Linux, // It seems "paste as text" is handled automatically on Windows and Linux,
// so we need to run the below code only on macOS. If we were to run this // so we need to run the below code only on macOS. If we were to run this
// on Windows/Linux, we would have this double-paste issue: // on Windows/Linux, we would have this double-paste issue:
// https://github.com/laurent22/joplin/issues/4243 // https://github.com/laurent22/joplin/issues/4243
// Handle "paste as text". Note that when pressing CtrlOrCmd+Shift+V it's going // While "paste as text" functionality is handled by Windows and Linux, if we
// to trigger the "keydown" event but not the "paste" event, so it's ok to process // want to allow the user to customize the shortcut we need to prevent when it
// it here and we don't need to do anything special in onPaste // has the default value so it doesn't paste the content twice
if (!shim.isWindows() && !shim.isLinux()) { // (one by the system and the other by our code)
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') { if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') {
pasteAsPlainText(); event.preventDefault();
} pasteAsPlainText(null);
} }
} }
async function onPasteAsText() { function onPasteAsText() {
await onPaste(createSyntheticClipboardEventWithoutHTML()); pasteAsPlainText(null);
} }
editor.on(TinyMceEditorEvents.KeyUp, onKeyUp); editor.on(TinyMceEditorEvents.KeyUp, onKeyUp);

View File

@@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react';
import { ResourceInfos } from './types'; import { ResourceInfos } from './types';
import markupLanguageUtils from '../../../utils/markupLanguageUtils'; import markupLanguageUtils from '../../../utils/markupLanguageUtils';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim';
const { themeStyle } = require('@joplin/lib/theme'); const { themeStyle } = require('@joplin/lib/theme');
import Note from '@joplin/lib/models/Note'; import Note from '@joplin/lib/models/Note';
@@ -23,6 +24,7 @@ export interface MarkupToHtmlOptions {
useCustomPdfViewer?: boolean; useCustomPdfViewer?: boolean;
noteId?: string; noteId?: string;
vendorDir?: string; vendorDir?: string;
platformName?: string;
} }
export default function useMarkupToHtml(deps: HookDependencies) { export default function useMarkupToHtml(deps: HookDependencies) {
@@ -40,6 +42,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
options = { options = {
replaceResourceInternalToExternalLinks: false, replaceResourceInternalToExternalLinks: false,
resourceInfos: {}, resourceInfos: {},
platformName: shim.platformName(),
...options, ...options,
}; };
@@ -62,6 +65,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
postMessageSyntax: 'ipcProxySendToHost', postMessageSyntax: 'ipcProxySendToHost',
splitted: true, splitted: true,
externalAssetsOnly: true, externalAssetsOnly: true,
codeHighlightCacheKey: 'useMarkupToHtml',
}, options)); }, options));
return result; return result;

View File

@@ -241,6 +241,7 @@ const NoteListComponent = (props: Props) => {
event.dataTransfer.setDragImage(new Image(), 1, 1); event.dataTransfer.setDragImage(new Image(), 1, 1);
event.dataTransfer.clearData(); event.dataTransfer.clearData();
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds)); event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
event.dataTransfer.effectAllowed = 'move';
}; };
const renderItem = useCallback((item: any, index: number) => { const renderItem = useCallback((item: any, index: number) => {
@@ -275,7 +276,6 @@ const NoteListComponent = (props: Props) => {
onCheckboxClick={noteItem_checkboxClick} onCheckboxClick={noteItem_checkboxClick}
onDragStart={noteItem_dragStart} onDragStart={noteItem_dragStart}
onNoteDragOver={noteItem_noteDragOver} onNoteDragOver={noteItem_noteDragOver}
onNoteDrop={noteItem_noteDrop}
onTitleClick={noteItem_titleClick} onTitleClick={noteItem_titleClick}
onContextMenu={itemContextMenu} onContextMenu={itemContextMenu}
/>; />;
@@ -295,7 +295,7 @@ const NoteListComponent = (props: Props) => {
useEffect(() => { useEffect(() => {
if (previousSelectedNoteIds !== props.selectedNoteIds && props.selectedNoteIds.length === 1) { if (previousSelectedNoteIds !== props.selectedNoteIds && props.selectedNoteIds.length === 1) {
const id = props.selectedNoteIds[0]; const id = props.selectedNoteIds[0];
const doRefocus = props.notes.length < previousNotes.length; const doRefocus = props.notes.length < previousNotes.length && !props.focusedField;
for (let i = 0; i < props.notes.length; i++) { for (let i = 0; i < props.notes.length; i++) {
if (props.notes[i].id === id) { if (props.notes[i].id === id) {
@@ -312,8 +312,7 @@ const NoteListComponent = (props: Props) => {
if (previousVisible !== props.visible) { if (previousVisible !== props.visible) {
updateSizeState(); updateSizeState();
} }
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied }, [previousSelectedNoteIds, previousNotes, previousVisible, props.selectedNoteIds, props.notes, props.focusedField, props.visible]);
}, [previousSelectedNoteIds, previousNotes, previousVisible, props.selectedNoteIds, props.notes]);
const scrollNoteIndex_ = (keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) => { const scrollNoteIndex_ = (keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) => {
@@ -526,6 +525,7 @@ const NoteListComponent = (props: Props) => {
style={props.size} style={props.size}
itemRenderer={renderItem} itemRenderer={renderItem}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
onNoteDrop={noteItem_noteDrop}
/> />
); );
}; };
@@ -559,6 +559,7 @@ const mapStateToProps = (state: AppState) => {
highlightedWords: state.highlightedWords, highlightedWords: state.highlightedWords,
plugins: state.pluginService.plugins, plugins: state.pluginService.plugins,
customCss: state.customCss, customCss: state.customCss,
focusedField: state.focusedField,
}; };
}; };

View File

@@ -23,4 +23,5 @@ export interface Props {
highlightedWords: string[]; highlightedWords: string[];
provisionalNoteIds: string[]; provisionalNoteIds: string[];
visible: boolean; visible: boolean;
focusedField: string;
} }

View File

@@ -1,6 +1,6 @@
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import * as React from 'react'; import * as React from 'react';
import { useEffect, useRef } from 'react'; import { useEffect, useRef, useMemo, useState } from 'react';
import SearchBar from '../SearchBar/SearchBar'; import SearchBar from '../SearchBar/SearchBar';
import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button'; import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button';
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
@@ -11,6 +11,13 @@ import { _ } from '@joplin/lib/locale';
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const styled = require('styled-components').default; const styled = require('styled-components').default;
enum BaseBreakpoint {
Sm = 75,
Md = 80,
Lg = 120,
Xl = 474,
}
interface Props { interface Props {
showNewNoteButtons: boolean; showNewNoteButtons: boolean;
sortOrderButtonsVisible: boolean; sortOrderButtonsVisible: boolean;
@@ -18,6 +25,15 @@ interface Props {
sortOrderReverse: boolean; sortOrderReverse: boolean;
notesParentType: string; notesParentType: string;
height: number; height: number;
width: number;
onContentHeightChange: (sameRow: boolean)=> void;
}
interface Breakpoints {
Sm: number;
Md: number;
Lg: number;
Xl: number;
} }
const StyledRoot = styled.div` const StyledRoot = styled.div`
@@ -34,7 +50,9 @@ const StyledButton = styled(Button)`
width: auto; width: auto;
height: 26px; height: 26px;
min-height: 26px; min-height: 26px;
flex: 1 0 auto; min-width: 37px;
max-width: none;
white-space: nowrap;
.fa, .fas { .fa, .fas {
font-size: 11px; font-size: 11px;
@@ -54,7 +72,13 @@ const StyledPairButtonR = styled(Button)`
width: auto; width: auto;
`; `;
const RowContainer = styled.div` const TopRow = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
`;
const BottomRow = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex: 1 1 auto; flex: 1 1 auto;
@@ -68,7 +92,102 @@ const SortOrderButtonsContainer = styled.div`
`; `;
function NoteListControls(props: Props) { function NoteListControls(props: Props) {
const [dynamicBreakpoints, setDynamicBreakpoints] = useState<Breakpoints>({ Sm: BaseBreakpoint.Sm, Md: BaseBreakpoint.Md, Lg: BaseBreakpoint.Lg, Xl: BaseBreakpoint.Xl });
const searchBarRef = useRef(null); const searchBarRef = useRef(null);
const newNoteRef = useRef(null);
const newTodoRef = useRef(null);
const noteControlsRef = useRef(null);
const searchAndSortRef = useRef(null);
const getTextWidth = (text: string): number => {
const canvas = document.createElement('canvas');
if (!canvas) throw new Error('Failed to create canvas element');
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get context');
const fontWeight = getComputedStyle(newNoteRef.current).getPropertyValue('font-weight');
const fontSize = getComputedStyle(newNoteRef.current).getPropertyValue('font-size');
const fontFamily = getComputedStyle(newNoteRef.current).getPropertyValue('font-family');
ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`;
return ctx.measureText(text).width;
};
// Initialize language-specific breakpoints
useEffect(() => {
// Use the longest string to calculate the amount of extra width needed
const smAdditional = getTextWidth(_('note')) > getTextWidth(_('to-do')) ? getTextWidth(_('note')) : getTextWidth(_('to-do'));
const mdAdditional = getTextWidth(_('New note')) > getTextWidth(_('New to-do')) ? getTextWidth(_('New note')) : getTextWidth(_('New to-do'));
const Sm = BaseBreakpoint.Sm + smAdditional * 2;
const Md = BaseBreakpoint.Md + mdAdditional * 2;
const Lg = BaseBreakpoint.Lg + Md;
const Xl = BaseBreakpoint.Xl;
setDynamicBreakpoints({ Sm, Md, Lg, Xl });
}, []);
const breakpoint = useMemo(() => {
// Find largest breakpoint that width is less than
const index = Object.values(dynamicBreakpoints).findIndex(x => props.width < x);
return index === -1 ? dynamicBreakpoints.Xl : Object.values(dynamicBreakpoints)[index];
}, [props.width, dynamicBreakpoints]);
const noteButtonText = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return '';
} else if (breakpoint === dynamicBreakpoints.Md) {
return _('note');
} else {
return _('New note');
}
}, [breakpoint, dynamicBreakpoints]);
const todoButtonText = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return '';
} else if (breakpoint === dynamicBreakpoints.Md) {
return _('to-do');
} else {
return _('New to-do');
}
}, [breakpoint, dynamicBreakpoints]);
const noteIcon = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return 'icon-note';
} else {
return 'fas fa-plus';
}
}, [breakpoint, dynamicBreakpoints]);
const todoIcon = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return 'far fa-check-square';
} else {
return 'fas fa-plus';
}
}, [breakpoint, dynamicBreakpoints]);
const showTooltip = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return true;
} else {
return false;
}
}, [breakpoint, dynamicBreakpoints.Sm]);
useEffect(() => {
if (breakpoint === dynamicBreakpoints.Xl) {
noteControlsRef.current.style.flexDirection = 'row';
searchAndSortRef.current.style.flex = '2 1 auto';
props.onContentHeightChange(true);
} else {
noteControlsRef.current.style.flexDirection = 'column';
props.onContentHeightChange(false);
}
}, [breakpoint, dynamicBreakpoints, props.onContentHeightChange]);
useEffect(() => { useEffect(() => {
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef)); CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
@@ -127,64 +246,63 @@ function NoteListControls(props: Props) {
if (!props.showNewNoteButtons) return null; if (!props.showNewNoteButtons) return null;
return ( return (
<RowContainer> <TopRow className="new-note-todo-buttons">
<StyledButton <StyledButton ref={newNoteRef}
className="new-note-button" className="new-note-button"
tooltip={CommandService.instance().label('newNote')} tooltip={ showTooltip ? CommandService.instance().label('newNote') : '' }
iconName="fas fa-plus" iconName={noteIcon}
title={_('%s', 'New note')} title={_('%s', noteButtonText)}
level={ButtonLevel.Primary} level={ButtonLevel.Primary}
size={ButtonSize.Small} size={ButtonSize.Small}
onClick={onNewNoteButtonClick} onClick={onNewNoteButtonClick}
/> />
<StyledButton <StyledButton ref={newTodoRef}
className="new-todo-button" className="new-todo-button"
tooltip={CommandService.instance().label('newTodo')} tooltip={ showTooltip ? CommandService.instance().label('newTodo') : '' }
iconName="fas fa-plus" iconName={todoIcon}
title={_('%s', 'New to-do')} title={_('%s', todoButtonText)}
level={ButtonLevel.Secondary} level={ButtonLevel.Secondary}
size={ButtonSize.Small} size={ButtonSize.Small}
onClick={onNewTodoButtonClick} onClick={onNewTodoButtonClick}
/> />
</RowContainer> </TopRow>
); );
} }
return ( return (
<StyledRoot> <StyledRoot ref={noteControlsRef}>
{renderNewNoteButtons()} {renderNewNoteButtons()}
<RowContainer> <BottomRow ref={searchAndSortRef} className="search-and-sort">
<SearchBar inputRef={searchBarRef}/> <SearchBar inputRef={searchBarRef}/>
<SortOrderButtonsContainer> {showsSortOrderButtons() &&
{showsSortOrderButtons() && <SortOrderButtonsContainer>
<StyledPairButtonL <StyledPairButtonL
className="sort-order-field-button" className="sort-order-field-button"
tooltip={sortOrderFieldTooltip()} tooltip={sortOrderFieldTooltip()}
iconName={sortOrderFieldIcon()} iconName={sortOrderFieldIcon()}
level={ButtonLevel.Secondary} level={ButtonLevel.Secondary}
size={ButtonSize.Small} size={ButtonSize.Small}
onClick={onSortOrderFieldButtonClick} onClick={onSortOrderFieldButtonClick}
/> />
} <StyledPairButtonR
{showsSortOrderButtons() && className="sort-order-reverse-button"
<StyledPairButtonR tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
className="sort-order-reverse-button" iconName={sortOrderReverseIcon()}
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')} level={ButtonLevel.Secondary}
iconName={sortOrderReverseIcon()} size={ButtonSize.Small}
level={ButtonLevel.Secondary} onClick={onSortOrderReverseButtonClick}
size={ButtonSize.Small} />
onClick={onSortOrderReverseButtonClick} </SortOrderButtonsContainer>
/> }
} </BottomRow>
</SortOrderButtonsContainer>
</RowContainer>
</StyledRoot> </StyledRoot>
); );
} }
const mapStateToProps = (state: AppState) => { const mapStateToProps = (state: AppState) => {
return { return {
showNewNoteButtons: state.focusedField !== 'globalSearch', // TODO: showNewNoteButtons and the logic associated is not needed anymore.
showNewNoteButtons: true,
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'], sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],
sortOrderField: state.settings['notes.sortOrder.field'], sortOrderField: state.settings['notes.sortOrder.field'],
sortOrderReverse: state.settings['notes.sortOrder.reverse'], sortOrderReverse: state.settings['notes.sortOrder.reverse'],

View File

@@ -56,7 +56,6 @@ interface NoteListItemProps {
onCheckboxClick: any; onCheckboxClick: any;
onDragStart: any; onDragStart: any;
onNoteDragOver: any; onNoteDragOver: any;
onNoteDrop: any;
onTitleClick: any; onTitleClick: any;
onContextMenu(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void; onContextMenu(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void;
} }
@@ -175,7 +174,6 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
<StyledRoot <StyledRoot
className={classNames} className={classNames}
onDragOver={props.onNoteDragOver} onDragOver={props.onNoteDragOver}
onDrop={props.onNoteDrop}
width={props.width} width={props.width}
height={props.height} height={props.height}
isProvisional={props.isProvisional} isProvisional={props.isProvisional}

View File

@@ -1,6 +1,6 @@
import { themeStyle } from '@joplin/lib/theme'; import { themeStyle } from '@joplin/lib/theme';
import * as React from 'react'; import * as React from 'react';
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import NoteList from '../NoteList/NoteList'; import NoteList from '../NoteList/NoteList';
import NoteListControls from '../NoteListControls/NoteListControls'; import NoteListControls from '../NoteListControls/NoteListControls';
import { Size } from '../ResizableLayout/utils/types'; import { Size } from '../ResizableLayout/utils/types';
@@ -22,7 +22,15 @@ const StyledRoot = styled.div`
export default function NoteListWrapper(props: Props) { export default function NoteListWrapper(props: Props) {
const theme = themeStyle(props.themeId); const theme = themeStyle(props.themeId);
const controlHeight = theme.topRowHeight; const [controlHeight, setControlHeight] = useState(theme.topRowHeight);
const onContentHeightChange = (sameRow: boolean) => {
if (sameRow) {
setControlHeight(theme.topRowHeight);
} else {
setControlHeight(theme.topRowHeight * 2);
}
};
const noteListSize = useMemo(() => { const noteListSize = useMemo(() => {
return { return {
@@ -33,7 +41,7 @@ export default function NoteListWrapper(props: Props) {
return ( return (
<StyledRoot> <StyledRoot>
<NoteListControls height={controlHeight}/> <NoteListControls height={controlHeight} width={noteListSize.width} onContentHeightChange={onContentHeightChange}/>
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/> <NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
</StyledRoot> </StyledRoot>
); );

View File

@@ -31,7 +31,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
private styleKey_: number; private styleKey_: number;
private styles_: any; private styles_: any;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.revisionsLink_click = this.revisionsLink_click.bind(this); this.revisionsLink_click = this.revisionsLink_click.bind(this);
@@ -56,17 +56,17 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}; };
} }
componentDidMount() { public componentDidMount() {
void this.loadNote(this.props.noteId); void this.loadNote(this.props.noteId);
} }
componentDidUpdate() { public componentDidUpdate() {
if (this.state.editedKey === null) { if (this.state.editedKey === null) {
this.okButton.current.focus(); this.okButton.current.focus();
} }
} }
async loadNote(noteId: string) { public async loadNote(noteId: string) {
if (!noteId) { if (!noteId) {
this.setState({ formNote: null }); this.setState({ formNote: null });
} else { } else {
@@ -76,7 +76,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
} }
} }
latLongFromLocation(location: string) { public latLongFromLocation(location: string) {
const o: any = {}; const o: any = {};
const l = location.split(','); const l = location.split(',');
if (l.length === 2) { if (l.length === 2) {
@@ -89,7 +89,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return o; return o;
} }
noteToFormNote(note: NoteEntity) { public noteToFormNote(note: NoteEntity) {
const formNote: any = {}; const formNote: any = {};
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time); formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
@@ -113,7 +113,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return formNote; return formNote;
} }
formNoteToNote(formNote: any) { public formNoteToNote(formNote: any) {
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location)); const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
note.user_created_time = time.formatLocalToMs(formNote.user_created_time); note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time); note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
@@ -127,7 +127,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return note; return note;
} }
styles(themeId: number) { public styles(themeId: number) {
const styleKey = themeId; const styleKey = themeId;
if (styleKey === this.styleKey_) return this.styles_; if (styleKey === this.styleKey_) return this.styles_;
@@ -168,7 +168,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return this.styles_; return this.styles_;
} }
async closeDialog(applyChanges: boolean) { public async closeDialog(applyChanges: boolean) {
if (applyChanges) { if (applyChanges) {
await this.saveProperty(); await this.saveProperty();
const note = this.formNoteToNote(this.state.formNote); const note = this.formNoteToNote(this.state.formNote);
@@ -183,16 +183,16 @@ class NotePropertiesDialog extends React.Component<Props, State> {
} }
} }
buttonRow_click(event: any) { private buttonRow_click(event: any) {
void this.closeDialog(event.buttonName === 'ok'); void this.closeDialog(event.buttonName === 'ok');
} }
revisionsLink_click() { private revisionsLink_click() {
void this.closeDialog(false); void this.closeDialog(false);
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick(); if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
} }
editPropertyButtonClick(key: string, initialValue: any) { public editPropertyButtonClick(key: string, initialValue: any) {
this.setState({ this.setState({
editedKey: key, editedKey: key,
editedValue: initialValue, editedValue: initialValue,
@@ -207,7 +207,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}, 100); }, 100);
} }
async saveProperty() { public async saveProperty() {
if (!this.state.editedKey) return; if (!this.state.editedKey) return;
return new Promise((resolve: Function) => { return new Promise((resolve: Function) => {
@@ -233,7 +233,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}); });
} }
async cancelProperty() { public async cancelProperty() {
return new Promise((resolve: Function) => { return new Promise((resolve: Function) => {
this.okButton.current.focus(); this.okButton.current.focus();
this.setState({ this.setState({
@@ -245,7 +245,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}); });
} }
createNoteField(key: string, value: any) { public createNoteField(key: string, value: any) {
const styles = this.styles(this.props.themeId); const styles = this.styles(this.props.themeId);
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>; const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
@@ -364,12 +364,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
); );
} }
formatLabel(key: string) { public formatLabel(key: string) {
if (this.keyToLabel_[key]) return this.keyToLabel_[key]; if (this.keyToLabel_[key]) return this.keyToLabel_[key];
return key; return key;
} }
formatValue(key: string, note: NoteEntity) { public formatValue(key: string, note: NoteEntity) {
if (key === 'location') { if (key === 'location') {
if (!Number(note.latitude) && !Number(note.longitude)) return null; if (!Number(note.latitude) && !Number(note.longitude)) return null;
const dms = formatcoords(Number(note.latitude), Number(note.longitude)); const dms = formatcoords(Number(note.latitude), Number(note.longitude));
@@ -383,7 +383,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return (note as any)[key]; return (note as any)[key];
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const formNote = this.state.formNote; const formNote = this.state.formNote;

View File

@@ -38,7 +38,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
private viewerRef_: any; private viewerRef_: any;
private helpButton_onClick: Function; private helpButton_onClick: Function;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@@ -57,7 +57,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
this.webview_ipcMessage = this.webview_ipcMessage.bind(this); this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
} }
style() { public style() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = { const style = {
@@ -74,7 +74,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
return style; return style;
} }
async viewer_domReady() { private async viewer_domReady() {
// this.viewerRef_.current.openDevTools(); // this.viewerRef_.current.openDevTools();
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId); const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
@@ -90,7 +90,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
); );
} }
async importButton_onClick() { private async importButton_onClick() {
if (!this.state.note) return; if (!this.state.note) return;
this.setState({ restoring: true }); this.setState({ restoring: true });
await RevisionService.instance().importRevisionNote(this.state.note); await RevisionService.instance().importRevisionNote(this.state.note);
@@ -98,11 +98,11 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
alert(RevisionService.instance().restoreSuccessMessage(this.state.note)); alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
} }
backButton_click() { private backButton_click() {
if (this.props.onBack) this.props.onBack(); if (this.props.onBack) this.props.onBack();
} }
revisionList_onChange(event: any) { private revisionList_onChange(event: any) {
const value = event.target.value; const value = event.target.value;
if (!value) { if (!value) {
@@ -119,7 +119,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
} }
} }
async reloadNote() { public async reloadNote() {
let noteBody = ''; let noteBody = '';
let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN; let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
if (!this.state.revisions.length || !this.state.currentRevId) { if (!this.state.revisions.length || !this.state.currentRevId) {
@@ -153,7 +153,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
}); });
} }
async webview_ipcMessage(event: any) { private async webview_ipcMessage(event: any) {
// For the revision view, we only suppport a minimal subset of the IPC messages. // For the revision view, we only suppport a minimal subset of the IPC messages.
// For example, we don't need interactive checkboxes or sync between viewer and editor view. // For example, we don't need interactive checkboxes or sync between viewer and editor view.
// We try to get most links work though, except for internal (joplin://) links. // We try to get most links work though, except for internal (joplin://) links.
@@ -183,7 +183,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
} }
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = this.style(); const style = this.style();

View File

@@ -20,7 +20,7 @@ class NoteSearchBar extends React.Component<Props> {
private backgroundColor: any; private backgroundColor: any;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.searchInput_change = this.searchInput_change.bind(this); this.searchInput_change = this.searchInput_change.bind(this);
@@ -33,7 +33,7 @@ class NoteSearchBar extends React.Component<Props> {
this.backgroundColor = undefined; this.backgroundColor = undefined;
} }
style() { public style() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = { const style = {
@@ -46,7 +46,7 @@ class NoteSearchBar extends React.Component<Props> {
return style; return style;
} }
buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) { public buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const searchButton = { const searchButton = {
@@ -74,12 +74,12 @@ class NoteSearchBar extends React.Component<Props> {
); );
} }
searchInput_change(event: any) { private searchInput_change(event: any) {
const query = event.currentTarget.value; const query = event.currentTarget.value;
this.triggerOnChange(query); this.triggerOnChange(query);
} }
searchInput_keyDown(event: any) { private searchInput_keyDown(event: any) {
if (event.keyCode === 13) { if (event.keyCode === 13) {
// ENTER // ENTER
event.preventDefault(); event.preventDefault();
@@ -106,28 +106,28 @@ class NoteSearchBar extends React.Component<Props> {
} }
} }
previousButton_click() { private previousButton_click() {
if (this.props.onPrevious) this.props.onPrevious(); if (this.props.onPrevious) this.props.onPrevious();
} }
nextButton_click() { private nextButton_click() {
if (this.props.onNext) this.props.onNext(); if (this.props.onNext) this.props.onNext();
} }
closeButton_click() { private closeButton_click() {
if (this.props.onClose) this.props.onClose(); if (this.props.onClose) this.props.onClose();
} }
triggerOnChange(query: string) { public triggerOnChange(query: string) {
if (this.props.onChange) this.props.onChange(query); if (this.props.onChange) this.props.onChange(query);
} }
focus() { public focus() {
(this.refs.searchInput as any).focus(); (this.refs.searchInput as any).focus();
(this.refs.searchInput as any).select(); (this.refs.searchInput as any).select();
} }
render() { public render() {
const query = this.props.query ? this.props.query : ''; const query = this.props.query ? this.props.query : '';
// backgroundColor needs to cached to a local variable to prevent the // backgroundColor needs to cached to a local variable to prevent the

View File

@@ -11,7 +11,7 @@ interface Props {
} }
class NoteStatusBarComponent extends React.Component<Props> { class NoteStatusBarComponent extends React.Component<Props> {
style() { public style() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = { const style = {
@@ -24,7 +24,7 @@ class NoteStatusBarComponent extends React.Component<Props> {
return style; return style;
} }
render() { public render() {
const note = this.props.note; const note = this.props.note;
return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>; return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>;
} }

View File

@@ -17,7 +17,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
private webviewRef_: any; private webviewRef_: any;
private webviewListeners_: any = null; private webviewListeners_: any = null;
constructor(props: any) { public constructor(props: any) {
super(props); super(props);
this.webviewRef_ = React.createRef(); this.webviewRef_ = React.createRef();
@@ -41,20 +41,20 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
this.webview_message = this.webview_message.bind(this); this.webview_message = this.webview_message.bind(this);
} }
webview_domReady(event: any) { private webview_domReady(event: any) {
this.domReady_ = true; this.domReady_ = true;
if (this.props.onDomReady) this.props.onDomReady(event); if (this.props.onDomReady) this.props.onDomReady(event);
} }
webview_ipcMessage(event: any) { private webview_ipcMessage(event: any) {
if (this.props.onIpcMessage) this.props.onIpcMessage(event); if (this.props.onIpcMessage) this.props.onIpcMessage(event);
} }
webview_load() { private webview_load() {
this.webview_domReady({}); this.webview_domReady({});
} }
webview_message(event: any) { private webview_message(event: any) {
if (!event.data || event.data.target !== 'main') return; if (!event.data || event.data.target !== 'main') return;
const callName = event.data.name; const callName = event.data.name;
@@ -68,11 +68,11 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
} }
} }
domReady() { public domReady() {
return this.domReady_; return this.domReady_;
} }
initWebview() { public initWebview() {
const wv = this.webviewRef_.current; const wv = this.webviewRef_.current;
if (!this.webviewListeners_) { if (!this.webviewListeners_) {
@@ -92,7 +92,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
this.webviewRef_.current.contentWindow.addEventListener('message', this.webview_message); this.webviewRef_.current.contentWindow.addEventListener('message', this.webview_message);
} }
destroyWebview() { public destroyWebview() {
const wv = this.webviewRef_.current; const wv = this.webviewRef_.current;
if (!wv || !this.initialized_) return; if (!wv || !this.initialized_) return;
@@ -115,28 +115,28 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
this.domReady_ = false; this.domReady_ = false;
} }
focus() { public focus() {
if (this.webviewRef_.current) { if (this.webviewRef_.current) {
this.webviewRef_.current.focus(); this.webviewRef_.current.focus();
} }
} }
tryInit() { public tryInit() {
if (!this.initialized_ && this.webviewRef_.current) { if (!this.initialized_ && this.webviewRef_.current) {
this.initWebview(); this.initWebview();
this.initialized_ = true; this.initialized_ = true;
} }
} }
componentDidMount() { public componentDidMount() {
this.tryInit(); this.tryInit();
} }
componentDidUpdate() { public componentDidUpdate() {
this.tryInit(); this.tryInit();
} }
componentWillUnmount() { public componentWillUnmount() {
this.destroyWebview(); this.destroyWebview();
} }
@@ -144,7 +144,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
// Wrap WebView functions // Wrap WebView functions
// ---------------------------------------------------------------- // ----------------------------------------------------------------
send(channel: string, arg0: any = null, arg1: any = null) { public send(channel: string, arg0: any = null, arg1: any = null) {
const win = this.webviewRef_.current.contentWindow; const win = this.webviewRef_.current.contentWindow;
if (channel === 'focus') { if (channel === 'focus') {
@@ -172,7 +172,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
// Wrap WebView functions (END) // Wrap WebView functions (END)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
render() { public render() {
const viewerStyle = Object.assign({}, { border: 'none' }, this.props.viewerStyle); const viewerStyle = Object.assign({}, { border: 'none' }, this.props.viewerStyle);
return <iframe className="noteTextViewer" ref={this.webviewRef_} style={viewerStyle} src="gui/note-viewer/index.html"></iframe>; return <iframe className="noteTextViewer" ref={this.webviewRef_} style={viewerStyle} src="gui/note-viewer/index.html"></iframe>;
} }

View File

@@ -14,7 +14,7 @@ interface Props {
} }
class OneDriveLoginScreenComponent extends React.Component<any, any> { class OneDriveLoginScreenComponent extends React.Component<any, any> {
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@@ -22,7 +22,7 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
}; };
} }
async componentDidMount() { public async componentDidMount() {
const log = (s: any) => { const log = (s: any) => {
this.setState((state: any) => { this.setState((state: any) => {
const authLog = state.authLog.slice(); const authLog = state.authLog.slice();
@@ -48,15 +48,15 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
} }
} }
startUrl() { public startUrl() {
return reg.syncTarget().api().authCodeUrl(this.redirectUrl()); return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
} }
redirectUrl() { public redirectUrl() {
return reg.syncTarget().api().nativeClientRedirectUrl(); return reg.syncTarget().api().nativeClientRedirectUrl();
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const logComps = []; const logComps = [];

View File

@@ -7,6 +7,8 @@ import { ContextMenuItemType, ContextMenuOptions } from './NoteEditor/utils/cont
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
import styled from 'styled-components'; import styled from 'styled-components';
import { themeStyle } from '@joplin/lib/theme'; import { themeStyle } from '@joplin/lib/theme';
const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode;
const Window = styled.div` const Window = styled.div`
height: 100%; height: 100%;
@@ -19,12 +21,6 @@ const Window = styled.div`
color: ${(props: any) => props.theme.color}; color: ${(props: any) => props.theme.color};
`; `;
const IFrame = styled.iframe`
height: 100%;
width: 100%;
border: none;
`;
interface Props { interface Props {
themeId: number; themeId: number;
dispatch: Function; dispatch: Function;
@@ -93,13 +89,12 @@ export default function PdfViewer(props: Props) {
const theme = themeStyle(props.themeId); const theme = themeStyle(props.themeId);
const escapedResourcePath = htmlentities(Resource.fullPath(props.resource));
const escapedMime = htmlentities('application/pdf');
return ( return (
<Window theme={theme}> <Window theme={theme}>
<IFrame src="./vendor/lib/@joplin/pdf-viewer/index.html" x-url={Resource.fullPath(props.resource)} <object data={escapedResourcePath} className="media-player media-pdf" type={escapedMime}></object>;
x-appearance={theme.appearance} ref={iframeRef}
x-title={props.resource.title}
x-anchorpage={props.pageNo}
x-type="full"></IFrame>
</Window> </Window>
); );
} }

View File

@@ -27,13 +27,13 @@ export default class PromptDialog extends React.Component<Props, any> {
private styles_: any; private styles_: any;
private styleKey_: string; private styleKey_: string;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.answerInput_ = React.createRef(); this.answerInput_ = React.createRef();
} }
UNSAFE_componentWillMount() { public UNSAFE_componentWillMount() {
this.setState({ this.setState({
visible: false, visible: false,
answer: this.props.defaultValue ? this.props.defaultValue : '', answer: this.props.defaultValue ? this.props.defaultValue : '',
@@ -41,7 +41,7 @@ export default class PromptDialog extends React.Component<Props, any> {
this.focusInput_ = true; this.focusInput_ = true;
} }
UNSAFE_componentWillReceiveProps(newProps: Props) { public UNSAFE_componentWillReceiveProps(newProps: Props) {
if ('visible' in newProps && newProps.visible !== this.props.visible) { if ('visible' in newProps && newProps.visible !== this.props.visible) {
this.setState({ visible: newProps.visible }); this.setState({ visible: newProps.visible });
if (newProps.visible) this.focusInput_ = true; if (newProps.visible) this.focusInput_ = true;
@@ -52,12 +52,12 @@ export default class PromptDialog extends React.Component<Props, any> {
} }
} }
componentDidUpdate() { public componentDidUpdate() {
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus(); if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
this.focusInput_ = false; this.focusInput_ = false;
} }
styles(themeId: number, width: number, height: number, visible: boolean) { public styles(themeId: number, width: number, height: number, visible: boolean) {
const styleKey = `${themeId}_${width}_${height}_${visible}`; const styleKey = `${themeId}_${width}_${height}_${visible}`;
if (styleKey === this.styleKey_) return this.styles_; if (styleKey === this.styleKey_) return this.styles_;
@@ -181,7 +181,7 @@ export default class PromptDialog extends React.Component<Props, any> {
return this.styles_; return this.styles_;
} }
render() { public render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel']; const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];

View File

@@ -147,7 +147,7 @@ describe('movements', () => {
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false); expect(canMove(MoveDirection.Right, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false);
}); });
test('Container with only one child should take the width of its parent', () => { test('container with only one child should take the width of its parent', () => {
let layout: LayoutItem = validateLayout({ let layout: LayoutItem = validateLayout({
key: 'root', key: 'root',
width: 100, width: 100,
@@ -170,7 +170,7 @@ describe('movements', () => {
expect(layout.children[0].children[0].width).toBe(undefined); expect(layout.children[0].children[0].width).toBe(undefined);
}); });
test('Temp container should take the width of the child it replaces', () => { test('temp container should take the width of the child it replaces', () => {
let layout: LayoutItem = validateLayout({ let layout: LayoutItem = validateLayout({
key: 'root', key: 'root',
width: 100, width: 100,
@@ -198,7 +198,7 @@ describe('movements', () => {
expect(layout.children[0].children[1].width).toBe(undefined); expect(layout.children[0].children[1].width).toBe(undefined);
}); });
test('Last child should have flexible width if all siblings have fixed width', () => { test('last child should have flexible width if all siblings have fixed width', () => {
let layout: LayoutItem = validateLayout({ let layout: LayoutItem = validateLayout({
key: 'root', key: 'root',
width: 100, width: 100,

View File

@@ -260,10 +260,6 @@ describe('useLayoutItemSizes', () => {
expect(sizes.col4.width).toBe(50); expect(sizes.col4.width).toBe(50);
}); });
});
describe('calculateMaxSizeAvailableForItem', () => {
test('should give maximum available space this item can take up during resizing', () => { test('should give maximum available space this item can take up during resizing', () => {
const layout: LayoutItem = validateLayout({ const layout: LayoutItem = validateLayout({
key: 'root', key: 'root',

View File

@@ -135,7 +135,7 @@ const getNextSortingOrderType = (s: SortingType): SortingType => {
const MAX_RESOURCES = 10000; const MAX_RESOURCES = 10000;
class ResourceScreenComponent extends React.Component<Props, State> { class ResourceScreenComponent extends React.Component<Props, State> {
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
resources: undefined, resources: undefined,
@@ -147,7 +147,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}; };
} }
async reloadResources(sorting: ActiveSorting) { public async reloadResources(sorting: ActiveSorting) {
this.setState({ isLoading: true }); this.setState({ isLoading: true });
const resources = await Resource.all({ const resources = await Resource.all({
order: [{ order: [{
@@ -161,11 +161,11 @@ class ResourceScreenComponent extends React.Component<Props, State> {
this.setState({ resources, isLoading: false }); this.setState({ resources, isLoading: false });
} }
componentDidMount() { public componentDidMount() {
void this.reloadResources(this.state.sorting); void this.reloadResources(this.state.sorting);
} }
onResourceDelete(resource: InnerResource) { public onResourceDelete(resource: InnerResource) {
const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), { const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), {
buttons: [_('Delete'), _('Cancel')], buttons: [_('Delete'), _('Cancel')],
defaultId: 1, defaultId: 1,
@@ -184,7 +184,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}); });
} }
openResource(resource: InnerResource) { public openResource(resource: InnerResource) {
const resourcePath = Resource.fullPath(resource); const resourcePath = Resource.fullPath(resource);
const ok = bridge().openExternal(`file://${resourcePath}`); const ok = bridge().openExternal(`file://${resourcePath}`);
if (!ok) { if (!ok) {
@@ -192,7 +192,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
} }
} }
onToggleSortOrder(sortOrder: SortingOrder) { public onToggleSortOrder(sortOrder: SortingOrder) {
let newSorting = { ...this.state.sorting }; let newSorting = { ...this.state.sorting };
if (sortOrder === this.state.sorting.order) { if (sortOrder === this.state.sorting.order) {
newSorting.type = getNextSortingOrderType(newSorting.type); newSorting.type = getNextSortingOrderType(newSorting.type);
@@ -206,7 +206,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
void this.reloadResources(newSorting); void this.reloadResources(newSorting);
} }
render() { public render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);

View File

@@ -1,5 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { useEffect, useRef, useCallback, useMemo } from 'react'; import { useEffect, useRef, useCallback, useMemo } from 'react';
import styled from 'styled-components';
import shim from '@joplin/lib/shim';
import { StyledRoot, StyledAddButton, StyledShareIcon, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles'; import { StyledRoot, StyledAddButton, StyledShareIcon, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { ButtonLevel } from '../Button/Button'; import { ButtonLevel } from '../Button/Button';
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
@@ -38,6 +40,24 @@ const { clipboard } = require('electron');
const logger = Logger.create('Sidebar'); const logger = Logger.create('Sidebar');
const StyledFoldersHolder = styled.div`
// linux bug: https://github.com/laurent22/joplin/issues/7506#issuecomment-1447101057
& a.list-item {
${shim.isLinux() && {
opacity: 1,
}}
}
`;
const TagsHolder = styled.div`
// linux bug: https://github.com/laurent22/joplin/issues/8000
// solution ref: https://github.com/laurent22/joplin/issues/7506#issuecomment-1447101057
& a.list-item {
${shim.isLinux() && {
opacity: 1,
}}
}
`;
interface Props { interface Props {
themeId: number; themeId: number;
dispatch: Function; dispatch: Function;
@@ -705,13 +725,13 @@ const SidebarComponent = (props: Props) => {
const folderItems = [renderAllNotesItem(theme, allNotesSelected)].concat(result.items); const folderItems = [renderAllNotesItem(theme, allNotesSelected)].concat(result.items);
folderItemsOrder_.current = result.order; folderItemsOrder_.current = result.order;
items.push( items.push(
<div <StyledFoldersHolder
className={`folders ${props.folderHeaderIsExpanded ? 'expanded' : ''}`} className={`folders ${props.folderHeaderIsExpanded ? 'expanded' : ''}`}
key="folder_items" key="folder_items"
style={foldersStyle} style={foldersStyle}
> >
{folderItems} {folderItems}
</div> </StyledFoldersHolder>
); );
} }
@@ -727,9 +747,9 @@ const SidebarComponent = (props: Props) => {
tagItemsOrder_.current = result.order; tagItemsOrder_.current = result.order;
items.push( items.push(
<div className="tags" key="tag_items" style={{ display: props.tagHeaderIsExpanded ? 'block' : 'none' }}> <TagsHolder className="tags" key="tag_items" style={{ display: props.tagHeaderIsExpanded ? 'block' : 'none' }}>
{tagItems} {tagItems}
</div> </TagsHolder>
); );
} }

View File

@@ -5,7 +5,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import { AppState } from '../app.reducer'; import { AppState } from '../app.reducer';
class TagItemComponent extends React.Component { class TagItemComponent extends React.Component {
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.tagStyle); const style = Object.assign({}, theme.tagStyle);
const { title, id } = this.props; const { title, id } = this.props;

View File

@@ -13,7 +13,7 @@ interface Props {
class ToolbarBaseComponent extends React.Component<Props, any> { class ToolbarBaseComponent extends React.Component<Props, any> {
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style: any = Object.assign({ const style: any = Object.assign({

View File

@@ -6,7 +6,7 @@ interface Props {
} }
class ToolbarSpace extends React.Component<Props> { class ToolbarSpace extends React.Component<Props> {
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.toolbarStyle); const style = Object.assign({}, theme.toolbarStyle);
style.minWidth = style.height / 2; style.minWidth = style.height / 2;

View File

@@ -7,11 +7,11 @@ const smalltalk = require('smalltalk');
const logger = Logger.create('dialogs'); const logger = Logger.create('dialogs');
class Dialogs { class Dialogs {
async alert(message: string, title = '') { public async alert(message: string, title = '') {
await smalltalk.alert(title, message); await smalltalk.alert(title, message);
} }
async confirm(message: string, title = '', options: any = {}) { public async confirm(message: string, title = '', options: any = {}) {
try { try {
await smalltalk.confirm(title, message, options); await smalltalk.confirm(title, message, options);
return true; return true;
@@ -21,7 +21,7 @@ class Dialogs {
} }
} }
async prompt(message: string, title = '', defaultValue = '', options: any = null) { public async prompt(message: string, title = '', defaultValue = '', options: any = null) {
if (options === null) options = {}; if (options === null) options = {};
try { try {

View File

@@ -154,34 +154,66 @@
setPercentScroll(percentScroll_); setPercentScroll(percentScroll_);
} }
// Note that this function keeps track of what's been added so as not to add the same CSS files multiple times // Note that this function keeps track of what's been added so as not to
// It also means that once an asset has been added it is never removed from the view, which in many case is // add the same CSS files multiple times.
// desirable, but still something to keep in mind.
function addPluginAssets(assets) { function addPluginAssets(assets) {
if (!assets) return; if (!assets) return;
const pluginAssetsContainer = document.getElementById('joplin-container-pluginAssetsContainer'); const pluginAssetsContainer = document.getElementById('joplin-container-pluginAssetsContainer');
const processedAssetIds = [];
for (let i = 0; i < assets.length; i++) { for (let i = 0; i < assets.length; i++) {
const asset = assets[i]; const asset = assets[i];
// # and ? can be used in valid paths and shouldn't be treated as the start of a query or fragment // # and ? can be used in valid paths and shouldn't be treated as the start of a query or fragment
const encodedPath = asset.path const encodedPath = asset.path
.replaceAll('#','%23') .replaceAll('#','%23')
.replaceAll('?','%3F') .replaceAll('?','%3F')
const assetId = asset.name ? asset.name : encodedPath; const assetId = asset.name ? asset.name : encodedPath;
processedAssetIds.push(assetId);
if (pluginAssetsAdded_[assetId]) continue; if (pluginAssetsAdded_[assetId]) continue;
pluginAssetsAdded_[assetId] = true;
let element = null;
if (asset.mime === 'application/javascript') { if (asset.mime === 'application/javascript') {
const script = document.createElement('script'); element = document.createElement('script');
script.src = encodedPath; element.src = encodedPath;
pluginAssetsContainer.appendChild(script); pluginAssetsContainer.appendChild(element);
} else if (asset.mime === 'text/css') { } else if (asset.mime === 'text/css') {
const link = document.createElement('link'); element = document.createElement('link');
link.rel = 'stylesheet'; element.rel = 'stylesheet';
link.href = encodedPath element.href = encodedPath
pluginAssetsContainer.appendChild(link); pluginAssetsContainer.appendChild(element);
}
pluginAssetsAdded_[assetId] = {
element,
}
}
// Once we have added the relevant assets, we also remove those that
// are no longer needed. It's necessary in particular for the CSS
// generated by noteStyle - if we don't remove it, we might end up
// with two or more stylesheet and that will create conflicts.
//
// It was happening for example when automatically switching from
// light to dark theme, and then back to light theme - in that case
// the viewer would remain dark because it would use the dark
// stylesheet that would still be in the DOM.
for (const [assetId, asset] of Object.entries(pluginAssetsAdded_)) {
if (!processedAssetIds.includes(assetId)) {
try {
asset.element.remove();
} catch (error) {
// We don't throw an exception but we log it since
// it shouldn't happen
console.warn('Tried to remove an asset but got an error', error);
}
pluginAssetsAdded_[assetId] = null;
} }
} }
} }

View File

@@ -56,6 +56,19 @@ if (typeof module !== 'undefined') {
const markJsUtils = {}; const markJsUtils = {};
const isInsideContainer = (node, tagName) => {
if (!node) return false;
tagName = tagName.toLowerCase();
while (node) {
if (node.tagName && node.tagName.toLowerCase() === tagName) return true;
node = node.parentNode;
}
return false;
};
markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => { markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
if (typeof keyword === 'string') { if (typeof keyword === 'string') {
keyword = { keyword = {
@@ -71,12 +84,13 @@ markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
if (isBasicSearch) accuracy = 'partially'; if (isBasicSearch) accuracy = 'partially';
if (keyword.type === 'regex') { if (keyword.type === 'regex') {
accuracy = 'complementary'; accuracy = 'complementary';
// Remove the trailing wildcard and "accuracy = complementary" will take care of // Remove the trailing wildcard and "accuracy = complementary" will take
// highlighting the relevant keywords. // care of highlighting the relevant keywords.
// Known bug: it will also highlight word that contain the term as a suffix for example for "ent*", it will highlight "present" // Known bug: it will also highlight word that contain the term as a
// which is incorrect (it should only highlight what starts with "ent") but for now will do. Mark.js doesn't have an option // suffix for example for "ent*", it will highlight "present" which is
// to tweak this behaviour. // incorrect (it should only highlight what starts with "ent") but for
// now will do. Mark.js doesn't have an option to tweak this behaviour.
value = keyword.value.substr(0, keyword.value.length - 1); value = keyword.value.substr(0, keyword.value.length - 1);
} }
@@ -86,6 +100,18 @@ markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
{}, {},
{ {
accuracy: accuracy, accuracy: accuracy,
filter: (node, _term, _totalCounter, _counter) => {
// We exclude SVG because it creates a "<mark>" tag inside
// the document, which is not a valid SVG tag. As a result
// the content within that tag disappears.
//
// mark.js has an "exclude" parameter, but it doesn't work
// so we use "filter" instead.
//
// https://github.com/joplin/plugin-abc-sheet-music
if (isInsideContainer(node, 'SVG')) return false;
return true;
},
}, },
extraOptions extraOptions
) )

View File

@@ -26,7 +26,7 @@ interface ContextMenuProps {
} }
export default class NoteListUtils { export default class NoteListUtils {
static makeContextMenu(noteIds: string[], props: ContextMenuProps) { public static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
const cmdService = CommandService.instance(); const cmdService = CommandService.instance();
const menuUtils = new MenuUtils(cmdService); const menuUtils = new MenuUtils(cmdService);
@@ -212,7 +212,7 @@ export default class NoteListUtils {
return menu; return menu;
} }
static async confirmDeleteNotes(noteIds: string[]) { public static async confirmDeleteNotes(noteIds: string[]) {
if (!noteIds.length) return; if (!noteIds.length) return;
const msg = await Note.deleteMessage(noteIds); const msg = await Note.deleteMessage(noteIds);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@joplin/app-desktop", "name": "@joplin/app-desktop",
"version": "2.10.6", "version": "2.11.1",
"description": "Joplin for Desktop", "description": "Joplin for Desktop",
"main": "main.js", "main": "main.js",
"private": true, "private": true,
@@ -27,6 +27,7 @@
}, },
"build": { "build": {
"appId": "net.cozic.joplin-desktop", "appId": "net.cozic.joplin-desktop",
"compression": "maximum",
"productName": "Joplin", "productName": "Joplin",
"npmRebuild": false, "npmRebuild": false,
"afterSign": "./tools/notarizeMacApp.js", "afterSign": "./tools/notarizeMacApp.js",
@@ -107,7 +108,7 @@
}, },
"homepage": "https://github.com/laurent22/joplin#readme", "homepage": "https://github.com/laurent22/joplin#readme",
"devDependencies": { "devDependencies": {
"@joplin/tools": "~2.10", "@joplin/tools": "~2.11",
"@testing-library/react-hooks": "8.0.1", "@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.2.6", "@types/jest": "29.2.6",
"@types/node": "18.11.18", "@types/node": "18.11.18",
@@ -120,8 +121,8 @@
"electron-rebuild": "3.2.9", "electron-rebuild": "3.2.9",
"glob": "8.1.0", "glob": "8.1.0",
"gulp": "4.0.2", "gulp": "4.0.2",
"jest": "29.4.2", "jest": "29.4.3",
"jest-environment-jsdom": "29.4.2", "jest-environment-jsdom": "29.4.3",
"js-sha512": "0.8.0", "js-sha512": "0.8.0",
"nan": "2.17.0", "nan": "2.17.0",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
@@ -136,9 +137,8 @@
"@electron/remote": "2.0.9", "@electron/remote": "2.0.9",
"@fortawesome/fontawesome-free": "5.15.4", "@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4", "@joeattardi/emoji-button": "4.6.4",
"@joplin/lib": "~2.10", "@joplin/lib": "~2.11",
"@joplin/pdf-viewer": "~2.10", "@joplin/renderer": "~2.11",
"@joplin/renderer": "~2.10",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"codemirror": "5.65.9", "codemirror": "5.65.9",
"color": "3.2.1", "color": "3.2.1",
@@ -147,8 +147,9 @@
"debounce": "1.2.1", "debounce": "1.2.1",
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"formatcoords": "1.1.3", "formatcoords": "1.1.3",
"fs-extra": "11.1.0", "fs-extra": "11.1.1",
"highlight.js": "11.7.0", "highlight.js": "11.7.0",
"html-entities": "1.4.0",
"immer": "7.0.15", "immer": "7.0.15",
"keytar": "7.9.0", "keytar": "7.9.0",
"mark.js": "8.11.1", "mark.js": "8.11.1",
@@ -163,15 +164,15 @@
"react-datetime": "3.2.0", "react-datetime": "3.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-redux": "8.0.5", "react-redux": "8.0.5",
"react-select": "5.7.0", "react-select": "5.7.2",
"react-toggle-button": "2.2.0", "react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1", "react-tooltip": "4.5.1",
"redux": "4.2.1", "redux": "4.2.1",
"reselect": "4.1.7", "reselect": "4.1.7",
"roboto-fontface": "0.10.0", "roboto-fontface": "0.10.0",
"smalltalk": "2.5.1", "smalltalk": "2.5.1",
"sqlite3": "5.1.4", "sqlite3": "5.1.6",
"styled-components": "5.3.6", "styled-components": "5.3.9",
"styled-system": "5.1.5", "styled-system": "5.1.5",
"taboverride": "4.0.3", "taboverride": "4.0.3",
"tinymce": "5.10.6" "tinymce": "5.10.6"

View File

@@ -65,7 +65,7 @@ class GotoAnything {
public static Dialog: any; public static Dialog: any;
public static manifest: any; public static manifest: any;
onTrigger(event: any) { public onTrigger(event: any) {
this.dispatch({ this.dispatch({
type: 'PLUGINLEGACY_DIALOG_SET', type: 'PLUGINLEGACY_DIALOG_SET',
open: true, open: true,
@@ -85,7 +85,7 @@ class Dialog extends React.PureComponent<Props, State> {
private markupToHtml_: MarkupToHtml; private markupToHtml_: MarkupToHtml;
private userCallback_: any = null; private userCallback_: any = null;
constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
const startString = props?.userData?.startString ? props?.userData?.startString : ''; const startString = props?.userData?.startString ? props?.userData?.startString : '';
@@ -119,7 +119,7 @@ class Dialog extends React.PureComponent<Props, State> {
if (startString) this.scheduleListUpdate(); if (startString) this.scheduleListUpdate();
} }
style() { public style() {
const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-'); const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-');
if (this.styles_[styleKey]) return this.styles_[styleKey]; if (this.styles_[styleKey]) return this.styles_[styleKey];
@@ -184,7 +184,7 @@ class Dialog extends React.PureComponent<Props, State> {
return this.styles_[styleKey]; return this.styles_[styleKey];
} }
componentDidMount() { public componentDidMount() {
document.addEventListener('keydown', this.onKeyDown); document.addEventListener('keydown', this.onKeyDown);
this.props.dispatch({ this.props.dispatch({
@@ -193,7 +193,7 @@ class Dialog extends React.PureComponent<Props, State> {
}); });
} }
componentWillUnmount() { public componentWillUnmount() {
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_); if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
document.removeEventListener('keydown', this.onKeyDown); document.removeEventListener('keydown', this.onKeyDown);
@@ -203,7 +203,7 @@ class Dialog extends React.PureComponent<Props, State> {
}); });
} }
onKeyDown(event: any) { public onKeyDown(event: any) {
if (event.keyCode === 27) { // ESCAPE if (event.keyCode === 27) { // ESCAPE
this.props.dispatch({ this.props.dispatch({
pluginName: PLUGIN_NAME, pluginName: PLUGIN_NAME,
@@ -213,7 +213,7 @@ class Dialog extends React.PureComponent<Props, State> {
} }
} }
modalLayer_onClick(event: any) { private modalLayer_onClick(event: any) {
if (event.currentTarget === event.target) { if (event.currentTarget === event.target) {
this.props.dispatch({ this.props.dispatch({
pluginName: PLUGIN_NAME, pluginName: PLUGIN_NAME,
@@ -223,17 +223,17 @@ class Dialog extends React.PureComponent<Props, State> {
} }
} }
helpButton_onClick() { private helpButton_onClick() {
this.setState({ showHelp: !this.state.showHelp }); this.setState({ showHelp: !this.state.showHelp });
} }
input_onChange(event: any) { private input_onChange(event: any) {
this.setState({ query: event.target.value }); this.setState({ query: event.target.value });
this.scheduleListUpdate(); this.scheduleListUpdate();
} }
scheduleListUpdate() { public scheduleListUpdate() {
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_); if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
this.listUpdateIID_ = shim.setTimeout(async () => { this.listUpdateIID_ = shim.setTimeout(async () => {
@@ -242,12 +242,12 @@ class Dialog extends React.PureComponent<Props, State> {
}, 100); }, 100);
} }
async keywords(searchQuery: string) { public async keywords(searchQuery: string) {
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery); const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery);
return SearchEngine.instance().allParsedQueryTerms(parsedQuery); return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
} }
markupToHtml() { public markupToHtml() {
if (this.markupToHtml_) return this.markupToHtml_; if (this.markupToHtml_) return this.markupToHtml_;
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml(); this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
return this.markupToHtml_; return this.markupToHtml_;
@@ -262,7 +262,7 @@ class Dialog extends React.PureComponent<Props, State> {
}; };
} }
async updateList() { public async updateList() {
let resultsInBody = false; let resultsInBody = false;
if (!this.state.query) { if (!this.state.query) {
@@ -402,7 +402,7 @@ class Dialog extends React.PureComponent<Props, State> {
this.itemListRef.current.makeItemIndexVisible(index); this.itemListRef.current.makeItemIndexVisible(index);
} }
async gotoItem(item: any) { public async gotoItem(item: any) {
this.props.dispatch({ this.props.dispatch({
pluginName: PLUGIN_NAME, pluginName: PLUGIN_NAME,
type: 'PLUGINLEGACY_DIALOG_SET', type: 'PLUGINLEGACY_DIALOG_SET',
@@ -465,7 +465,7 @@ class Dialog extends React.PureComponent<Props, State> {
} }
} }
listItem_onClick(event: any) { private listItem_onClick(event: any) {
const itemId = event.currentTarget.getAttribute('data-id'); const itemId = event.currentTarget.getAttribute('data-id');
const parentId = event.currentTarget.getAttribute('data-parent-id'); const parentId = event.currentTarget.getAttribute('data-parent-id');
const itemType = Number(event.currentTarget.getAttribute('data-type')); const itemType = Number(event.currentTarget.getAttribute('data-type'));
@@ -478,7 +478,7 @@ class Dialog extends React.PureComponent<Props, State> {
}); });
} }
renderItem(item: SearchResult) { public renderItem(item: SearchResult) {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = this.style(); const style = this.style();
const isSelected = item.id === this.state.selectedItemId; const isSelected = item.id === this.state.selectedItemId;
@@ -502,7 +502,7 @@ class Dialog extends React.PureComponent<Props, State> {
); );
} }
selectedItemIndex(results: any[] = undefined, itemId: string = undefined) { public selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
if (typeof results === 'undefined') results = this.state.results; if (typeof results === 'undefined') results = this.state.results;
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId; if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
for (let i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
@@ -512,13 +512,13 @@ class Dialog extends React.PureComponent<Props, State> {
return -1; return -1;
} }
selectedItem() { public selectedItem() {
const index = this.selectedItemIndex(); const index = this.selectedItemIndex();
if (index < 0) return null; if (index < 0) return null;
return { ...this.state.results[index], commandArgs: this.state.commandArgs }; return { ...this.state.results[index], commandArgs: this.state.commandArgs };
} }
input_onKeyDown(event: any) { private input_onKeyDown(event: any) {
const keyCode = event.keyCode; const keyCode = event.keyCode;
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
@@ -554,7 +554,7 @@ class Dialog extends React.PureComponent<Props, State> {
return maxItemCount * itemHeight; return maxItemCount * itemHeight;
} }
renderList() { public renderList() {
const style = this.style(); const style = this.style();
const itemListStyle = { const itemListStyle = {
@@ -573,7 +573,7 @@ class Dialog extends React.PureComponent<Props, State> {
); );
} }
render() { public render() {
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
const style = this.style(); const style = this.style();
const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>; const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;

View File

@@ -6,15 +6,15 @@ const { shimInit } = require('@joplin/lib/shim-init-node.js');
const folderId1 = 'aa012345678901234567890123456789'; const folderId1 = 'aa012345678901234567890123456789';
const folderId2 = 'bb012345678901234567890123456789'; const folderId2 = 'bb012345678901234567890123456789';
beforeAll(async () => {
shimInit();
Setting.autoSaveEnabled = false;
PerFolderSortOrderService.initialize();
Setting.setValue('notes.perFolderSortOrderEnabled', true);
});
describe('PerFolderSortOrderService', () => { describe('PerFolderSortOrderService', () => {
beforeAll(async () => {
shimInit();
Setting.autoSaveEnabled = false;
PerFolderSortOrderService.initialize();
Setting.setValue('notes.perFolderSortOrderEnabled', true);
});
test('get(), isSet() and set()', async () => { test('get(), isSet() and set()', async () => {
// Clear all per-folder sort order // Clear all per-folder sort order
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false); expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);

View File

@@ -2,13 +2,13 @@ import { notesSortOrderFieldArray, notesSortOrderNextField, setNotesSortOrder }
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
const { shimInit } = require('@joplin/lib/shim-init-node.js'); const { shimInit } = require('@joplin/lib/shim-init-node.js');
beforeAll(() => {
shimInit();
Setting.autoSaveEnabled = false;
});
describe('notesSortOrderUtils', () => { describe('notesSortOrderUtils', () => {
beforeAll(() => {
shimInit();
Setting.autoSaveEnabled = false;
});
it('should always provide the same ordered fields', async () => { it('should always provide the same ordered fields', async () => {
const expected = ['user_updated_time', 'user_created_time', 'title', 'order']; const expected = ['user_updated_time', 'user_created_time', 'title', 'order'];
expect(notesSortOrderFieldArray()).toStrictEqual(expected); expect(notesSortOrderFieldArray()).toStrictEqual(expected);

View File

@@ -72,10 +72,6 @@ async function main() {
src: langSourceDir, src: langSourceDir,
dest: `${buildLibDir}/tinymce/langs`, dest: `${buildLibDir}/tinymce/langs`,
}, },
{
src: resolve(__dirname, '../../pdf-viewer/dist'),
dest: `${buildLibDir}/@joplin/pdf-viewer`,
},
]; ];
const files = [ const files = [
@@ -93,10 +89,6 @@ async function main() {
src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'), src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'),
dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`, dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`,
}, },
{
src: resolve(__dirname, '../../pdf-viewer/index.html'),
dest: `${buildLibDir}/@joplin/pdf-viewer/index.html`,
},
]; ];
// First we delete all the destination directories, then we copy the files. // First we delete all the destination directories, then we copy the files.

View File

@@ -1,6 +0,0 @@
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
};

View File

@@ -6,24 +6,24 @@ import KvStore from '@joplin/lib/services/KvStore';
export default class PluginAssetsLoader { export default class PluginAssetsLoader {
static instance_: PluginAssetsLoader = null; private static instance_: PluginAssetsLoader = null;
logger_: any = null; private logger_: any = null;
static instance() { public static instance() {
if (PluginAssetsLoader.instance_) return PluginAssetsLoader.instance_; if (PluginAssetsLoader.instance_) return PluginAssetsLoader.instance_;
PluginAssetsLoader.instance_ = new PluginAssetsLoader(); PluginAssetsLoader.instance_ = new PluginAssetsLoader();
return PluginAssetsLoader.instance_; return PluginAssetsLoader.instance_;
} }
setLogger(logger: any) { public setLogger(logger: any) {
this.logger_ = logger; this.logger_ = logger;
} }
logger() { public logger() {
return this.logger_; return this.logger_;
} }
async importAssets() { public async importAssets() {
const destDir = `${Setting.value('resourceDir')}/pluginAssets`; const destDir = `${Setting.value('resourceDir')}/pluginAssets`;
await shim.fsDriver().mkdir(destDir); await shim.fsDriver().mkdir(destDir);

View File

@@ -150,8 +150,8 @@ android {
applicationId "net.cozic.joplin" applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097683 versionCode 2097687
versionName "2.10.7" versionName "2.11.2"
// ndk { // ndk {
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" // abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
// } // }

View File

@@ -2,7 +2,7 @@ const { BackButtonService } = require('../services/back-button.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;
export default class BackButtonDialogBox extends DialogBox { export default class BackButtonDialogBox extends DialogBox {
constructor(props: any) { public constructor(props: any) {
super(props); super(props);
this.backHandler_ = () => { this.backHandler_ = () => {
@@ -14,7 +14,7 @@ export default class BackButtonDialogBox extends DialogBox {
}; };
} }
async componentDidUpdate() { public async componentDidUpdate() {
if (this.state.isVisible) { if (this.state.isVisible) {
BackButtonService.addHandler(this.backHandler_); BackButtonService.addHandler(this.backHandler_);
} else { } else {

View File

@@ -14,7 +14,7 @@ import Setting from '@joplin/lib/models/Setting';
Icon.loadFont().catch((error: any) => { console.info(error); }); Icon.loadFont().catch((error: any) => { console.info(error); });
class CameraView extends Component { class CameraView extends Component {
constructor() { public constructor() {
super(); super();
const dimensions = Dimensions.get('window'); const dimensions = Dimensions.get('window');
@@ -34,18 +34,18 @@ class CameraView extends Component {
this.onLayout = this.onLayout.bind(this); this.onLayout = this.onLayout.bind(this);
} }
onLayout(event: any) { public onLayout(event: any) {
this.setState({ this.setState({
screenWidth: event.nativeEvent.layout.width, screenWidth: event.nativeEvent.layout.width,
screenHeight: event.nativeEvent.layout.height, screenHeight: event.nativeEvent.layout.height,
}); });
} }
back_onPress() { private back_onPress() {
if (this.props.onCancel) this.props.onCancel(); if (this.props.onCancel) this.props.onCancel();
} }
reverse_onPress() { private reverse_onPress() {
if (this.props.cameraType === RNCamera.Constants.Type.back) { if (this.props.cameraType === RNCamera.Constants.Type.back) {
Setting.setValue('camera.type', RNCamera.Constants.Type.front); Setting.setValue('camera.type', RNCamera.Constants.Type.front);
} else { } else {
@@ -53,7 +53,7 @@ class CameraView extends Component {
} }
} }
ratio_onPress() { private ratio_onPress() {
if (this.state.ratios.length <= 1) return; if (this.state.ratios.length <= 1) return;
let index = this.state.ratios.indexOf(this.props.cameraRatio); let index = this.state.ratios.indexOf(this.props.cameraRatio);
@@ -62,7 +62,7 @@ class CameraView extends Component {
Setting.setValue('camera.ratio', this.state.ratios[index]); Setting.setValue('camera.ratio', this.state.ratios[index]);
} }
async photo_onPress() { private async photo_onPress() {
if (!this.camera || !this.props.onPhoto) return; if (!this.camera || !this.props.onPhoto) return;
this.setState({ snapping: true }); this.setState({ snapping: true });
@@ -79,14 +79,14 @@ class CameraView extends Component {
} }
async onCameraReady() { public async onCameraReady() {
if (this.supportsRatios()) { if (this.supportsRatios()) {
const ratios = await this.camera.getSupportedRatiosAsync(); const ratios = await this.camera.getSupportedRatiosAsync();
this.setState({ ratios: ratios }); this.setState({ ratios: ratios });
} }
} }
renderButton(onPress: Function, iconNameOrIcon: any, style: any) { public renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
let icon = null; let icon = null;
if (typeof iconNameOrIcon === 'string') { if (typeof iconNameOrIcon === 'string') {
@@ -112,7 +112,7 @@ class CameraView extends Component {
); );
} }
fitRectIntoBounds(rect: any, bounds: any) { public fitRectIntoBounds(rect: any, bounds: any) {
const rectRatio = rect.width / rect.height; const rectRatio = rect.width / rect.height;
const boundsRatio = bounds.width / bounds.height; const boundsRatio = bounds.width / bounds.height;
@@ -130,7 +130,7 @@ class CameraView extends Component {
return newDimensions; return newDimensions;
} }
cameraRect(ratio: string) { public cameraRect(ratio: string) {
// To keep the calculations simpler, it's assumed that the phone is in // To keep the calculations simpler, it's assumed that the phone is in
// portrait orientation. Then at the end we swap the values if needed. // portrait orientation. Then at the end we swap the values if needed.
const splitted = ratio.split(':'); const splitted = ratio.split(':');
@@ -152,11 +152,11 @@ class CameraView extends Component {
return output; return output;
} }
supportsRatios() { public supportsRatios() {
return shim.mobilePlatform() === 'android'; return shim.mobilePlatform() === 'android';
} }
render() { public render() {
const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera'; const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera';
const displayRatios = this.supportsRatios() && this.state.ratios.length > 1; const displayRatios = this.supportsRatios() && this.state.ratios.length > 1;

View File

@@ -2,6 +2,7 @@
* @jest-environment jsdom * @jest-environment jsdom
*/ */
import { EditorSettings } from '../types'; import { EditorSettings } from '../types';
import { initCodeMirror } from './CodeMirror'; import { initCodeMirror } from './CodeMirror';
import { themeStyle } from '@joplin/lib/theme'; import { themeStyle } from '@joplin/lib/theme';
@@ -9,6 +10,8 @@ import Setting from '@joplin/lib/models/Setting';
import { forceParsing } from '@codemirror/language'; import { forceParsing } from '@codemirror/language';
import loadLangauges from './testUtil/loadLanguages'; import loadLangauges from './testUtil/loadLanguages';
import { expect, describe, it } from '@jest/globals';
const createEditorSettings = (themeId: number) => { const createEditorSettings = (themeId: number) => {
const themeData = themeStyle(themeId); const themeData = themeStyle(themeId);
@@ -23,6 +26,14 @@ const createEditorSettings = (themeId: number) => {
}; };
describe('CodeMirror', () => { describe('CodeMirror', () => {
// This checks for a regression -- occasionally, when updating packages,
// syntax highlighting in the CodeMirror editor stops working. This is usually
// fixed by
// 1. removing all `@codemirror/` and `@lezer/` dependencies from yarn.lock,
// 2. upgrading all CodeMirror packages to the latest versions in package.json, and
// 3. re-running `yarn install`.
//
// See https://github.com/laurent22/joplin/issues/7253
it('should give headings a different style', async () => { it('should give headings a different style', async () => {
const headerLineText = '# Testing...'; const headerLineText = '# Testing...';
const initialText = `${headerLineText}\nThis is a test.`; const initialText = `${headerLineText}\nThis is a test.`;

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