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

Compare commits

...

164 Commits

Author SHA1 Message Date
Laurent Cozic
e1cd8d9b85 Server v2.10.8 2023-02-09 18:24:30 +00:00
Laurent Cozic
293f621e46 Server: Fixed sharing issue when a user no longer has a user item associated with their account 2023-02-09 18:24:09 +00:00
Joplin Bot
c5b551bbcb Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-09 18:17:12 +00:00
Laurent Cozic
ac776eabc1 Server v2.10.7 2023-02-09 15:57:38 +00:00
Laurent Cozic
05c17fbfac Server: Fixed sharing issue for changes that are associated with deleted items 2023-02-09 15:56:44 +00:00
Laurent Cozic
6b9de394ca Tools: Update post release commands 2023-02-09 11:48:22 +00:00
Laurent Cozic
bef9a29581 Renovate exclude 2023-02-09 01:34:02 +00:00
renovate[bot]
1546aad7e9 Update dependency punycode to v2.3.0 (#7749) 2023-02-09 01:33:32 +00:00
renovate[bot]
fe47fef261 Update dependency redux to v4.2.1 (#7743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 00:24:35 +00:00
renovate[bot]
288f4ab43b Update dependency react-native-image-picker to v5.0.1 (#7747)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-08 20:36:31 +00:00
renovate[bot]
294cbaea4d Update dependency react-native-image-picker to v5 (#7746) 2023-02-08 17:01:40 +00:00
Carlos
f5fc1f2f22 Mobile: Resolves #1044: Add create sub-notebook feature (#7728) 2023-02-08 14:34:29 +00:00
Henry Heino
c90865c4d2 Mobile: Fixes #7700: Fix double-scroll issue in long notes (#7701) 2023-02-08 14:24:59 +00:00
Helmut K. C. Tessarek
322641ccd6 Desktop: Fixes #7694: Markdown editor not surrounding highlighted text with backticks (#7697) 2023-02-08 14:24:20 +00:00
Henry Heino
2656666ed8 Mobile: Fixes #7687: Fix startup error (#7688) 2023-02-08 14:21:59 +00:00
andy1631
2d673902a4 Desktop: Fix highlighting in GotoAnything dialogue (#7592) 2023-02-08 14:20:54 +00:00
Julien
631c41a1ff Desktop: Resolves #6143: Show installed plugins in Help - About Joplin (#7711) 2023-02-08 14:16:09 +00:00
renovate[bot]
7841c99c02 Update dependency @rmp135/sql-ts to v1.16.0 (#7736) 2023-02-08 14:13:42 +00:00
renovate[bot]
ac75d8f6ac Update dependency react-native-safe-area-context to v4.5.0 (#7744) 2023-02-08 14:13:28 +00:00
github-actions[bot]
200ff617dc @rakeshhotker has signed the CLA from Pull Request #7745 2023-02-08 06:07:30 +00:00
Joplin Bot
bbdc18f371 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-07 18:18:46 +00:00
Joplin Bot
0d35b64f9a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-07 12:21:36 +00:00
renovate[bot]
765c4482d6 Update dependency react-native-webview to v11.26.1 (#7735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-07 06:18:23 +00:00
renovate[bot]
0a0e31a37c Update dependency madge to v5.0.2 (#7734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-07 02:34:11 +00:00
Laurent Cozic
0c1f4031b4 Server v2.10.6 2023-02-06 19:01:57 +00:00
Laurent Cozic
9ed022458b Chore: Server: Clean up 2023-02-06 18:59:36 +00:00
Laurent Cozic
ba5f0bc6e3 Server: Fixed issue when an item is associated with a share that no longer exists 2023-02-06 18:59:36 +00:00
renovate[bot]
793e8f6c0f Update dependency jsdom to v21 (#7732) 2023-02-06 17:29:47 +00:00
renovate[bot]
6182ce521d Update dependency nodemailer to v6.9.1 (#7726) 2023-02-06 16:17:15 +00:00
Joplin Bot
544c50663a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-06 10:17:57 +00:00
Laurent Cozic
53aa9e2b42 Chore: Restore stats 2023-02-06 10:07:25 +00:00
renovate[bot]
884260189c Update dependency punycode to v2.2.2 (#7724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-05 20:28:43 +00:00
Laurent Cozic
2f9464f21f Tools: Revert CI issue 2023-02-05 19:10:31 +00:00
github-actions[bot]
c6e993b04a @TaoK has signed the CLA from Pull Request #7729 2023-02-05 17:06:32 +00:00
Laurent Cozic
89eb012b25 Tools: Add repeat mechanism when electron-builder randomly fails to build 2023-02-05 16:51:47 +00:00
Laurent Cozic
1e2aa4e2b5 Tools: Fixed Renovate patterns 2023-02-05 12:27:09 +00:00
Laurent Cozic
0019bb8d6b Tools: Add eslint rule "@typescript-eslint/no-inferrable-types" 2023-02-05 12:27:09 +00:00
renovate[bot]
e629a4d325 Update dependency nodemailer to v6.9.0 (#7722) 2023-02-05 11:43:37 +00:00
renovate[bot]
049c769d37 Update dependency eslint-plugin-react to v7.32.0 (#7698) 2023-02-05 11:41:13 +00:00
renovate[bot]
47aed8742a Update dependency punycode to v2.2.0 (#7695) 2023-02-05 11:40:58 +00:00
Adarsh Singh
8aad67ccfe Desktop: Fixes #7521: Mermaid images are incorrectly sized when exported as PNG (#7546) 2023-02-05 11:39:26 +00:00
renovate[bot]
af7cbcbca7 Update dependency eslint-plugin-import to v2.27.4 (#7717) 2023-02-05 11:06:20 +00:00
Laurent Cozic
88a91314af Tools: Reduce noise with Renovate updates 2023-02-05 11:06:10 +00:00
renovate[bot]
9873c2d756 Update dependency glob to v8.1.0 (#7718) 2023-02-04 16:12:16 +00:00
renovate[bot]
46b68cf461 Update typescript-eslint monorepo to v5.48.2 (#7705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 07:58:54 +00:00
renovate[bot]
6b96b1f355 Update dependency react-native-paper to v5.1.4 (#7714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 05:37:03 +00:00
renovate[bot]
dc819700bb Update dependency knex to v2.4.2 (#7713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 01:11:49 +00:00
renovate[bot]
1d1d5fea06 Update dependency @react-native-community/datetimepicker to v6.7.3 (#7712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 21:02:58 +00:00
renovate[bot]
c3afc0ede7 Update dependency prettier to v2.8.3 (#7704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 17:08:36 +00:00
github-actions[bot]
2092110a6e @julien-me has signed the CLA from Pull Request #7711 2023-02-03 12:51:14 +00:00
github-actions[bot]
fc940e9a7c @abhiippili has signed the CLA from Pull Request #7709 2023-02-01 17:12:16 +00:00
github-actions[bot]
8718310dd0 @deepampriyadarshi has signed the CLA from Pull Request #7708 2023-02-01 15:00:52 +00:00
renovate[bot]
1b527f2bbe Update dependency @react-native-community/slider to v4.4.1 (#7702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-01 00:38:41 +00:00
Joplin Bot
4da217bc2f Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-31 00:43:35 +00:00
github-actions[bot]
d3abd4ebf2 @tessus has signed the CLA from Pull Request #7697 2023-01-31 00:13:17 +00:00
Mr-Kanister
38851edf86 All: Translation: Update de_DE.po (#7696) 2023-01-30 18:15:05 -05:00
Joplin Bot
05efb765d6 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-30 18:16:44 +00:00
majsterkovic
28dc4a6abd All: Translation: Update pl_PL.po (#7690) 2023-01-29 22:54:17 -05:00
renovate[bot]
2f7b56f96f Update dependency @react-native-community/datetimepicker to v6.7.2 (#7689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-30 02:26:11 +00:00
renovate[bot]
fdaa3735fb Update dependency @types/yargs to v17.0.20 (#7685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 17:03:51 +00:00
Laurent Cozic
7dfaea12f7 Chore: Fixed build following conversion from JSX to TSX 2023-01-29 13:11:53 +00:00
renovate[bot]
18199b27d9 Update dependency @types/react to v17.0.53 (#7684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 06:18:39 +00:00
renovate[bot]
a7c52082bb Update dependency @types/react to v16.14.35 (#7683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 02:18:29 +00:00
renovate[bot]
3b5357e0c1 Update dependency @types/jest to v29.2.6 (#7682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 22:41:15 +00:00
Self Not Found
10dd4e45ed Desktop: Fixes #7678: Fix open files with non-ASCII characters in path (#7679) 2023-01-28 12:28:01 +00:00
github-actions[bot]
1963835309 @carlosngo has signed the CLA from Pull Request #7681 2023-01-28 10:07:12 +00:00
renovate[bot]
46ec0c1381 Update dependency knex to v2.4.1 (#7676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 03:43:16 +00:00
Laurent Cozic
bb84ae4d68 Tools: Disable tests that randomly fail onn CI 2023-01-27 18:55:39 +00:00
Laurent Cozic
b3ff53c0da Tools: Added hack to try to fix issue with broken GitHub API 2023-01-27 17:16:23 +00:00
Self Not Found
acd7bfd9f5 Desktop: Remove auto-matching for greater than character (#7669) 2023-01-27 16:50:07 +00:00
renovate[bot]
1dff50d080 Update dependency knex to v2.4.0 (#7674) 2023-01-27 16:47:20 +00:00
Laurent Cozic
af40970d09 Ignore package 2023-01-27 16:46:37 +00:00
Joplin Bot
07535a494e Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-27 06:17:16 +00:00
Joplin Bot
907422cefa Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-27 00:45:49 +00:00
renovate[bot]
f643baea25 Update dependency tap to v16.3.4 (#7671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 08:50:49 +00:00
github-actions[bot]
55a4f33982 @majsterkovic has signed the CLA from Pull Request #7668 2023-01-25 21:26:20 +00:00
Dmitriy Q
df6700959a All: Translation: Update ru_RU.po (#7665) 2023-01-25 16:10:50 -05:00
github-actions[bot]
fde8235f3e @krote5k has signed the CLA from Pull Request #7665 2023-01-25 12:59:46 +00:00
Laurent Cozic
f6ba56d966 Doc: Added GSoC idea 2023-01-24 15:56:22 +00:00
Laurent Cozic
4a5312823b Doc: Add info for GSoC 2023 2023-01-24 15:18:51 +00:00
Light
31a27b0e1c Desktop: Fixes #7565: Fix text editor text highlighting when used with special IME methods (#7630) 2023-01-24 14:46:40 +00:00
github-actions[bot]
984ad868e8 @trevor-james-nangosha has signed the CLA from Pull Request #7663 2023-01-24 12:56:39 +00:00
Betty Alagwu
9b657eeda2 Desktop: Resolves #7602: Fix copy text with no selection (#7641) 2023-01-23 18:50:24 +00:00
Laurent Cozic
6f3ad4b3b0 Doc: Allow setting period from parameter 2023-01-23 17:49:16 +00:00
renovate[bot]
56f06fae3c Update dependency ts-jest to v29.0.5 (#7659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 15:52:54 +00:00
ThetaDev
c22d884357 Doc: Update rscss link (#7653) 2023-01-23 10:11:34 +00:00
github-actions[bot]
35dc22197d @Theta-Dev has signed the CLA from Pull Request #7653 2023-01-22 22:43:08 +00:00
renovate[bot]
70d56ca0be Update dependency ts-jest to v29.0.4 (#7651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-22 02:05:55 +00:00
Laurent Cozic
bca09b9476 Android 2.10.5 2023-01-21 14:28:32 +00:00
javad mnjd
b82bf16505 Android: Resolves #6942: Improve filesystem sync performance (#7637) 2023-01-21 14:11:37 +00:00
Laurent Cozic
2f254d81cd Ignore some dependencies 2023-01-21 12:20:22 +00:00
Self Not Found
b14ce03e5b All: Translation: Update zh_CN.po (#7643) 2023-01-20 20:38:45 -05:00
Laurent Cozic
90b04cbd37 Doc: Fixes #7642: Fixed menu padding in narrower view 2023-01-20 18:07:50 +00:00
Laurent Cozic
5ae866ea85 iOS 12.10.2 2023-01-20 17:42:14 +00:00
Laurent Cozic
b450ab9f5a lock file 2023-01-20 17:41:03 +00:00
Laurent Cozic
138bc8144b Android: Fixes non-working alarms
Also imported react-native-alarm-notificatio into the project
2023-01-20 17:33:19 +00:00
Laurent Cozic
c9831833c4 Desktop: Fixes #7617: Note editor scrolls back to top when editing certain notes 2023-01-20 15:05:57 +00:00
Laurent Cozic
2813f93c18 Desktop: Fixes #7617: Note editor scrolls back to top when editing certain notes 2023-01-20 15:03:22 +00:00
Laurent Cozic
27bec674a0 Chore: Desktop: Convert last JSX files to TSX 2023-01-20 14:35:22 +00:00
renovate[bot]
ff79ca8781 Update dependency react-native-paper to v5.1.3 (#7638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-20 01:12:04 +00:00
github-actions[bot]
34a1342db6 @jd1378 has signed the CLA from Pull Request #7637 2023-01-19 20:42:18 +00:00
renovate[bot]
e252986b98 Update dependency tap to v16.3.3 (#7631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-19 17:25:42 +00:00
Helmut K. C. Tessarek
3537c3e5f9 All: Translation: Update da_DK.po (thanks ERYpTION) 2023-01-19 08:19:19 -05:00
github-actions[bot]
fdc86f94c4 @LightAPIs has signed the CLA from Pull Request #7630 2023-01-18 17:05:59 +00:00
Laurent Cozic
dc5dc94ed5 Desktop: Fixes #7621: Certain plugins could create invalid settings, which could result in a crash 2023-01-17 15:34:04 +00:00
Laurent Cozic
f7682d3da3 Desktop: Resolves #7506: Disable custom PDF viewer by default 2023-01-17 13:35:08 +00:00
Joplin Bot
8e2975d23d Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-16 17:48:37 +00:00
Laurent Cozic
ce85489166 Docs: Added news about "GitHub Action Raw Log Viewer" 2023-01-16 17:28:30 +00:00
Mike Sheldon
13f5738090 All: Resolves #7627: Improve dialogue spacing in Fountain renderer (#7628) 2023-01-16 15:46:17 +00:00
github-actions[bot]
cce2ae7401 @Elleo has signed the CLA from Pull Request #7628 2023-01-16 14:00:37 +00:00
Laurent Cozic
c9b49a50c8 Desktop release v2.10.5 2023-01-16 13:41:31 +00:00
github-actions[bot]
c419c43622 @JackGruber has signed the CLA from Pull Request #7622 2023-01-15 15:44:53 +00:00
Joplin Bot
cc5ecfba2b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-15 06:17:01 +00:00
Joplin Bot
a98d5feff6 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-15 00:44:15 +00:00
Laurent Cozic
7aa4feffd4 Update translations 2023-01-14 20:56:46 +00:00
Joplin Bot
0b46a744f1 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-14 18:17:51 +00:00
Laurent Cozic
4d5efff2af Android 2.10.4 2023-01-14 17:31:24 +00:00
Laurent Cozic
4b2aaa6836 lock file 2023-01-14 17:31:03 +00:00
Laurent Cozic
4e27600f9c fix build 2023-01-14 17:24:50 +00:00
Laurent Cozic
6d4394a88d Merge branch 'dev' into release-2.10 2023-01-14 16:57:14 +00:00
Joplin Bot
aad9c803ba Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-14 00:41:30 +00:00
Joplin Bot
5418cc82ca Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-13 18:16:59 +00:00
Joplin Bot
79eb33a7f4 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-12 18:18:15 +00:00
Andris Strazdiņš
cdef433f38 Chore: Correct language labels for Latvian language. (#7615) 2023-01-12 17:11:01 +00:00
github-actions[bot]
b29b50df0b @andzs has signed the CLA from Pull Request #7615 2023-01-12 14:38:42 +00:00
Laurent Cozic
91c3986f88 Tools: Remove .d.ts and .map files from ignored files (not needed anymore) 2023-01-12 13:33:39 +00:00
Joplin Bot
29e4a335fb Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-12 12:22:35 +00:00
Wartijn
4e942afba6 API: Fixes #6862 set todo related fields when adding or changing a todo (#7395) 2023-01-11 19:14:19 +00:00
Self Not Found
3bee0a142b Desktop: Fixes #6211: Try to replace the external link with internal link when attachment file is pasted in Markdown editor (#6865) 2023-01-11 19:12:34 +00:00
Henry Heino
9a9fdef512 Mobile: Confirm closing settings with unsaved changes (#7566) 2023-01-11 18:45:00 +00:00
Laurent Cozic
4888b4bd14 Tools: Enable eslint rule "comma-spacing" 2023-01-11 18:40:14 +00:00
Joplin Bot
bba3af17b7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-11 18:20:46 +00:00
Henry Heino
b695acf4ed Mobile: Fixes #6682: Fixed issue when floating keyboard is visible (#7593) 2023-01-11 17:17:55 +00:00
Laurent Cozic
3367b52b53 Desktop: Fixes #7605: Hyperlink insertion no longer works in Markdown editor 2023-01-11 14:01:10 +00:00
github-actions[bot]
c31622f286 @matthijskooijman has signed the CLA from Pull Request #7611 2023-01-11 12:48:29 +00:00
Laurent Cozic
488e469e33 Desktop: Fixes #7610: Optimise sidebar rendering speed 2023-01-10 18:32:06 +00:00
Laurent Cozic
fa7d48a3bd Chore: Desktop: Add more ways to automatically populate the database 2023-01-10 18:14:48 +00:00
Henry Heino
0cd2fd660d iOS: Fixes #7553: Fix Dropdown accessibility (#7564) 2023-01-10 17:13:32 +00:00
Laurent Cozic
6bb52d5ad6 Mobile: Add support for multiple profiles 2023-01-10 12:08:13 +00:00
Laurent Cozic
c2d5fc13d7 Tools: Create an Android pre-release by default 2023-01-08 17:40:02 +00:00
Laurent Cozic
b1e5896d4c lock file 2023-01-08 17:38:25 +00:00
renovate[bot]
cc42b7bc38 Update dependency react-native-paper to v5.1.2 (#7600) 2023-01-08 17:29:37 +00:00
Laurent Cozic
4bdb3d0b7e Mobile: Upgrade to React Native 0.70 2023-01-08 17:23:53 +00:00
renovate[bot]
6601547b81 Update dependency react-native-paper to v5.1.0 (#7599) 2023-01-08 15:10:03 +00:00
Laurent Cozic
7e29804e68 Mobile: Upgrade to React Native 0.69 2023-01-08 13:39:27 +00:00
Henry Heino
257a24166e Chore: Mobile: Migrate action button to react-native-paper (#7477) 2023-01-08 12:22:41 +00:00
Laurent Cozic
8b3c9e81a7 Desktop: Fixes #7598: Fixed crash when loading certain plugins 2023-01-08 11:26:36 +00:00
Joplin Bot
c65e051e1f Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-07 18:18:23 +00:00
Henry Heino
a91c8ffbf3 Mobile: Fixes #7594: Remove gray line around text editor (#7595) 2023-01-07 17:49:14 +00:00
Henry Heino
0530c74924 Mobile: Configurable editor font size (#7596) 2023-01-07 17:47:52 +00:00
Henry Heino
2feed88c51 Tools: Ignore autogenerated .bundle.js.md5 file (#7590) 2023-01-07 17:45:43 +00:00
Laurent Cozic
2017e9d7e9 Doc: Add new sponsor 2023-01-07 16:08:14 +00:00
github-actions[bot]
afe6cb9454 @andy1631 has signed the CLA from Pull Request #7592 2023-01-07 15:21:19 +00:00
github-actions[bot]
690e22b39a @Mighil31 has signed the CLA from Pull Request #7591 2023-01-07 15:16:53 +00:00
Joplin Bot
8a0aa40707 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-07 12:18:35 +00:00
renovate[bot]
1f40a14084 Update dependency aws-sdk to v2.1290.0 (#7585) 2023-01-07 09:39:17 +00:00
Laurent Cozic
6b9a270225 Desktop: Fixes crash when changing note time from properties dialog 2023-01-06 20:50:11 +00:00
Laurent Cozic
0bc55bff7f Tools: Add delay before creating Renovate PR 2023-01-05 21:33:41 +00:00
Laurent Cozic
d9215b044f Tools: Add delay before creating Renovate PR 2023-01-05 21:31:12 +00:00
Laurent Cozic
2f9384f891 lock file 2023-01-05 21:13:08 +00:00
Joplin Bot
a172b1380e Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-05 18:17:06 +00:00
Laurent Cozic
0d4c074e84 Tools: Refactor Discourse code 2023-01-05 17:05:40 +00:00
Laurent Cozic
9eff7e6060 Mobile: Fixed biometics prompt on new devices 2023-01-05 13:30:09 +00:00
renovate[bot]
1edb345e85 Update dependency react-native-share to v8.1.0 (#7582) 2023-01-05 13:10:18 +00:00
297 changed files with 25786 additions and 23750 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -109,6 +109,7 @@ module.exports = {
'exports': 'always-multiline',
'functions': 'never',
}],
'comma-spacing': ['error', { 'before': false, 'after': true }],
'no-trailing-spaces': 'error',
'linebreak-style': ['error', 'unix'],
'prefer-template': ['error'],
@@ -158,6 +159,7 @@ module.exports = {
// 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/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
'@typescript-eslint/comma-dangle': ['error', {
'arrays': 'always-multiline',
'objects': 'always-multiline',

1560
.gitignore vendored

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -728,6 +728,16 @@ footer .bottom-links-row p {
}
}
/*****************************************************************
LARGE VIEW
*****************************************************************/
@media (max-width: 1200px) {
#nav-section a {
margin-left: 10px;
}
}
/*****************************************************************
MEDIUM VIEW
- Make menu bar elements smaller and closer to each others

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -1,4 +1,15 @@
<?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>Wed, 21 Dec 2022 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0)]]></title><description><![CDATA[<p>As was <a href="https://discourse.joplinapp.org/t/rfc-switch-to-agpl-license-for-joplin-server/16529">discussed last year</a>, Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0) for the desktop, mobile and CLI applications, as well as the web clipper.</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>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>
<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><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>While it's not impossible to read, all colours that would display nicely in a terminal are gone and replaced by <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI codes</a>. You can find what you need in there but it's not particularly easy.</p>
<p>This is where the new <strong>GitHub Action Raw Log Viewer</strong> extension for Chrome can help. It will parse your raw log and convert the ANSI codes to proper colours. This results in a much more readable rendering:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log-colored.png" alt="Raw log with extension"></p>
<p>The extension is fast even for very large logs and it's of course easy to search for text since it simply works with your browser built-in search.</p>
<p>The extension is open source, with the code available here: <a href="https://github.com/laurent22/github-actions-logs-extension">https://github.com/laurent22/github-actions-logs-extension</a></p>
<p>And to install it, follow this link:</p>
<p><a href="https://chrome.google.com/webstore/detail/github-action-raw-log-vie/lgejlnoopmcdglhfjblaeldbcfnmjddf"><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-extension-get-it-now.png" alt="Download GitHub Action Raw Log Viewer extension"></a></p>
]]></description><link>https://joplinapp.org/news/20230116-github-actions-log-viewer/</link><guid isPermaLink="false">20230116-github-actions-log-viewer</guid><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><twitter-text>Introducing the &quot;GitHub Action Raw Log Viewer&quot; extension for Chrome</twitter-text></item><item><title><![CDATA[Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0)]]></title><description><![CDATA[<p>As was <a href="https://discourse.joplinapp.org/t/rfc-switch-to-agpl-license-for-joplin-server/16529">discussed last year</a>, Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0) for the desktop, mobile and CLI applications, as well as the web clipper.</p>
<p>Any open source or commercial fork of Joplin will have to license any changes they make under AGPL, and share these changes back with the community. This is the main reason we switch to this license. It allows us to continue releasing the project as open source while ensuring that those who benefit commercially (or not) from it share back their changes.</p>
<h2>What is the GPL license?<a name="what-is-the-gpl-license" href="#what-is-the-gpl-license" class="heading-anchor">🔗</a></h2>
<p>The AGPL license is based on the GPL license. This is what tldr;Legal has to say about the GPL license:</p>
@@ -288,9 +299,4 @@
<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><item><title><![CDATA[Joplin Cloud is officially production ready!]]></title><description><![CDATA[<p><a href="https://joplinapp.org/plans/">Joplin Cloud</a> has been out of beta for a few weeks now and since then it has been quietly running without any troubles. There is no known bugs and the service is running smoothly so it's now safe to say that it is production ready!</p>
<p>As a reminder, Joplin Cloud is meant to provide a more seamless Joplin experience - if you want to quickly get started, it's as easy as downloading the app and getting a Joplin Cloud account. Besides improved sync performance, that will give you the ability to collaborate on notebooks with others, as well as publishing and sharing notes.</p>
<p>Of course Joplin still supports other sync options such as Nextcloud, Dropbox and OneDrive or AWS S3. You can also self host using Joplin Server. The advantage of Joplin Cloud being that you don't need to maintain a server yourself - for a small fee you'll get that taken care of.</p>
<p>Additionally, subscribing to Joplin Cloud is a great way to support the project as a whole, including the open source applications. Such support is needed in the long term to provide bug and security fixes, add new features, and provide support.</p>
<p>At some level it is also an experiment, to see if such a service is financially viable and can allow me to work full time on the project. This is certainly something I would like, and perhaps Joplin Cloud combined with your donations will allow that.</p>
]]></description><link>https://joplinapp.org/news/20210831-154354/</link><guid isPermaLink="false">20210831-154354</guid><pubDate>Tue, 31 Aug 2021 15:43:54 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
]]></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

@@ -130,7 +130,11 @@
});
setupBetaHandling(urlQuery);
applyPeriod('yearly');
if (urlQuery.get('period') === 'monthly') {
// Nothing - this is the default
} else {
applyPeriod('yearly');
}
});
</script>
</div>

View File

@@ -36,7 +36,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/J
Operating System | Download | Alt. Download
---|---|---
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.8.1/joplin-v2.8.1.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.8.1/joplin-v2.8.1-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.9.8/joplin-v2.9.8.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.9.8/joplin-v2.9.8-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -64,7 +64,7 @@ A community maintained list of these distributions can be found here: [Unofficia
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://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://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>
<!-- SPONSORS-ORG -->
* * *
@@ -530,47 +530,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&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) | 82%
<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/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) | 59%
<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) | | 46%
<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) | 92%
<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) | 92%
<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) | 79%
<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) | 99%
<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/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/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/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/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/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/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/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/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) | 91%
<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/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/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 98%
<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 | 98%
<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) | 30%
<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) | 92%
<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) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 80%
<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) | 80%
<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) | | 81%
<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) | 91%
<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) | 91%
<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) | 57%
<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) | 92%
<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) | 91%
<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) | 75%
<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) | 52%
<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) | 83%
<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) | 99%
<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) | | 38%
<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) | | 80%
<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) | 92%
<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) | 75%
<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) | 91%
<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) | 83%
<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) | | 67%
<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) | 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) | 92%
<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) | 92%
<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) | 92%
<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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -317,6 +317,9 @@
"packages/app-tools/github_oauth_token.txt": true,
"packages/generator-joplin/generators/app/templates/api/": true,
"packages/htmlpack/dist/": true,
"packages/react-native-alarm-notification/android/build": true,
"packages/react-native-saf-x/android/build": true,
"packages/react-native-saf-x/android/wrapper": true,
"packages/renderer/**/.vscode/": true,
"packages/renderer/**/copyLib.bat": true,
"packages/renderer/**/node_modules/": true,

View File

@@ -65,21 +65,21 @@
},
"devDependencies": {
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.48.0",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"cspell": "5.21.2",
"eslint": "8.31.0",
"eslint-interactive": "10.3.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.27.4",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react": "7.32.0",
"fs-extra": "11.1.0",
"glob": "8.0.3",
"glob": "8.1.0",
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "13.1.0",
"madge": "5.0.1",
"madge": "5.0.2",
"npm-package-json-lint": "6.4.0",
"typedoc": "0.17.8",
"typescript": "4.9.4"

View File

@@ -26,7 +26,7 @@ class Command extends BaseCommand {
const destinationDuplicates = await Folder.search({ titlePattern: destination, limit: 2 });
if (destinationDuplicates.length > 1) {
throw new Error(_('Ambiguous notebook "%s". Please use short notebook id instead - press "ti" to see the short notebook id' , destination));
throw new Error(_('Ambiguous notebook "%s". Please use short notebook id instead - press "ti" to see the short notebook id', destination));
}
const itemFolder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);

View File

@@ -12,7 +12,7 @@ class Command extends BaseCommand {
}
async action() {
this.stdout(versionInfo(require('./package.json')).message);
this.stdout(versionInfo(require('./package.json'), {}).message);
}
}

View File

@@ -36,7 +36,7 @@ class FolderListWidget extends ListWidget {
if (Setting.value('showNoteCounts')) {
let noteCount = item.note_count;
// Subtract children note_count from parent folder.
if (this.folderHasChildren_(this.folders,item.id)) {
if (this.folderHasChildren_(this.folders, item.id)) {
for (let i = 0; i < this.folders.length; i++) {
if (this.folders[i].parent_id === item.id) {
noteCount -= this.folders[i].note_count;

View File

@@ -42,7 +42,7 @@
"dependencies": {
"@joplin/lib": "~2.10",
"@joplin/renderer": "~2.10",
"aws-sdk": "2.1288.0",
"aws-sdk": "2.1290.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",
"fs-extra": "11.1.0",
@@ -70,7 +70,7 @@
"devDependencies": {
"@joplin/tools": "~2.10",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"gulp": "4.0.2",
"jest": "29.3.1",

View File

@@ -559,7 +559,12 @@ class Application extends BaseApplication {
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
// await populateDatabase(reg.db());
// await populateDatabase(reg.db(), {
// clearDatabase: true,
// folderCount: 1000,
// rootFolderCount: 1,
// subFolderDepth: 1,
// });
// setTimeout(() => {
// console.info(CommandService.instance().commandsToMarkdownTable(this.store().getState()));

View File

@@ -40,7 +40,7 @@ async function fetchLatestRelease(options: CheckForUpdateOptions) {
if (!response.ok) {
const responseText = await response.text();
throw new Error(`Cannot get latest release info: ${responseText.substr(0,500)}`);
throw new Error(`Cannot get latest release info: ${responseText.substr(0, 500)}`);
}
const releases = await response.json();

View File

@@ -1,7 +1,7 @@
const React = require('react');
const { connect } = require('react-redux');
const { clipboard } = require('electron');
const ExtensionBadge = require('./ExtensionBadge.min');
import ExtensionBadge from './ExtensionBadge';
import bridge from '../services/bridge';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';

View File

@@ -453,6 +453,12 @@ class ConfigScreenComponent extends React.Component<any, any> {
inputStyle.marginBottom = subLabel.marginBottom;
const splitCmd = (cmdString: string) => {
// Normally not necessary but certain plugins found a way to
// set the set the value to "undefined", leading to a crash.
// This is now fixed at the model level but to be sure we
// check here too, to handle any already existing data.
// https://github.com/laurent22/joplin/issues/7621
if (!cmdString) cmdString = '';
const path = pathUtils.extractExecutablePath(cmdString);
const args = cmdString.substr(path.length + 1);
return [pathUtils.unquotePath(path), args];

View File

@@ -20,7 +20,7 @@ const { space } = require('styled-system');
const logger = Logger.create('PluginState');
const maxWidth: number = 320;
const maxWidth = 320;
const Root = styled.div`
display: flex;

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import versionInfo from '@joplin/lib/versionInfo';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';
import restart from '../services/restart';
const packageInfo = require('../packageInfo.js');
@@ -21,6 +21,7 @@ interface State {
error: Error;
errorInfo: ErrorInfo;
pluginInfos: PluginInfo[];
plugins: Plugins;
}
interface Props {
@@ -29,14 +30,16 @@ interface Props {
export default class ErrorBoundary extends React.Component<Props, State> {
public state: State = { error: null, errorInfo: null, pluginInfos: [] };
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
componentDidCatch(error: any, errorInfo: ErrorInfo) {
if (typeof error === 'string') error = { message: error };
const pluginInfos: PluginInfo[] = [];
let plugins: Plugins = {};
try {
const service = PluginService.instance();
plugins = service.plugins;
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
for (const pluginId in pluginSettings) {
const plugin = PluginService.instance().pluginById(pluginId);
@@ -52,7 +55,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
console.error('Could not get plugin info:', error);
}
this.setState({ error, errorInfo, pluginInfos });
this.setState({ error, errorInfo, pluginInfos, plugins });
}
componentDidMount() {
@@ -91,7 +94,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
output.push(
<section key="versionInfo">
<h2>Version info</h2>
<pre>{versionInfo(packageInfo).message}</pre>
<pre>{versionInfo(packageInfo, this.state.plugins).message}</pre>
</section>
);

View File

@@ -1,45 +0,0 @@
const React = require('react');
const bridge = require('@electron/remote').require('./bridge').default;
const styleSelector = require('./style/ExtensionBadge');
const { _ } = require('@joplin/lib/locale');
function platformAssets(type) {
if (type === 'firefox') {
return {
logoImage: `${bridge().buildDir()}/images/firefox-logo.svg`,
locationLabel: _('Firefox Extension'),
};
}
if (type === 'chrome') {
return {
logoImage: `${bridge().buildDir()}/images/chrome-logo.svg`,
locationLabel: _('Chrome Web Store'),
};
}
throw new Error(`Invalid type:${type}`);
}
function ExtensionBadge(props) {
const style = styleSelector(null, props);
const assets = platformAssets(props.type);
const onClick = () => {
bridge().openExternal(props.url);
};
const rootStyle = props.style ? Object.assign({}, style.root, props.style) : style.root;
return (
<a style={rootStyle} onClick={onClick} href="#">
<img style={style.logo} src={assets.logoImage}/>
<div style={style.labelGroup} >
<div>{_('Get it now:')}</div>
<div style={style.locationLabel}>{assets.locationLabel}</div>
</div>
</a>
);
}
module.exports = ExtensionBadge;

View File

@@ -0,0 +1,98 @@
import * as React from 'react';
import bridge from '../services/bridge';
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
const { createSelector } = require('reselect');
interface Props {
themeId: number;
type: string;
url: string;
style?: any;
}
const themeSelector = (_state: any, props: any) => themeStyle(props.themeId);
const styleSelector = createSelector(
themeSelector,
(theme: any) => {
const output = {
root: {
width: 220,
height: 60,
borderRadius: 4,
border: '1px solid',
borderColor: theme.dividerColor,
backgroundColor: theme.backgroundColor,
paddingLeft: 14,
paddingRight: 14,
paddingTop: 8,
paddingBottom: 8,
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'row',
boxShadow: '0px 1px 1px rgba(0,0,0,0.3)',
},
logo: {
width: 42,
height: 42,
},
labelGroup: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
marginLeft: 14,
fontFamily: theme.fontFamily,
color: theme.color,
fontSize: theme.fontSize,
},
locationLabel: {
fontSize: theme.fontSize * 1.2,
fontWeight: 'bold',
},
};
return output;
}
);
function platformAssets(type: string) {
if (type === 'firefox') {
return {
logoImage: `${bridge().buildDir()}/images/firefox-logo.svg`,
locationLabel: _('Firefox Extension'),
};
}
if (type === 'chrome') {
return {
logoImage: `${bridge().buildDir()}/images/chrome-logo.svg`,
locationLabel: _('Chrome Web Store'),
};
}
throw new Error(`Invalid type:${type}`);
}
function ExtensionBadge(props: Props) {
const style = styleSelector(null, props);
const assets = platformAssets(props.type);
const onClick = () => {
void bridge().openExternal(props.url);
};
const rootStyle = props.style ? Object.assign({}, style.root, props.style) : style.root;
return (
<a style={rootStyle} onClick={onClick} href="#">
<img style={style.logo} src={assets.logoImage}/>
<div style={style.labelGroup} >
<div>{_('Get it now:')}</div>
<div style={style.locationLabel}>{assets.locationLabel}</div>
</div>
</a>
);
}
export default ExtensionBadge;

View File

@@ -1,10 +1,18 @@
const React = require('react');
import * as React from 'react';
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
import { themeStyle } from '@joplin/lib/theme';
import { AppState } from '../app.reducer';
class HelpButtonComponent extends React.Component {
constructor() {
super();
interface Props {
tip: string;
onClick: Function;
themeId: number;
style: any;
}
class HelpButtonComponent extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.onClick = this.onClick.bind(this);
}
@@ -17,7 +25,7 @@ class HelpButtonComponent extends React.Component {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
const extraProps = {};
const extraProps: any = {};
if (this.props.tip) extraProps['data-tip'] = this.props.tip;
return (
<a href="#" style={style} onClick={this.onClick} {...extraProps}>
@@ -27,7 +35,7 @@ class HelpButtonComponent extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state: AppState) => {
return {
themeId: state.settings.theme,
};
@@ -35,4 +43,4 @@ const mapStateToProps = state => {
const HelpButton = connect(mapStateToProps)(HelpButtonComponent);
module.exports = HelpButton;
export default HelpButton;

View File

@@ -1,7 +1,14 @@
const React = require('react');
const { themeStyle } = require('@joplin/lib/theme');
import * as React from 'react';
import { themeStyle } from '@joplin/lib/theme';
class IconButton extends React.Component {
interface Props {
themeId: number;
style: any;
iconName: string;
onClick: Function;
}
class IconButton extends React.Component<Props> {
render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);
@@ -42,4 +49,4 @@ class IconButton extends React.Component {
}
}
module.exports = { IconButton };
export default IconButton;

View File

@@ -1,12 +1,29 @@
const React = require('react');
import * as React from 'react';
import Folder from '@joplin/lib/models/Folder';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
import { filename, basename } from '@joplin/lib/path-utils';
import importEnex from '@joplin/lib/import-enex';
import { AppState } from '../app.reducer';
const { connect } = require('react-redux');
const Folder = require('@joplin/lib/models/Folder').default;
const { themeStyle } = require('@joplin/lib/theme');
const { _ } = require('@joplin/lib/locale');
const { filename, basename } = require('@joplin/lib/path-utils');
const importEnex = require('@joplin/lib/import-enex').default;
class ImportScreenComponent extends React.Component {
interface Props {
filePath: string;
themeId: number;
}
interface Message {
key: string;
text: string;
}
interface State {
filePath: string;
doImport: boolean;
messages: Message[];
}
class ImportScreenComponent extends React.Component<Props, State> {
UNSAFE_componentWillMount() {
this.setState({
doImport: true,
@@ -15,7 +32,7 @@ class ImportScreenComponent extends React.Component {
});
}
UNSAFE_componentWillReceiveProps(newProps) {
UNSAFE_componentWillReceiveProps(newProps: Props) {
if (newProps.filePath) {
this.setState(
{
@@ -24,7 +41,7 @@ class ImportScreenComponent extends React.Component {
messages: [],
},
() => {
this.doImport();
void this.doImport();
}
);
}
@@ -32,11 +49,11 @@ class ImportScreenComponent extends React.Component {
componentDidMount() {
if (this.state.filePath && this.state.doImport) {
this.doImport();
void this.doImport();
}
}
addMessage(key, text) {
addMessage(key: string, text: string) {
const messages = this.state.messages.slice();
messages.push({ key: key, text: text });
@@ -66,7 +83,7 @@ class ImportScreenComponent extends React.Component {
let lastProgress = '';
const options = {
onProgress: progressState => {
onProgress: (progressState: any) => {
const line = [];
line.push(_('Found: %d.', progressState.loaded));
line.push(_('Created: %d.', progressState.created));
@@ -77,7 +94,7 @@ class ImportScreenComponent extends React.Component {
lastProgress = line.join(' ');
this.addMessage('progress', lastProgress);
},
onError: error => {
onError: (error: any) => {
// Don't display the error directly because most of the time it doesn't matter
// (eg. for weird broken HTML, but the note is still imported)
console.warn('When importing ENEX file', error);
@@ -116,7 +133,7 @@ class ImportScreenComponent extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state: AppState) => {
return {
themeId: state.settings.theme,
};
@@ -124,4 +141,5 @@ const mapStateToProps = state => {
const ImportScreen = connect(mapStateToProps)(ImportScreenComponent);
module.exports = { ImportScreen };
export default ImportScreen;

View File

@@ -1,8 +1,27 @@
const React = require('react');
import * as React from 'react';
class ItemList extends React.Component {
constructor() {
super();
interface Props {
style: any;
itemHeight: number;
items: any[];
disabled?: boolean;
onKeyDown?: Function;
itemRenderer: Function;
className?: string;
}
interface State {
topItemIndex: number;
bottomItemIndex: number;
}
class ItemList extends React.Component<Props, State> {
private scrollTop_: number;
private listRef: any;
constructor(props: Props) {
super(props);
this.scrollTop_ = 0;
@@ -12,12 +31,12 @@ class ItemList extends React.Component {
this.onKeyDown = this.onKeyDown.bind(this);
}
visibleItemCount(props) {
visibleItemCount(props: Props = undefined) {
if (typeof props === 'undefined') props = this.props;
return Math.ceil(props.style.height / props.itemHeight);
}
updateStateItemIndexes(props) {
updateStateItemIndexes(props: Props = undefined) {
if (typeof props === 'undefined') props = this.props;
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
@@ -44,20 +63,20 @@ class ItemList extends React.Component {
this.updateStateItemIndexes();
}
UNSAFE_componentWillReceiveProps(newProps) {
UNSAFE_componentWillReceiveProps(newProps: Props) {
this.updateStateItemIndexes(newProps);
}
onScroll(event) {
onScroll(event: any) {
this.scrollTop_ = event.target.scrollTop;
this.updateStateItemIndexes();
}
onKeyDown(event) {
onKeyDown(event: any) {
if (this.props.onKeyDown) this.props.onKeyDown(event);
}
makeItemIndexVisible(itemIndex) {
makeItemIndexVisible(itemIndex: number) {
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
const bottom = Math.max(0, this.state.bottomItemIndex);
@@ -105,7 +124,7 @@ class ItemList extends React.Component {
if (!this.props.itemHeight) throw new Error('itemHeight is required');
const blankItem = function(key, height) {
const blankItem = function(key: string, height: number) {
return <div key={key} style={{ height: height }}></div>;
};
@@ -129,4 +148,4 @@ class ItemList extends React.Component {
}
}
module.exports = { ItemList };
export default ItemList;

View File

@@ -43,7 +43,7 @@ import invitationRespond from '../../services/share/invitationRespond';
import restart from '../../services/restart';
const { connect } = require('react-redux');
import PromptDialog from '../PromptDialog';
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
import NotePropertiesDialog from '../NotePropertiesDialog';
const PluginManager = require('@joplin/lib/services/PluginManager');
const ipcRenderer = require('electron').ipcRenderer;

View File

@@ -21,6 +21,7 @@ import checkForUpdates from '../checkForUpdates';
const { connect } = require('react-redux');
import { reg } from '@joplin/lib/registry';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const packageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;
@@ -485,7 +486,8 @@ function useMenu(props: Props) {
}
function _showAbout() {
const v = versionInfo(packageInfo);
const v = versionInfo(packageInfo, PluginService.instance().plugins);
const copyToClipboard = bridge().showMessageBox(v.message, {
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,

View File

@@ -1,11 +1,15 @@
const React = require('react');
const Component = React.Component;
const Setting = require('@joplin/lib/models/Setting').default;
const { connect } = require('react-redux');
import Setting from '@joplin/lib/models/Setting';
import { AppState } from '../app.reducer';
const bridge = require('@electron/remote').require('./bridge').default;
class NavigatorComponent extends Component {
UNSAFE_componentWillReceiveProps(newProps) {
interface Props {
route: any;
}
class NavigatorComponent extends React.Component<Props> {
UNSAFE_componentWillReceiveProps(newProps: Props) {
if (newProps.route) {
const screenInfo = this.props.screens[newProps.route.routeName];
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
@@ -17,7 +21,7 @@ class NavigatorComponent extends Component {
}
}
updateWindowTitle(title) {
updateWindowTitle(title: string) {
try {
if (bridge().window()) bridge().window().setTitle(title);
} catch (error) {
@@ -46,10 +50,10 @@ class NavigatorComponent extends Component {
}
}
const Navigator = connect(state => {
const Navigator = connect((state: AppState) => {
return {
route: state.route,
};
})(NavigatorComponent);
module.exports = { Navigator };
export default Navigator;

View File

@@ -16,6 +16,7 @@ import NoteTextViewer from '../../../NoteTextViewer';
import Editor from './Editor';
import usePluginServiceRegistration from '../../utils/usePluginServiceRegistration';
import Setting from '@joplin/lib/models/Setting';
import Note from '@joplin/lib/models/Note';
import { _ } from '@joplin/lib/locale';
import bridge from '../../../../services/bridge';
import markdownUtils from '@joplin/lib/markdownUtils';
@@ -30,6 +31,7 @@ import dialogs from '../../../dialogs';
import convertToScreenCoordinates from '../../../utils/convertToScreenCoordinates';
import { MarkupToHtml } from '@joplin/renderer';
const { clipboard } = require('electron');
const debounce = require('debounce');
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -286,15 +288,25 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const editorCopyText = useCallback(() => {
if (editorRef.current) {
const selections = editorRef.current.getSelections();
if (selections.length > 0) {
// Handle the case when there is a selection - copy the selection to the clipboard
// When there is no selection, the selection array contains an empty string.
if (selections.length > 0 && selections[0]) {
clipboard.writeText(selections[0]);
} else {
// This is the case when there is no selection - copy the current line to the clipboard
const cursor = editorRef.current.getCursor();
const line = editorRef.current.getLine(cursor.line);
clipboard.writeText(line);
}
}
}, []);
const editorPasteText = useCallback(() => {
const editorPasteText = useCallback(async () => {
if (editorRef.current) {
editorRef.current.replaceSelection(clipboard.readText());
const modifiedMd = await Note.replaceResourceExternalToInternalLinks(clipboard.readText(), { useAbsolutePaths: true });
editorRef.current.replaceSelection(modifiedMd);
}
}, []);
@@ -302,7 +314,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const clipboardText = clipboard.readText();
if (clipboardText) {
editorPasteText();
void editorPasteText();
} else {
// To handle pasting images
void onEditorPaste();
@@ -402,7 +414,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const maxWidthCss = props.contentMaxWidth ? `
margin-right: auto !important;
margin-left: auto !important;
max-width: ${props.contentMaxWidth}px !important;
max-width: ${props.contentMaxWidth}px !important;
` : '';
const element = document.createElement('style');
@@ -619,7 +631,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
contentMaxWidth: props.contentMaxWidth,
mapsToLine: true,
// Always using useCustomPdfViewer for now, we can add a new setting for it in future if we need to.
useCustomPdfViewer: true,
useCustomPdfViewer: props.useCustomPdfViewer,
noteId: props.noteId,
vendorDir: bridge().vendorDir(),
}));
@@ -671,7 +683,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}, [renderedBody, webviewReady]);
useEffect(() => {
if (!props.searchMarkers) return;
if (!props.searchMarkers) return () => {};
// If there is a currently active search, it's important to re-search the text as the user
// types. However this is slow for performance so we ONLY want it to happen when there is
@@ -686,11 +698,19 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
webviewRef.current.send('setMarkers', props.searchMarkers.keywords, props.searchMarkers.options);
if (editorRef.current) {
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
// Fixes https://github.com/laurent22/joplin/issues/7565
const debouncedMarkers = debounce(() => {
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
props.setLocalSearchResultCount(matches);
props.setLocalSearchResultCount(matches);
}, 50);
debouncedMarkers();
return () => {
debouncedMarkers.clear();
};
}
}
return () => {};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [props.searchMarkers, previousSearchMarkers, props.setLocalSearchResultCount, props.content, previousContent, renderedBody, previousRenderedBody, renderedBody]);
@@ -849,7 +869,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
function renderEditor() {
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '<>()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '``()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
return (
<div style={cellEditorStyle}>
@@ -880,6 +900,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
<div style={cellViewerStyle}>
<NoteTextViewer
ref={webviewRef}
themeId={props.themeId}
viewerStyle={styles.viewer}
onIpcMessage={webview_ipcMessage}
onDomReady={webview_domReady}
@@ -906,4 +927,3 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
export default forwardRef(CodeMirror);

View File

@@ -35,14 +35,14 @@ describe('useCursorUtils', () => {
const numberedListWithEmptyLines = [
'1. item1',
'2. item2',
'3. ' ,
'3. ',
'4. item3',
];
const noPrefixListWithEmptyLines = [
'item1',
'item2',
'' ,
'',
'item3',
];

View File

@@ -111,7 +111,7 @@ export default function useCursorUtils(CodeMirror: any) {
const lines = selected.split(/\r?\n/);
// Save the newline character to restore it later
const newLines = selected.match(/\r?\n/);
modifyListLines(lines,num,string1);
modifyListLines(lines, num, string1);
const newLine = newLines !== null ? newLines[0] : '\n';
selectedStrings[i] = lines.join(newLine);
}

View File

@@ -102,7 +102,7 @@ interface LastOnChangeEventInfo {
let loadedCssFiles_: string[] = [];
let loadedJsFiles_: string[] = [];
let dispatchDidUpdateIID_: any = null;
let changeId_: number = 1;
let changeId_ = 1;
const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const [editor, setEditor] = useState(null);
@@ -187,7 +187,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
return prop_htmlToMarkdownRef.current(props.contentMarkupLanguage, editorRef.current.getContent(), props.contentOriginalCss);
},
resetScroll: () => {
if (editor) editor.getWin().scrollTo(0,0);
if (editor) editor.getWin().scrollTo(0, 0);
},
scrollTo: (options: ScrollOptions) => {
if (!editor) return;

View File

@@ -34,12 +34,12 @@ import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
const { themeStyle } = require('@joplin/lib/theme');
const { substrWithEllipsis } = require('@joplin/lib/string-utils');
const NoteSearchBar = require('../NoteSearchBar.min.js');
import NoteSearchBar from '../NoteSearchBar';
import { reg } from '@joplin/lib/registry';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
const bridge = require('@electron/remote').require('./bridge').default;
const NoteRevisionViewer = require('../NoteRevisionViewer.min');
import NoteRevisionViewer from '../NoteRevisionViewer';
const commands = [
require('./commands/showRevisions'),
@@ -424,6 +424,7 @@ function NoteEditor(props: NoteEditorProps) {
fontSize: Setting.value('style.editor.fontSize'),
contentMaxWidth: props.contentMaxWidth,
isSafeMode: props.isSafeMode,
useCustomPdfViewer: props.useCustomPdfViewer,
// We need it to identify the context for which media is rendered.
// It is currently used to remember pdf scroll position for each attacments of each note uniquely.
noteId: props.noteId,
@@ -630,6 +631,7 @@ const mapStateToProps = (state: AppState) => {
], whenClauseContext)[0],
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
isSafeMode: state.settings.isSafeMode,
useCustomPdfViewer: state.settings.useCustomPdfViewer,
};
};

View File

@@ -7,7 +7,7 @@ export default function styles(props: NoteEditorProps) {
return {
root: {
boxSizing: 'border-box',
paddingLeft: 0,// theme.mainPadding,
paddingLeft: 0, // theme.mainPadding,
paddingTop: 0,
width: '100%',
height: '100%',

View File

@@ -2,7 +2,7 @@ import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng, svgDimensions } from './contextMenuUtils';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import Resource from '@joplin/lib/models/Resource';
@@ -106,8 +106,10 @@ export function menuItems(dispatch: Function): ContextMenuItems {
if (!options.filename) {
throw new Error('Filename is needed to save as png');
}
// double dimensions to make sure it's always big enough even on hdpi screens
const [width, height] = svgDimensions(document, options.textToCopy).map((x: number) => x * 2 || undefined);
const dataUri = textToDataUri(options.textToCopy, options.mime);
const png = await svgUriToPng(document, dataUri);
const png = await svgUriToPng(document, dataUri, width, height);
const filename = options.filename.replace('.svg', '.png');
await saveFileData(png, filename);
},

View File

@@ -1,5 +1,6 @@
import Resource from '@joplin/lib/models/Resource';
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('contextMenuUtils');
export enum ContextMenuItemType {
None = '',
Image = 'image',
@@ -40,8 +41,23 @@ export async function resourceInfo(options: ContextMenuOptions) {
export function textToDataUri(text: string, mime: string): string {
return `data:${mime};base64,${Buffer.from(text).toString('base64')}`;
}
export const svgUriToPng = (document: Document, svg: string) => {
export const svgDimensions = (document: Document, svg: string) => {
let width: number;
let height: number;
try {
const parser = new DOMParser();
const id = parser.parseFromString(svg, 'text/html').querySelector('svg').id;
({ width, height } = document.querySelector<HTMLIFrameElement>('.noteTextViewer').contentWindow.document.querySelector(`#${id}`).getBoundingClientRect());
} catch (error) {
logger.warn('Could not get SVG dimensions.');
logger.warn('Error was: ', error);
}
if (!width || !height) {
return [undefined, undefined];
}
return [width, height];
};
export const svgUriToPng = (document: Document, svg: string, width: number, height: number) => {
return new Promise<Uint8Array>((resolve, reject) => {
let canvas: HTMLCanvasElement;
let img: HTMLImageElement;
@@ -63,11 +79,21 @@ export const svgUriToPng = (document: Document, svg: string) => {
try {
canvas = document.createElement('canvas');
if (!canvas) throw new Error('Failed to create canvas element');
canvas.width = img.width;
canvas.height = img.height;
if (!width || !height) {
const maxDimension = 1024;
if (img.width > img.height) {
width = maxDimension;
height = width * (img.height / img.width);
} else {
height = maxDimension;
width = height * (img.width / img.height);
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get context');
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
const pngUri = canvas.toDataURL('image/png');
if (!pngUri) throw new Error('Failed to generate png uri');
const pngBase64 = pngUri.split(',')[1];

View File

@@ -43,6 +43,7 @@ export interface NoteEditorProps {
richTextBannerDismissed: boolean;
contentMaxWidth: number;
isSafeMode: boolean;
useCustomPdfViewer: boolean;
}
export interface NoteBodyEditorProps {
@@ -76,6 +77,7 @@ export interface NoteBodyEditorProps {
contentMaxWidth: number;
isSafeMode: boolean;
noteId: string;
useCustomPdfViewer: boolean;
}
export interface FormNote {

View File

@@ -13,7 +13,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import shim from '@joplin/lib/shim';
import styled from 'styled-components';
import { themeStyle } from '@joplin/lib/theme';
const { ItemList } = require('../ItemList.min.js');
import ItemList from '../ItemList';
const { connect } = require('react-redux');
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
@@ -124,7 +124,7 @@ const NoteListComponent = (props: Props) => {
});
menu.popup(bridge().window());
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles,props.plugins, props.selectedFolderId, props.customCss]);
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles, props.plugins, props.selectedFolderId, props.customCss]);
const onGlobalDrop_ = () => {
unregisterGlobalDragEndEvent_();
@@ -307,7 +307,7 @@ const NoteListComponent = (props: Props) => {
updateSizeState();
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [previousSelectedNoteIds,previousNotes, previousVisible, props.selectedNoteIds, props.notes]);
}, [previousSelectedNoteIds, previousNotes, previousVisible, props.selectedNoteIds, props.notes]);
const scrollNoteIndex_ = (keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) => {

View File

@@ -1,18 +1,38 @@
const React = require('react');
const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme');
const time = require('@joplin/lib/time').default;
const DialogButtonRow = require('./DialogButtonRow').default;
const Datetime = require('react-datetime');
const Note = require('@joplin/lib/models/Note').default;
const formatcoords = require('formatcoords');
const bridge = require('@electron/remote').require('./bridge').default;
const shim = require('@joplin/lib/shim').default;
import * as React from 'react';
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
import time from '@joplin/lib/time';
import DialogButtonRow from './DialogButtonRow';
import Note from '@joplin/lib/models/Note';
import bridge from '../services/bridge';
import shim from '@joplin/lib/shim';
import { NoteEntity } from '@joplin/lib/services/database/types';
const Datetime = require('react-datetime').default;
const { clipboard } = require('electron');
const formatcoords = require('formatcoords');
class NotePropertiesDialog extends React.Component {
constructor() {
super();
interface Props {
noteId: string;
onClose: Function;
onRevisionLinkClick: Function;
themeId: number;
}
interface State {
editedKey: string;
formNote: any;
editedValue: any;
}
class NotePropertiesDialog extends React.Component<Props, State> {
private okButton: any;
private keyToLabel_: Record<string, string>;
private styleKey_: number;
private styles_: any;
constructor(props: Props) {
super(props);
this.revisionsLink_click = this.revisionsLink_click.bind(this);
this.buttonRow_click = this.buttonRow_click.bind(this);
@@ -37,7 +57,7 @@ class NotePropertiesDialog extends React.Component {
}
componentDidMount() {
this.loadNote(this.props.noteId);
void this.loadNote(this.props.noteId);
}
componentDidUpdate() {
@@ -46,7 +66,7 @@ class NotePropertiesDialog extends React.Component {
}
}
async loadNote(noteId) {
async loadNote(noteId: string) {
if (!noteId) {
this.setState({ formNote: null });
} else {
@@ -56,8 +76,8 @@ class NotePropertiesDialog extends React.Component {
}
}
latLongFromLocation(location) {
const o = {};
latLongFromLocation(location: string) {
const o: any = {};
const l = location.split(',');
if (l.length === 2) {
o.latitude = l[0].trim();
@@ -69,8 +89,8 @@ class NotePropertiesDialog extends React.Component {
return o;
}
noteToFormNote(note) {
const formNote = {};
noteToFormNote(note: NoteEntity) {
const formNote: any = {};
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
formNote.user_created_time = time.formatMsToLocal(note.user_created_time);
@@ -93,7 +113,7 @@ class NotePropertiesDialog extends React.Component {
return formNote;
}
formNoteToNote(formNote) {
formNoteToNote(formNote: any) {
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
@@ -107,7 +127,7 @@ class NotePropertiesDialog extends React.Component {
return note;
}
styles(themeId) {
styles(themeId: number) {
const styleKey = themeId;
if (styleKey === this.styleKey_) return this.styles_;
@@ -148,7 +168,7 @@ class NotePropertiesDialog extends React.Component {
return this.styles_;
}
async closeDialog(applyChanges) {
async closeDialog(applyChanges: boolean) {
if (applyChanges) {
await this.saveProperty();
const note = this.formNoteToNote(this.state.formNote);
@@ -163,26 +183,26 @@ class NotePropertiesDialog extends React.Component {
}
}
buttonRow_click(event) {
this.closeDialog(event.buttonName === 'ok');
buttonRow_click(event: any) {
void this.closeDialog(event.buttonName === 'ok');
}
revisionsLink_click() {
this.closeDialog(false);
void this.closeDialog(false);
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
}
editPropertyButtonClick(key, initialValue) {
editPropertyButtonClick(key: string, initialValue: any) {
this.setState({
editedKey: key,
editedValue: initialValue,
});
shim.setTimeout(() => {
if (this.refs.editField.openCalendar) {
this.refs.editField.openCalendar();
if ((this.refs.editField as any).openCalendar) {
(this.refs.editField as any).openCalendar();
} else {
this.refs.editField.focus();
(this.refs.editField as any).focus();
}
}, 100);
}
@@ -190,7 +210,7 @@ class NotePropertiesDialog extends React.Component {
async saveProperty() {
if (!this.state.editedKey) return;
return new Promise((resolve) => {
return new Promise((resolve: Function) => {
const newFormNote = Object.assign({}, this.state.formNote);
if (this.state.editedKey.indexOf('_time') >= 0) {
@@ -214,7 +234,7 @@ class NotePropertiesDialog extends React.Component {
}
async cancelProperty() {
return new Promise((resolve) => {
return new Promise((resolve: Function) => {
this.okButton.current.focus();
this.setState({
editedKey: null,
@@ -225,7 +245,7 @@ class NotePropertiesDialog extends React.Component {
});
}
createNoteField(key, value) {
createNoteField(key: string, value: any) {
const styles = this.styles(this.props.themeId);
const theme = themeStyle(this.props.themeId);
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
@@ -234,11 +254,11 @@ class NotePropertiesDialog extends React.Component {
let editCompHandler = null;
let editCompIcon = null;
const onKeyDown = event => {
const onKeyDown = (event: any) => {
if (event.keyCode === 13) {
this.saveProperty();
void this.saveProperty();
} else if (event.keyCode === 27) {
this.cancelProperty();
void this.cancelProperty();
}
};
@@ -251,17 +271,17 @@ class NotePropertiesDialog extends React.Component {
dateFormat={time.dateFormat()}
timeFormat={time.timeFormat()}
inputProps={{
onKeyDown: event => onKeyDown(event, key),
onKeyDown: (event: any) => onKeyDown(event),
style: styles.input,
}}
onChange={momentObject => {
onChange={(momentObject: any) => {
this.setState({ editedValue: momentObject });
}}
/>
);
editCompHandler = () => {
this.saveProperty();
void this.saveProperty();
};
editCompIcon = 'fa-save';
} else {
@@ -344,12 +364,12 @@ class NotePropertiesDialog extends React.Component {
);
}
formatLabel(key) {
formatLabel(key: string) {
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
return key;
}
formatValue(key, note) {
formatValue(key: string, note: NoteEntity) {
if (key === 'location') {
if (!Number(note.latitude) && !Number(note.longitude)) return null;
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
@@ -357,10 +377,10 @@ class NotePropertiesDialog extends React.Component {
}
if (['user_updated_time', 'user_created_time', 'todo_completed'].indexOf(key) >= 0) {
return time.formatMsToLocal(note[key]);
return time.formatMsToLocal((note as any)[key]);
}
return note[key];
return (note as any)[key];
}
render() {
@@ -389,4 +409,4 @@ class NotePropertiesDialog extends React.Component {
}
}
module.exports = NotePropertiesDialog;
export default NotePropertiesDialog;

View File

@@ -1,25 +1,45 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const { _ } = require('@joplin/lib/locale');
const NoteTextViewer = require('./NoteTextViewer').default;
const HelpButton = require('./HelpButton.min');
const BaseModel = require('@joplin/lib/BaseModel').default;
const Revision = require('@joplin/lib/models/Revision').default;
import * as React from 'react';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
import NoteTextViewer from './NoteTextViewer';
import HelpButton from './HelpButton';
import BaseModel from '@joplin/lib/BaseModel';
import Revision from '@joplin/lib/models/Revision';
import Setting from '@joplin/lib/models/Setting';
import RevisionService from '@joplin/lib/services/RevisionService';
import { MarkupToHtml } from '@joplin/renderer';
import time from '@joplin/lib/time';
import bridge from '../services/bridge';
import markupLanguageUtils from '../utils/markupLanguageUtils';
import { NoteEntity, RevisionEntity } from '@joplin/lib/services/database/types';
import { AppState } from '../app.reducer';
const urlUtils = require('@joplin/lib/urlUtils');
const Setting = require('@joplin/lib/models/Setting').default;
const RevisionService = require('@joplin/lib/services/RevisionService').default;
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
const { MarkupToHtml } = require('@joplin/renderer');
const time = require('@joplin/lib/time').default;
const ReactTooltip = require('react-tooltip');
const { urlDecode } = require('@joplin/lib/string-utils');
const bridge = require('@electron/remote').require('./bridge').default;
const markupLanguageUtils = require('../utils/markupLanguageUtils').default;
const { connect } = require('react-redux');
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
class NoteRevisionViewerComponent extends React.PureComponent {
constructor() {
super();
interface Props {
themeId: number;
noteId: string;
onBack: Function;
customCss: string;
}
interface State {
note: NoteEntity;
revisions: RevisionEntity[];
currentRevId: string;
restoring: boolean;
}
class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
private viewerRef_: any;
private helpButton_onClick: Function;
constructor(props: Props) {
super(props);
this.state = {
revisions: [],
@@ -65,7 +85,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
},
() => {
this.reloadNote();
void this.reloadNote();
}
);
}
@@ -82,7 +102,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
if (this.props.onBack) this.props.onBack();
}
revisionList_onChange(event) {
revisionList_onChange(event: any) {
const value = event.target.value;
if (!value) {
@@ -93,7 +113,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
currentRevId: value,
},
() => {
this.reloadNote();
void this.reloadNote();
}
);
}
@@ -128,12 +148,12 @@ class NoteRevisionViewerComponent extends React.PureComponent {
});
this.viewerRef_.current.send('setHtml', result.html, {
cssFiles: result.cssFiles,
// cssFiles: result.cssFiles,
pluginAssets: result.pluginAssets,
});
}
async webview_ipcMessage(event) {
async webview_ipcMessage(event: any) {
// 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.
// We try to get most links work though, except for internal (joplin://) links.
@@ -148,9 +168,9 @@ class NoteRevisionViewerComponent extends React.PureComponent {
throw new Error(_('Unsupported link or message: %s', msg));
} else if (urlUtils.urlProtocol(msg)) {
if (msg.indexOf('file://') === 0) {
require('electron').shell.openExternal(urlDecode(msg));
void require('electron').shell.openExternal(urlDecode(msg));
} else {
require('electron').shell.openExternal(msg);
void require('electron').shell.openExternal(msg);
}
} else if (msg.indexOf('#') === 0) {
// This is an internal anchor, which is handled by the WebView so skip this case
@@ -202,7 +222,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
const viewer = <NoteTextViewer themeId={this.props.themeId} viewerStyle={{ display: 'flex', flex: 1, borderLeft: 'none' }} ref={this.viewerRef_} onDomReady={this.viewer_domReady} onIpcMessage={this.webview_ipcMessage} />;
return (
<div style={style.root}>
<div style={style.root as any}>
{titleInput}
{viewer}
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
@@ -211,7 +231,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state: AppState) => {
return {
themeId: state.settings.theme,
};
@@ -219,4 +239,4 @@ const mapStateToProps = state => {
const NoteRevisionViewer = connect(mapStateToProps)(NoteRevisionViewerComponent);
module.exports = NoteRevisionViewer;
export default NoteRevisionViewer;

View File

@@ -1,10 +1,27 @@
const React = require('react');
const { themeStyle } = require('@joplin/lib/theme');
const { _ } = require('@joplin/lib/locale');
import * as React from 'react';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
class NoteSearchBar extends React.Component {
constructor() {
super();
interface Props {
themeId: number;
onNext: Function;
onPrevious: Function;
onClose: Function;
onChange: Function;
query: string;
searching: boolean;
resultCount: number;
selectedIndex: number;
visiblePanes: string[];
style: any;
}
class NoteSearchBar extends React.Component<Props> {
private backgroundColor: any;
constructor(props: Props) {
super(props);
this.searchInput_change = this.searchInput_change.bind(this);
this.searchInput_keyDown = this.searchInput_keyDown.bind(this);
@@ -29,7 +46,7 @@ class NoteSearchBar extends React.Component {
return style;
}
buttonIconComponent(iconName, clickHandler, isEnabled) {
buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
const theme = themeStyle(this.props.themeId);
const searchButton = {
@@ -57,12 +74,12 @@ class NoteSearchBar extends React.Component {
);
}
searchInput_change(event) {
searchInput_change(event: any) {
const query = event.currentTarget.value;
this.triggerOnChange(query);
}
searchInput_keyDown(event) {
searchInput_keyDown(event: any) {
if (event.keyCode === 13) {
// ENTER
event.preventDefault();
@@ -101,13 +118,13 @@ class NoteSearchBar extends React.Component {
if (this.props.onClose) this.props.onClose();
}
triggerOnChange(query) {
triggerOnChange(query: string) {
if (this.props.onChange) this.props.onChange(query);
}
focus() {
this.refs.searchInput.focus();
this.refs.searchInput.select();
(this.refs.searchInput as any).focus();
(this.refs.searchInput as any).select();
}
render() {
@@ -177,4 +194,4 @@ class NoteSearchBar extends React.Component {
}
}
module.exports = NoteSearchBar;
export default NoteSearchBar;

View File

@@ -1,9 +1,16 @@
const React = require('react');
import * as React from 'react';
import time from '@joplin/lib/time';
import { themeStyle } from '@joplin/lib/theme';
import { NoteEntity } from '@joplin/lib/services/database/types';
import { AppState } from '../app.reducer';
const { connect } = require('react-redux');
const time = require('@joplin/lib/time').default;
const { themeStyle } = require('@joplin/lib/theme');
class NoteStatusBarComponent extends React.Component {
interface Props {
themeId: number;
note: NoteEntity;
}
class NoteStatusBarComponent extends React.Component<Props> {
style() {
const theme = themeStyle(this.props.themeId);
@@ -23,7 +30,7 @@ class NoteStatusBarComponent extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state: AppState) => {
return {
// notes: state.notes,
// folders: state.folders,
@@ -34,4 +41,4 @@ const mapStateToProps = state => {
const NoteStatusBar = connect(mapStateToProps)(NoteStatusBarComponent);
module.exports = { NoteStatusBar };
export default NoteStatusBar;

View File

@@ -6,7 +6,8 @@ interface Props {
onDomReady: Function;
onIpcMessage: Function;
viewerStyle: any;
contentMaxWidth: number;
contentMaxWidth?: number;
themeId: number;
}
export default class NoteTextViewerComponent extends React.Component<Props, any> {

View File

@@ -24,9 +24,9 @@ import MasterPasswordDialog from './MasterPasswordDialog/Dialog';
import EditFolderDialog from './EditFolderDialog/Dialog';
import PdfViewer from './PdfViewer';
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
const { ImportScreen } = require('./ImportScreen.min.js');
import ImportScreen from './ImportScreen';
const { ResourceScreen } = require('./ResourceScreen.js');
const { Navigator } = require('./Navigator.min.js');
import Navigator from './Navigator';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components');
const bridge = require('@electron/remote').require('./bridge').default;

View File

@@ -135,8 +135,7 @@ const SidebarComponent = (props: Props) => {
tagItemsOrder_.current = [];
const rootRef = useRef(null);
const anchorItemRefs = useRef<Record<string, any>>(null);
anchorItemRefs.current = {};
const anchorItemRefs = useRef<Record<string, any>>({});
// This whole component is a bit of a mess and rather than passing
// a plugins prop around, not knowing how it's going to affect
@@ -695,6 +694,11 @@ const SidebarComponent = (props: Props) => {
})
);
const foldersStyle = useMemo(() => {
return { display: props.folderHeaderIsExpanded ? 'block' : 'none', paddingBottom: 10 };
}, [props.folderHeaderIsExpanded]);
if (props.folders.length) {
const allNotesSelected = props.notesParentType === 'SmartFilter' && props.selectedSmartFilterId === ALL_NOTES_FILTER_ID;
const result = shared.renderFolders(props, renderFolderItem);
@@ -704,7 +708,7 @@ const SidebarComponent = (props: Props) => {
<div
className={`folders ${props.folderHeaderIsExpanded ? 'expanded' : ''}`}
key="folder_items"
style={{ display: props.folderHeaderIsExpanded ? 'block' : 'none', paddingBottom: 10 }}
style={foldersStyle}
>
{folderItems}
</div>

View File

@@ -1,7 +1,8 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const CommandService = require('@joplin/lib/services/CommandService').default;
import { themeStyle } from '@joplin/lib/theme';
import CommandService from '@joplin/lib/services/CommandService';
import { AppState } from '../app.reducer';
class TagItemComponent extends React.Component {
render() {
@@ -13,10 +14,8 @@ class TagItemComponent extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state: AppState) => {
return { themeId: state.settings.theme };
};
const TagItem = connect(mapStateToProps)(TagItemComponent);
module.exports = TagItem;
export default connect(mapStateToProps)(TagItemComponent);

View File

@@ -1,10 +1,10 @@
import * as React from 'react';
import { useMemo } from 'react';
import { AppState } from '../app.reducer';
import TagItem from './TagItem';
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const TagItem = require('./TagItem.min.js');
interface Props {
themeId: number;

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import ToolbarButton from './ToolbarButton/ToolbarButton';
import ToggleEditorsButton, { Value } from './ToggleEditorsButton/ToggleEditorsButton';
const React = require('react');
import ToolbarSpace from './ToolbarSpace';
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const ToolbarSpace = require('./ToolbarSpace.min.js');
interface Props {
themeId: number;

View File

@@ -1,14 +0,0 @@
const React = require('react');
const { themeStyle } = require('@joplin/lib/theme');
class ToolbarSpace extends React.Component {
render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.toolbarStyle);
style.minWidth = style.height / 2;
return <span style={style}></span>;
}
}
module.exports = ToolbarSpace;

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import { themeStyle } from '@joplin/lib/theme';
interface Props {
themeId: number;
}
class ToolbarSpace extends React.Component<Props> {
render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.toolbarStyle);
style.minWidth = style.height / 2;
return <span style={style}></span>;
}
}
export default ToolbarSpace;

View File

@@ -1,100 +0,0 @@
const React = require('react');
const bridge = require('@electron/remote').require('./bridge').default;
class VerticalResizer extends React.PureComponent {
constructor() {
super();
this.state = {
parentRight: 0,
parentHeight: 0,
parentWidth: 0,
drag: {
startX: 0,
lastX: 0,
},
};
this.onDragStart = this.onDragStart.bind(this);
this.onDrag = this.onDrag.bind(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.document_onDragOver = this.document_onDragOver.bind(this);
}
document_onDragOver(event) {
// This is just to prevent the cursor from changing to a "+" as it's dragged
// over other elements. With this it stays a normal cursor.
event.dataTransfer.dropEffect = 'none';
}
onDragStart(event) {
document.addEventListener('dragover', this.document_onDragOver);
event.dataTransfer.dropEffect = 'none';
const cursor = bridge().screen().getCursorScreenPoint();
this.setState({
drag: {
startX: cursor.x,
lastX: cursor.x,
},
});
if (this.props.onDragStart) this.props.onDragStart({});
}
onDrag() {
// If we got a drag event with no buttons pressed, it's the last drag event
// that we should ignore, because it's sometimes use to put the dragged element
// back to its original position (if there was no valid drop target), which we don't want.
// Also if clientX, screenX, etc. are 0, it's also the last event and we want to ignore these buggy values.
// const e = event.nativeEvent;
// if (!e.buttons || (!e.clientX && !e.clientY && !e.screenX && !e.screenY)) return;
const cursor = bridge().screen().getCursorScreenPoint();
const newX = cursor.x;
const delta = newX - this.state.drag.lastX;
if (!delta) return;
this.setState(
{
drag: Object.assign({}, this.state.drag, { lastX: newX }),
},
() => {
this.props.onDrag({ deltaX: delta });
}
);
}
onDragEnd() {
document.removeEventListener('dragover', this.document_onDragOver);
}
componentWillUnmount() {
document.removeEventListener('dragover', this.document_onDragOver);
}
render() {
const debug = false;
const rootStyle = Object.assign(
{},
{
height: '100%',
width: 5,
borderColor: 'red',
borderWidth: debug ? 1 : 0,
borderStyle: 'solid',
cursor: 'col-resize',
boxSizing: 'border-box',
opacity: 0,
},
this.props.style
);
return <div style={rootStyle} draggable={true} onDragStart={this.onDragStart} onDrag={this.onDrag} onDragEnd={this.onDragEnd} />;
}
}
module.exports = VerticalResizer;

View File

@@ -1,4 +1,10 @@
const smalltalk = require('smalltalk/bundle');
import Logger from '@joplin/lib/Logger';
// Can't upgrade beyond 2.x because it doesn't work with Electron. If trying to
// upgrade again, check that adding a link from the CodeMirror editor works/
const smalltalk = require('smalltalk');
const logger = Logger.create('dialogs');
class Dialogs {
async alert(message: string, title = '') {
@@ -10,6 +16,7 @@ class Dialogs {
await smalltalk.confirm(title, message, options);
return true;
} catch (error) {
logger.error(error);
return false;
}
}
@@ -21,6 +28,7 @@ class Dialogs {
const answer = await smalltalk.prompt(title, message, defaultValue, options);
return answer;
} catch (error) {
logger.error(error);
return null;
}
}

View File

@@ -137,8 +137,14 @@
// calculates viewer's GUI-dependent pixel-based raw percent
const viewerPercent = scrollmap.translateL2V(percent);
const newScrollTop = viewerPercent * maxScrollTop();
// Even if the scroll position hasn't changed (percent is the same),
// we still ignore the next scroll event, so that it doesn't create
// undesired side effects.
// https://github.com/laurent22/joplin/issues/7617
ignoreNextScrollEvent();
if (Math.floor(contentElement.scrollTop) !== Math.floor(newScrollTop)) {
ignoreNextScrollEvent();
percentScroll_ = percent;
contentElement.scrollTop = newScrollTop;
}

View File

@@ -1,49 +1,44 @@
'use strict';
const { createSelector } = require('reselect');
const { themeStyle } = require('@joplin/lib/theme');
const themeSelector = (state, props) => themeStyle(props.themeId);
const style = createSelector(
themeSelector,
(theme) => {
const output = {
root: {
width: 220,
height: 60,
borderRadius: 4,
border: '1px solid',
borderColor: theme.dividerColor,
backgroundColor: theme.backgroundColor,
paddingLeft: 14,
paddingRight: 14,
paddingTop: 8,
paddingBottom: 8,
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'row',
boxShadow: '0px 1px 1px rgba(0,0,0,0.3)',
},
logo: {
width: 42,
height: 42,
},
labelGroup: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
marginLeft: 14,
fontFamily: theme.fontFamily,
color: theme.color,
fontSize: theme.fontSize,
},
locationLabel: {
fontSize: theme.fontSize * 1.2,
fontWeight: 'bold',
},
};
return output;
}
);
const style = createSelector(themeSelector, (theme) => {
const output = {
root: {
width: 220,
height: 60,
borderRadius: 4,
border: '1px solid',
borderColor: theme.dividerColor,
backgroundColor: theme.backgroundColor,
paddingLeft: 14,
paddingRight: 14,
paddingTop: 8,
paddingBottom: 8,
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'row',
boxShadow: '0px 1px 1px rgba(0,0,0,0.3)',
},
logo: {
width: 42,
height: 42,
},
labelGroup: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
marginLeft: 14,
fontFamily: theme.fontFamily,
color: theme.color,
fontSize: theme.fontSize,
},
locationLabel: {
fontSize: theme.fontSize * 1.2,
fontWeight: 'bold',
},
};
return output;
});
module.exports = style;
// # sourceMappingURL=ExtensionBadge.js.map

View File

@@ -21,6 +21,9 @@ const tasks = {
electronRebuild: {
fn: require('./tools/electronRebuild.js'),
},
electronBuilder: {
fn: require('./tools/electronBuilder.js'),
},
tsc: require('@joplin/tools/gulp/tasks/tsc'),
updateIgnoredTypeScriptBuild: require('@joplin/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
buildCommandIndex: require('@joplin/tools/gulp/tasks/buildCommandIndex'),

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.10.4",
"version": "2.10.5",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -8,6 +8,7 @@
"dist": "yarn run electronRebuild && npx electron-builder",
"build": "gulp build",
"postinstall": "yarn run build",
"electronBuilder": "gulp electronBuilder",
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
@@ -108,18 +109,16 @@
"devDependencies": {
"@joplin/tools": "~2.10",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"@types/react": "16.14.34",
"@types/react": "16.14.35",
"@types/react-redux": "7.1.25",
"@types/styled-components": "5.1.26",
"babel-cli": "6.26.0",
"babel-preset-react": "6.24.1",
"electron": "19.1.4",
"electron-builder": "23.6.0",
"electron-notarize": "1.2.2",
"electron-rebuild": "3.2.9",
"glob": "8.0.3",
"glob": "8.1.0",
"gulp": "4.0.2",
"jest": "29.3.1",
"jest-environment-jsdom": "29.3.1",
@@ -167,10 +166,10 @@
"react-select": "5.7.0",
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",
"redux": "4.2.0",
"redux": "4.2.1",
"reselect": "4.1.7",
"roboto-fontface": "0.10.0",
"smalltalk": "4.1.1",
"smalltalk": "2.5.1",
"sqlite3": "5.1.4",
"styled-components": "5.3.6",
"styled-system": "5.1.5",

View File

@@ -3,18 +3,17 @@ import { AppState } from '../app.reducer';
import CommandService, { SearchResult as CommandSearchResult } from '@joplin/lib/services/CommandService';
import KeymapService from '@joplin/lib/services/KeymapService';
import shim from '@joplin/lib/shim';
const { connect } = require('react-redux');
const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme');
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import gotoAnythingStyleQuery from '@joplin/lib/services/searchengine/gotoAnythingStyleQuery';
import BaseModel from '@joplin/lib/BaseModel';
import Tag from '@joplin/lib/models/Tag';
import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note';
const { ItemList } = require('../gui/ItemList.min');
const HelpButton = require('../gui/HelpButton.min');
import ItemList from '../gui/ItemList';
import HelpButton from '../gui/HelpButton';
const { surroundKeywords, nextWhitespaceIndex, removeDiacritics } = require('@joplin/lib/string-utils.js');
import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils';
import markupLanguageUtils from '../utils/markupLanguageUtils';

View File

@@ -22,6 +22,8 @@
# ./runForTesting.sh 1 createUsers,createData,reset,sync && ./runForTesting.sh 2 reset,sync && ./runForTesting.sh 1
# ./runForTesting.sh 1 createUsers,createData,reset,sync && ./runForTesting.sh 2 reset,sync && ./runForTesting.sh 3 reset,sync && ./runForTesting.sh 1
# ----------------------------------------------------------------------------------
# To create two client profiles, in sync, both used by the same user:
# ----------------------------------------------------------------------------------

View File

@@ -38,7 +38,7 @@ export default function(frameWindow: any, isReady: boolean, pluginId: string, vi
frameWindow.addEventListener('message', onMessage_);
return () => {
frameWindow.removeEventListener('message', onMessage_);
if (frameWindow?.removeEventListener) frameWindow.removeEventListener('message', onMessage_);
};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [frameWindow, isReady, pluginId, viewId]);

View File

@@ -1,61 +1,8 @@
const fs = require('fs-extra');
const spawnSync = require('child_process').spawnSync;
const { chdir } = require('process');
const basePath = `${__dirname}/../../..`;
function fileIsNewerThan(path1, path2) {
if (!fs.existsSync(path2)) return true;
const stat1 = fs.statSync(path1);
const stat2 = fs.statSync(path2);
return stat1.mtime > stat2.mtime;
}
function convertJsx(paths) {
chdir(`${__dirname}/..`);
paths.forEach(path => {
fs.readdirSync(path).forEach((filename) => {
const jsxPath = `${path}/${filename}`;
const p = jsxPath.split('.');
if (p.length <= 1) return;
const ext = p[p.length - 1];
if (ext !== 'jsx') return;
p.pop();
const basePath = p.join('.');
const jsPath = `${basePath}.min.js`;
if (fileIsNewerThan(jsxPath, jsPath)) {
console.info(`Compiling ${jsxPath}...`);
// { shell: true } is needed to get it working on Windows:
// https://discourse.joplinapp.org/t/attempting-to-build-on-windows/22559/12
const result = spawnSync('yarn', ['run', 'babel', '--presets', 'react', '--out-file', jsPath, jsxPath], { shell: true });
if (result.status !== 0) {
const msg = [];
if (result.stdout) msg.push(result.stdout.toString());
if (result.stderr) msg.push(result.stderr.toString());
console.error(msg.join('\n'));
if (result.error) console.error(result.error);
process.exit(result.status);
}
}
});
});
}
module.exports = function() {
convertJsx([
`${__dirname}/../gui`,
`${__dirname}/../gui/MainScreen`,
`${__dirname}/../gui/NoteList`,
`${__dirname}/../plugins`,
]);
const libContent = [
fs.readFileSync(`${basePath}/packages/lib/string-utils-common.js`, 'utf8'),
fs.readFileSync(`${basePath}/packages/lib/markJsUtils.js`, 'utf8'),

View File

@@ -80,13 +80,15 @@ async function main() {
const files = [
'@fortawesome/fontawesome-free/css/all.min.css',
'react-datetime/css/react-datetime.css',
'smalltalk/css/smalltalk.css',
'roboto-fontface/css/roboto/roboto-fontface.css',
'codemirror/lib/codemirror.css',
'codemirror/addon/dialog/dialog.css',
'@joeattardi/emoji-button/dist/index.js',
'codemirror/addon/dialog/dialog.css',
'codemirror/lib/codemirror.css',
'mark.js/dist/mark.min.js',
'react-datetime/css/react-datetime.css',
'roboto-fontface/css/roboto/roboto-fontface.css',
'smalltalk/css/smalltalk.css',
'smalltalk/img/IDR_CLOSE_DIALOG_H.png',
'smalltalk/img/IDR_CLOSE_DIALOG.png',
{
src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'),
dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`,

View File

@@ -0,0 +1,31 @@
// Note: this is not working because electron-builder needs access to env
// variables which are not defined in this context. To make it work, we'll need
// to somehow pass this to the execCommand call.
const execCommand = require('./execCommand');
async function main() {
process.chdir(`${__dirname}/..`);
const maxTries = 3;
for (let i = 0; i < maxTries; i++) {
try {
console.info(await execCommand(['yarn', 'run', 'electron-builder'].join(' ')));
console.info('electronBuilder: electron-builder completed successfully');
break;
} catch (error) {
console.info(error.stdout);
console.error(error);
if (error.stdout.includes('cannot resolve') && i !== maxTries - 1) {
console.info(`electronBuilder: electron-builder could not download an asset - trying again (${i + 1})`);
continue;
} else {
throw error;
}
}
}
}
module.exports = main;

View File

@@ -1,22 +1,4 @@
const execCommand = function(command) {
const exec = require('child_process').exec;
console.info(`Running: ${command}`);
return new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
if (error.signal === 'SIGTERM') {
resolve('Process was killed');
} else {
reject(error);
}
} else {
resolve(stdout.trim());
}
});
});
};
const execCommand = require('./execCommand');
const isWindows = () => {
return process && process.platform === 'win32';

View File

@@ -0,0 +1,22 @@
const execCommand = (command) => {
const exec = require('child_process').exec;
console.info(`Running: ${command}`);
return new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
if (error.signal === 'SIGTERM') {
resolve('Process was killed');
} else {
error.stdout = stdout;
reject(error);
}
} else {
resolve(stdout.trim());
}
});
});
};
module.exports = execCommand;

View File

@@ -1,24 +1,7 @@
const fs = require('fs');
const path = require('path');
const electron_notarize = require('electron-notarize');
function execCommand(command) {
const exec = require('child_process').exec;
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
if (error.signal === 'SIGTERM') {
resolve('Process was killed');
} else {
reject(new Error([stdout.trim(), stderr.trim()].join('\n')));
}
} else {
resolve([stdout.trim(), stderr.trim()].join('\n'));
}
});
});
}
const execCommand = require('./execCommand');
function isDesktopAppTag(tagName) {
if (!tagName) return false;

View File

@@ -20,6 +20,7 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local
# Android/IntelliJ
#
@@ -29,6 +30,7 @@ build/
local.properties
*.iml
*.hprof
.cxx/
# node.js
#
@@ -49,9 +51,10 @@ buck-out/
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
# Bundle artifact
*.jsbundle
@@ -66,5 +69,6 @@ lib/rnInjectedJs/
dist/
components/NoteEditor/CodeMirror/CodeMirror.bundle.js
components/NoteEditor/CodeMirror/CodeMirror.bundle.min.js
components/NoteEditor/**/*.bundle.js.md5
utils/fs-driver-android.js

View File

@@ -1 +1 @@
2.7.6
2.7.5

View File

@@ -1,4 +1,4 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby '2.7.6'
ruby '2.7.5'
gem 'cocoapods', '~> 1.11', '>= 1.11.2'

View File

@@ -79,7 +79,7 @@ import org.apache.tools.ant.taskdefs.condition.Os
*/
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
enableHermes: true, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
@@ -150,8 +150,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097679
versionName "2.10.3"
versionCode 2097681
versionName "2.10.5"
// ndk {
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
// }
@@ -165,25 +165,14 @@ android {
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
// We configure the NDK build only if you decide to opt-in for the New Architecture.
// We configure the CMake build only if you decide to opt-in for the New Architecture.
externalNativeBuild {
ndkBuild {
arguments "APP_PLATFORM=android-21",
"APP_STL=c++_shared",
"NDK_TOOLCHAIN_VERSION=clang",
"GENERATED_SRC_DIR=$buildDir/generated/source",
"PROJECT_BUILD_DIR=$buildDir",
"REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
"REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build"
cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1"
cppFlags "-std=c++17"
// Make sure this target name is the same you specify inside the
// src/main/jni/Android.mk file for the `LOCAL_MODULE` variable.
targets "joplin_appmodules"
// Fix for windows limit on number of character in file paths and in command lines
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
arguments "NDK_APP_SHORT_COMMANDS=true"
}
cmake {
arguments "-DPROJECT_BUILD_DIR=$buildDir",
"-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
"-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
"-DNODE_MODULES_DIR=$rootDir/../node_modules",
"-DANDROID_STL=c++_shared"
}
}
if (!enableSeparateBuildPerCPUArchitecture) {
@@ -197,8 +186,8 @@ android {
if (isNewArchitectureEnabled()) {
// We configure the NDK build only if you decide to opt-in for the New Architecture.
externalNativeBuild {
ndkBuild {
path "$projectDir/src/main/jni/Android.mk"
cmake {
path "$projectDir/src/main/jni/CMakeLists.txt"
}
}
def reactAndroidProjectDir = project(':ReactAndroid').projectDir
@@ -219,15 +208,15 @@ android {
preDebugBuild.dependsOn(packageReactNdkDebugLibs)
preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
// Due to a bug inside AGP, we have to explicitly set a dependency
// between configureNdkBuild* tasks and the preBuild tasks.
// between configureCMakeDebug* tasks and the preBuild tasks.
// This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
configureNdkBuildRelease.dependsOn(preReleaseBuild)
configureNdkBuildDebug.dependsOn(preDebugBuild)
configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild)
configureCMakeDebug.dependsOn(preDebugBuild)
reactNativeArchitectures().each { architecture ->
tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure {
tasks.findByName("configureCMakeDebug[${architecture}]")?.configure {
dependsOn("preDebugBuild")
}
tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure {
tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
dependsOn("preReleaseBuild")
}
}
@@ -327,9 +316,10 @@ dependencies {
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
//noinspection GradleDynamicVersion
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
exclude group:'com.facebook.fbjni'
}
} else {
implementation jscFlavor
}
@@ -345,7 +335,11 @@ if (isNewArchitectureEnabled()) {
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("com.facebook.react:react-native"))
.using(project(":ReactAndroid")).because("On New Architecture we're building React Native from source")
.using(project(":ReactAndroid"))
.because("On New Architecture we're building React Native from source")
substitute(module("com.facebook.react:hermes-engine"))
.using(project(":ReactAndroid:hermes-engine"))
.because("On New Architecture we're building Hermes from source")
}
}
}

View File

@@ -17,7 +17,8 @@ public class MainActivity extends ReactActivity {
/**
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
* you can specify the rendered you wish to use (Fabric or the older renderer).
* you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer
* (Paper).
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
@@ -34,5 +35,12 @@ public class MainActivity extends ReactActivity {
reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
return reactRootView;
}
@Override
protected boolean isConcurrentRootEnabled() {
// If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
// More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
}
}

View File

@@ -9,6 +9,7 @@ import androidx.multidex.MultiDex;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;

View File

@@ -18,6 +18,7 @@ import com.facebook.react.fabric.ComponentFactory;
import com.facebook.react.fabric.CoreComponentsRegistry;
import com.facebook.react.fabric.EmptyReactNativeConfig;
import com.facebook.react.fabric.FabricJSIModuleProvider;
import com.facebook.react.fabric.ReactNativeConfig;
import com.facebook.react.uimanager.ViewManagerRegistry;
import net.cozic.joplin.BuildConfig;
import net.cozic.joplin.newarchitecture.components.MainComponentsRegistry;
@@ -105,7 +106,7 @@ public class MainApplicationReactNativeHost extends ReactNativeHost {
return new FabricJSIModuleProvider(
reactApplicationContext,
componentFactory,
new EmptyReactNativeConfig(),
ReactNativeConfig.DEFAULT_CONFIG,
viewManagerRegistry);
}
});

View File

@@ -1,49 +0,0 @@
THIS_DIR := $(call my-dir)
include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
# If you wish to add a custom TurboModule or Fabric component in your app you
# will have to include the following autogenerated makefile.
# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk
include $(CLEAR_VARS)
LOCAL_PATH := $(THIS_DIR)
# You can customize the name of your application .so file here.
LOCAL_MODULE := joplin_appmodules
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
# If you wish to add a custom TurboModule or Fabric component in your app you
# will have to uncomment those lines to include the generated source
# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni)
#
# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
# Here you should add any native library you wish to depend on.
LOCAL_SHARED_LIBRARIES := \
libfabricjni \
libfbjni \
libfolly_futures \
libfolly_json \
libglog \
libjsi \
libreact_codegen_rncore \
libreact_debug \
libreact_nativemodule_core \
libreact_render_componentregistry \
libreact_render_core \
libreact_render_debug \
libreact_render_graphics \
librrc_view \
libruntimeexecutor \
libturbomodulejsijni \
libyoga
LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.13)
# Define the library name here.
project(joplin_appmodules)
# This file includes all the necessary to let you build your application with the New Architecture.
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)

View File

@@ -6,7 +6,7 @@ namespace facebook {
namespace react {
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
const std::string moduleName,
const std::string &moduleName,
const JavaTurboModule::InitParams &params) {
// Here you can provide your own module provider for TurboModules coming from
// either your application or from external libraries. The approach to follow
@@ -17,6 +17,13 @@ std::shared_ptr<TurboModule> MainApplicationModuleProvider(
// return module;
// }
// return rncore_ModuleProvider(moduleName, params);
// Module providers autolinked by RN CLI
auto rncli_module = rncli_ModuleProvider(moduleName, params);
if (rncli_module != nullptr) {
return rncli_module;
}
return rncore_ModuleProvider(moduleName, params);
}

View File

@@ -9,7 +9,7 @@ namespace facebook {
namespace react {
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
const std::string moduleName,
const std::string &moduleName,
const JavaTurboModule::InitParams &params);
} // namespace react

View File

@@ -22,21 +22,21 @@ void MainApplicationTurboModuleManagerDelegate::registerNatives() {
std::shared_ptr<TurboModule>
MainApplicationTurboModuleManagerDelegate::getTurboModule(
const std::string name,
const std::shared_ptr<CallInvoker> jsInvoker) {
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker) {
// Not implemented yet: provide pure-C++ NativeModules here.
return nullptr;
}
std::shared_ptr<TurboModule>
MainApplicationTurboModuleManagerDelegate::getTurboModule(
const std::string name,
const std::string &name,
const JavaTurboModule::InitParams &params) {
return MainApplicationModuleProvider(name, params);
}
bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
std::string name) {
const std::string &name) {
return getTurboModule(name, nullptr) != nullptr ||
getTurboModule(name, {.moduleName = name}) != nullptr;
}

View File

@@ -21,17 +21,17 @@ class MainApplicationTurboModuleManagerDelegate
static void registerNatives();
std::shared_ptr<TurboModule> getTurboModule(
const std::string name,
const std::shared_ptr<CallInvoker> jsInvoker) override;
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker) override;
std::shared_ptr<TurboModule> getTurboModule(
const std::string name,
const std::string &name,
const JavaTurboModule::InitParams &params) override;
/**
* Test-only method. Allows user to verify whether a TurboModule can be
* created by instances of this class.
*/
bool canCreateTurboModule(std::string name);
bool canCreateTurboModule(const std::string &name);
};
} // namespace react

View File

@@ -4,6 +4,7 @@
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/rncore/ComponentDescriptors.h>
#include <rncli.h>
namespace facebook {
namespace react {
@@ -14,6 +15,9 @@ std::shared_ptr<ComponentDescriptorProviderRegistry const>
MainComponentsRegistry::sharedProviderRegistry() {
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
// Autolinked providers registered by RN CLI
rncli_registerProviders(providerRegistry);
// Custom Fabric Components go here. You can register custom
// components coming from your App or from 3rd party libraries here.
//

View File

@@ -21,9 +21,9 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.4")
classpath("com.android.tools.build:gradle:7.2.1")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("de.undercouch:gradle-download-task:4.1.2")
classpath("de.undercouch:gradle-download-task:5.0.1")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,8 +1,12 @@
rootProject.name = 'Joplin'
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
include(":ReactAndroid")
project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid')
include(":ReactAndroid:hermes-engine")
project(":ReactAndroid:hermes-engine").projectDir = file('../node_modules/react-native/ReactAndroid/hermes-engine')
}

View File

@@ -0,0 +1,73 @@
const React = require('react');
import { useState, useCallback, useMemo } from 'react';
const Icon = require('react-native-vector-icons/Ionicons').default;
import { FAB, Portal } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';
type OnButtonPress = ()=> void;
interface ButtonSpec {
icon: string;
label: string;
color?: string;
onPress?: OnButtonPress;
}
interface ActionButtonProps {
buttons?: ButtonSpec[];
// If not given, an "add" button will be used.
mainButton?: ButtonSpec;
}
const defaultOnPress = () => {};
// Returns a render function compatible with React Native Paper.
const getIconRenderFunction = (iconName: string) => {
return (props: any) => <Icon name={iconName} {...props} />;
};
const useIcon = (iconName: string) => {
return useMemo(() => {
return getIconRenderFunction(iconName);
}, [iconName]);
};
const ActionButton = (props: ActionButtonProps) => {
const [open, setOpen] = useState(false);
const onMenuToggled = useCallback(
(state: { open: boolean }) => setOpen(state.open)
, [setOpen]);
const actions = useMemo(() => (props.buttons ?? []).map(button => {
return {
...button,
icon: getIconRenderFunction(button.icon),
onPress: button.onPress ?? defaultOnPress,
};
}), [props.buttons]);
const closedIcon = useIcon(props.mainButton?.icon ?? 'md-add');
const openIcon = useIcon('close');
return (
<Portal>
<FAB.Group
open={open}
accessibilityLabel={props.mainButton?.label ?? _('Add new')}
icon={ open ? openIcon : closedIcon }
fabStyle={{
backgroundColor: props.mainButton?.color ?? 'rgba(231,76,60,1)',
}}
onStateChange={onMenuToggled}
actions={actions}
onPress={props.mainButton?.onPress ?? defaultOnPress}
visible={true}
/>
</Portal>
);
};
export default ActionButton;

View File

@@ -1,6 +1,7 @@
const React = require('react');
import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle } from 'react-native';
import { Component } from 'react';
import { _ } from '@joplin/lib/locale';
const { ItemList } = require('./ItemList.js');
type ValueType = string;
@@ -58,6 +59,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
const items = this.props.items;
const itemHeight = 60;
const windowHeight = Dimensions.get('window').height - 50;
const windowWidth = Dimensions.get('window').width;
// Dimensions doesn't return quite the right dimensions so leave an extra gap to make
// sure nothing is off screen.
@@ -66,11 +68,20 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
const maxListTop = windowHeight - listHeight;
const listTop = Math.min(maxListTop, this.state.headerSize.y + this.state.headerSize.height);
const wrapperStyle = {
const wrapperStyle: ViewStyle = {
width: this.state.headerSize.width,
height: listHeight + 2, // +2 for the border (otherwise it makes the scrollbar appear)
marginTop: listTop,
marginLeft: this.state.headerSize.x,
top: listTop,
left: this.state.headerSize.x,
position: 'absolute',
};
const backgroundCloseButtonStyle: ViewStyle = {
position: 'absolute',
top: 0,
left: 0,
height: windowHeight,
width: windowWidth,
};
const itemListStyle = Object.assign({}, this.props.itemListStyle ? this.props.itemListStyle : {}, {
@@ -126,6 +137,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
return (
<TouchableOpacity
style={itemWrapperStyle}
accessibilityRole="menuitem"
key={key}
onPress={() => {
closeList();
@@ -139,6 +151,22 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
);
};
// Use a separate screen-reader-only button for closing the menu. If we
// allow the background to be focusable, instead, the focus order might be
// incorrect on some devices. For example, the background button might be focused
// when navigating near the middle of the dropdown's list.
const screenReaderCloseMenuButton = (
<TouchableWithoutFeedback
accessibilityRole='button'
onPress={()=> closeList()}
>
<Text style={{
opacity: 0,
height: 0,
}}>{_('Close dropdown')}</Text>
</TouchableWithoutFeedback>
);
return (
<View style={{ flex: 1, flexDirection: 'column' }}>
<TouchableOpacity
@@ -163,21 +191,28 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
}}
>
<TouchableWithoutFeedback
onPressOut={() => {
accessibilityElementsHidden={true}
importantForAccessibility='no-hide-descendants'
onPress={() => {
closeList();
}}
style={backgroundCloseButtonStyle}
>
<View style={{ flex: 1 }}>
<View style={wrapperStyle}>
<ItemList
style={itemListStyle}
items={this.props.items}
itemHeight={itemHeight}
itemRenderer={itemRenderer}
/>
</View>
</View>
<View style={{ flex: 1 }}/>
</TouchableWithoutFeedback>
<View
accessibilityRole='menu'
style={wrapperStyle}>
<ItemList
style={itemListStyle}
items={this.props.items}
itemHeight={itemHeight}
itemRenderer={itemRenderer}
/>
</View>
{screenReaderCloseMenuButton}
</Modal>
</View>
);

View File

@@ -25,9 +25,11 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
const [resourceLoadedTime, setResourceLoadedTime] = useState(0);
const [isFirstRender, setIsFirstRender] = useState(true);
const paddingTop = '.8em';
const rendererTheme = useMemo(() => {
return {
bodyPaddingTop: '.8em', // Extra top padding on the rendered MD so it doesn't touch the border
bodyPaddingTop: paddingTop, // Extra top padding on the rendered MD so it doesn't touch the border
bodyPaddingBottom: paddingBottom, // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text)
...themeStyle(themeId),
};
@@ -152,8 +154,14 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
*/
body > #rendered-md {
width: 100vw;
height: 100vh;
overflow: auto;
height: calc(100vh - ${paddingBottom}px - ${paddingTop});
padding-bottom: ${paddingBottom}px;
padding-top: ${paddingTop};
}
:root > body {
padding: 0;
}
`;

View File

@@ -1 +0,0 @@
0e1c132c199587530715a478bdb29a74

View File

@@ -65,41 +65,41 @@ describe('markdownCommands.toggleList', () => {
});
it('should correctly replace an unordered list with a checklist', async () => {
const editor = await createEditor(
unorderedListText,
EditorSelection.cursor(unorderedListText.length),
['BulletList']
);
// it('should correctly replace an unordered list with a checklist', async () => {
// const editor = await createEditor(
// unorderedListText,
// EditorSelection.cursor(unorderedListText.length),
// ['BulletList']
// );
toggleList(ListType.CheckList)(editor);
expect(editor.state.doc.toString()).toBe(
'- [ ] 1\n- [ ] 2\n- [ ] 3\n- [ ] 4\n- [ ] 5\n- [ ] 6\n- [ ] 7'
);
});
// toggleList(ListType.CheckList)(editor);
// expect(editor.state.doc.toString()).toBe(
// '- [ ] 1\n- [ ] 2\n- [ ] 3\n- [ ] 4\n- [ ] 5\n- [ ] 6\n- [ ] 7'
// );
// });
it('should properly toggle a sublist of a bulleted list', async () => {
const preSubListText = '# List test\n * This\n * is\n';
const initialDocText = `${preSubListText}\t* a\n\t* test\n * of list toggling`;
// it('should properly toggle a sublist of a bulleted list', async () => {
// const preSubListText = '# List test\n * This\n * is\n';
// const initialDocText = `${preSubListText}\t* a\n\t* test\n * of list toggling`;
const editor = await createEditor(
initialDocText,
EditorSelection.cursor(preSubListText.length + '\t* a'.length),
['BulletList', 'ATXHeading1']
);
// const editor = await createEditor(
// initialDocText,
// EditorSelection.cursor(preSubListText.length + '\t* a'.length),
// ['BulletList', 'ATXHeading1']
// );
// Indentation should be preserved when changing list types
toggleList(ListType.OrderedList)(editor);
expect(editor.state.doc.toString()).toBe(
'# List test\n * This\n * is\n\t1. a\n\t2. test\n * of list toggling'
);
// // Indentation should be preserved when changing list types
// toggleList(ListType.OrderedList)(editor);
// expect(editor.state.doc.toString()).toBe(
// '# List test\n * This\n * is\n\t1. a\n\t2. test\n * of list toggling'
// );
// The changed region should be selected
expect(editor.state.selection.main.from).toBe(preSubListText.length);
expect(editor.state.selection.main.to).toBe(
`${preSubListText}\t1. a\n\t2. test`.length
);
});
// // The changed region should be selected
// expect(editor.state.selection.main.from).toBe(preSubListText.length);
// expect(editor.state.selection.main.to).toBe(
// `${preSubListText}\t1. a\n\t2. test`.length
// );
// });
it('should not preserve indentation when removing sublists', async () => {
const preSubListText = '# List test\n * This\n * is\n';

View File

@@ -72,8 +72,8 @@ const createTheme = (theme: any): Extension[] => {
'&.cm-focused .cm-selectionBackground, ::selection': baseSelectionStyle,
'.cm-selectionBackground': blurredSelectionStyle,
'&.cm-focused': {
outline: 'none',
'&.cm-editor.cm-focused': {
outline: 'none !important',
},
'& .cm-blockQuote': {

View File

@@ -2,12 +2,20 @@
// Because this will be running both in a WebView and in nodeJS, we need to use
// globalThis in place of window. We need to tell ESLint that we're doing this:
/* global globalThis*/
// /* global globalThis*/
export function postMessage(name: string, data: any) {
// Only call postMessage if we're running in a WebView (this code may be called
// in integration tests).
(globalThis as any).ReactNativeWebView?.postMessage(JSON.stringify({
// globalThis doesn't seem to be defined as of RN 69 or 70, so use `window` instead.
// (globalThis as any).ReactNativeWebView?.postMessage(JSON.stringify({
// data,
// name,
// }));
(window as any).ReactNativeWebView?.postMessage(JSON.stringify({
data,
name,
}));

View File

@@ -99,9 +99,16 @@ function useHtml(css: string): string {
}
function editorTheme(themeId: number) {
const fontSizeInPx = Setting.value('style.editor.fontSize');
// Convert from `px` to `em`. To support font size scaling based on
// system accessibility settings, we need to provide font sizes in `em`.
// 16px is about 1em with the default root font size.
const estimatedFontSizeInEm = fontSizeInPx / 16;
return {
...themeStyle(themeId),
fontSize: 0.85, // em
fontSize: estimatedFontSizeInEm,
fontFamily: fontFamilyFromSettings(),
};
}

View File

@@ -0,0 +1,102 @@
const React = require('react');
import { useCallback, useEffect, useMemo, useState } from 'react';
const { View, StyleSheet } = require('react-native');
import createRootStyle from '../../utils/createRootStyle';
import ScreenHeader from '../ScreenHeader';
import { _ } from '@joplin/lib/locale';
import { loadProfileConfig, saveProfileConfig } from '../../services/profiles';
import { createNewProfile } from '@joplin/lib/services/profileConfig';
import useProfileConfig from './useProfileConfig';
const { TextInput } = require('react-native-paper');
interface NavigationState {
profileId: string;
}
interface Navigation {
state: NavigationState;
}
interface Props {
themeId: number;
dispatch: Function;
navigation: Navigation;
}
const useStyle = (themeId: number) => {
return useMemo(() => {
return StyleSheet.create({
...createRootStyle(themeId),
});
}, [themeId]);
};
export default (props: Props) => {
const profileId = props.navigation.state?.profileId;
const isNew = !profileId;
const profileConfig = useProfileConfig();
const style = useStyle(props.themeId);
const [name, setName] = useState('');
const profile = !isNew && profileConfig ? profileConfig.profiles.find(p => p.id === profileId) : null;
useEffect(() => {
if (!profile) return;
setName(profile.name);
}, [profile]);
const onSaveButtonPress = useCallback(async () => {
if (isNew) {
const profileConfig = await loadProfileConfig();
const result = createNewProfile(profileConfig, name);
await saveProfileConfig(result.newConfig);
} else {
const newProfiles = profileConfig.profiles.map(p => {
if (p.id === profile.id) {
return {
...profile,
name,
};
}
return p;
});
const newProfileConfig = {
...profileConfig,
profiles: newProfiles,
};
await saveProfileConfig(newProfileConfig);
}
props.dispatch({
type: 'NAV_BACK',
});
}, [name, isNew, profileConfig, profile, props.dispatch]);
const isModified = useMemo(() => {
if (isNew) return true;
if (!profile) return false;
return profile.name !== name;
}, [isNew, profile, name]);
return (
<View style={style.root}>
<ScreenHeader
title={isNew ? _('Create new profile...') : _('Edit profile')}
onSaveButtonPress={onSaveButtonPress}
saveButtonDisabled={!isModified}
showSaveButton={true}
showSideMenuButton={false}
showSearchButton={false}
/>
<View style={{}}>
<TextInput label={_('Profile name')}
value={name}
onChangeText={(text: string) => setName(text)}
/>
</View>
</View>
);
};

View File

@@ -0,0 +1,176 @@
const React = require('react');
import { useCallback, useMemo, useState } from 'react';
const { View, FlatList, StyleSheet } = require('react-native');
import createRootStyle from '../../utils/createRootStyle';
import ScreenHeader from '../ScreenHeader';
const { FAB, List } = require('react-native-paper');
import { Profile } from '@joplin/lib/services/profileConfig/types';
import useProfileConfig from './useProfileConfig';
import { Alert } from 'react-native';
import { _ } from '@joplin/lib/locale';
import { deleteProfileById } from '@joplin/lib/services/profileConfig';
import { saveProfileConfig, switchProfile } from '../../services/profiles';
const { themeStyle } = require('../global-style');
interface Props {
themeId: number;
dispatch: Function;
}
const useStyle = (themeId: number) => {
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
...createRootStyle(themeId),
fab: {
position: 'absolute',
margin: 16,
right: 0,
bottom: 0,
},
profileListItem: {
paddingLeft: theme.margin,
paddingRight: theme.margin,
},
});
}, [themeId]);
};
export default (props: Props) => {
const style = useStyle(props.themeId);
const [profileConfigTime, setProfileConfigTime] = useState(Date.now());
const profileConfig = useProfileConfig(profileConfigTime);
const profiles = useMemo(() => {
return profileConfig ? profileConfig.profiles : [];
}, [profileConfig]);
const onProfileItemPress = useCallback(async (profile: Profile) => {
const doIt = async () => {
try {
await switchProfile(profile.id);
} catch (error) {
Alert.alert(_('Could not switch profile: %s', error.message));
}
};
Alert.alert(
_('Confirmation'),
_('To switch the profile, the app is going to close and you will need to restart it.'),
[
{
text: _('Continue'),
onPress: () => doIt(),
style: 'default',
},
{
text: _('Cancel'),
onPress: () => {},
style: 'cancel',
},
]
);
}, []);
const onEditProfile = useCallback(async (profileId: string) => {
props.dispatch({
type: 'NAV_GO',
routeName: 'ProfileEditor',
profileId: profileId,
});
}, [props.dispatch]);
const onDeleteProfile = useCallback(async (profile: Profile) => {
const doIt = async () => {
try {
const newConfig = deleteProfileById(profileConfig, profile.id);
await saveProfileConfig(newConfig);
setProfileConfigTime(Date.now());
} catch (error) {
Alert.alert(error.message);
}
};
Alert.alert(
_('Delete this profile?'),
_('All data, including notes, notebooks and tags will be permanently deleted.'),
[
{
text: _('Delete profile "%s"', profile.name),
onPress: () => doIt(),
style: 'destructive',
},
{
text: _('Cancel'),
onPress: () => {},
style: 'cancel',
},
]
);
}, [profileConfig]);
const renderProfileItem = (event: any) => {
const profile = event.item as Profile;
const titleStyle = { fontWeight: profile.id === profileConfig.currentProfileId ? 'bold' : 'normal' };
return (
<List.Item
title={profile.name}
style={style.profileListItem}
titleStyle={titleStyle}
left={() => <List.Icon icon="file-account-outline" />}
key={profile.id}
profileId={profile.id}
onPress={() => { void onProfileItemPress(profile); }}
onLongPress={() => {
Alert.alert(
_('Configuration'),
'',
[
{
text: _('Edit'),
onPress: () => onEditProfile(profile.id),
style: 'default',
},
{
text: _('Delete'),
onPress: () => onDeleteProfile(profile),
style: 'default',
},
{
text: _('Close'),
onPress: () => {},
style: 'cancel',
},
]
);
}}
/>
);
};
return (
<View style={style.root}>
<ScreenHeader title={_('Profiles')} showSaveButton={false} showSideMenuButton={false} showSearchButton={false} />
<View>
<FlatList
data={profiles}
renderItem={renderProfileItem}
keyExtractor={(profile: Profile) => profile.id}
/>
</View>
<FAB
icon="plus"
style={style.fab}
onPress={() => {
props.dispatch({
type: 'NAV_GO',
routeName: 'ProfileEditor',
});
}}
/>
</View>
);
};

View File

@@ -0,0 +1,20 @@
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import { useState } from 'react';
import { loadProfileConfig } from '../../services/profiles';
export default (timestamp: number = 0) => {
const [profileConfig, setProfileConfig] = useState<ProfileConfig>(null);
useAsyncEffect(async (event: AsyncEffectEvent) => {
const load = async () => {
const r = await loadProfileConfig();
if (event.cancelled) return;
setProfileConfig(r);
};
void load();
}, [timestamp]);
return profileConfig;
};

View File

@@ -103,7 +103,7 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
return (
<View style={{ flex: 0, margin: 20, alignItems: 'center' }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{ this.state.date && <Text style={{ ...theme.normalText,color: theme.color, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
{ this.state.date && <Text style={{ ...theme.normalText, color: theme.color, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
<Button title="Set date" onPress={this.onSetDate} />
</View>
<DateTimePickerModal

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