1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

355 Commits

Author SHA1 Message Date
Laurent Cozic
f0496bd348 Android 3.1.3 2024-09-02 13:18:23 +01:00
Laurent Cozic
a746c02d4a Desktop release v3.1.6 2024-09-02 13:05:22 +01:00
Laurent Cozic
02def5900e lock file 2024-09-02 13:04:32 +01:00
Henry Heino
fd06c18cf0 Desktop: Windows portable: Fix keychain-backed storage incorrectly enabled (#10942) 2024-09-02 12:26:43 +01:00
Alice
6163364b26 Desktop: Seamless-Updates - added tests for autoUpdaterService (#10935)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-09-02 12:24:19 +01:00
renovate[bot]
7e9c7d7d23 Update dependency @types/node to v18.19.34 (#10966) 2024-09-02 02:37:00 +00:00
renovate[bot]
88d02b04dd Update dependency ldapts to v7.0.12 (#10951) 2024-09-01 20:33:07 +00:00
renovate[bot]
42e39b7789 Update dependency re-resizable to v6.9.17 (#10953) 2024-09-01 20:33:05 +00:00
renovate[bot]
9b3ce555cb Update dependency react-native-webview to v13.8.7 (#10956) 2024-09-01 20:33:04 +00:00
renovate[bot]
bae311b2a9 Update dependency sharp to v0.33.4 (#10958) 2024-09-01 20:32:56 +00:00
Laurent Cozic
f8a09e5540 Update renovate.json5 2024-09-01 21:14:35 +01:00
renovate[bot]
461ba1762a Update dependency sass to v1.76.0 (#10962)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:11:34 +01:00
renovate[bot]
82a4b8915b Update dependency async-mutex to v0.5.0 (#10961)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:11:21 +01:00
renovate[bot]
e8593e7ae5 Update dependency @react-native-clipboard/clipboard to v1.14.1 (#10960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:11:13 +01:00
renovate[bot]
8f1c7bf61c Update dependency @playwright/test to v1.43.1 (#10959)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:11:04 +01:00
renovate[bot]
23c08f77e0 Update dependency react-native-safe-area-context to v4.10.3 (#10955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:10:28 +01:00
renovate[bot]
e1fa7d0e83 Update dependency react-native-device-info to v10.13.2 (#10954)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:10:15 +01:00
renovate[bot]
fc3c59bad5 Update dependency lint-staged to v15.2.5 (#10952)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:10:01 +01:00
renovate[bot]
8588e36373 Update dependency koa to v2.15.3 (#10950)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:09:44 +01:00
renovate[bot]
1f43b199d5 Update dependency glob to v10.3.16 (#10949)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 21:09:37 +01:00
Joplin Bot
e2fd48eecd Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-09-01 00:53:18 +00:00
renovate[bot]
d0e6368867 Update dependency @types/react to v18.3.3 (#10964) 2024-08-31 18:04:01 +00:00
renovate[bot]
4f98aad24f Update dependency dayjs to v1.11.11 (#10948)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-31 17:59:17 +00:00
renovate[bot]
fae313785d Update dependency react-native-zip-archive to v6.1.2 (#10957) 2024-08-31 15:29:19 +00:00
renovate[bot]
db5bbeecd4 Update react monorepo (#10963) 2024-08-31 15:28:44 +00:00
Henry Heino
d2b7d64f4f Desktop: Accessibility: Improve note list keyboard and screen reader accessibility (#10940) 2024-08-31 16:05:01 +01:00
Henry Heino
4f2d0c8e5d Desktop: Add left/right arrow keys as expand/collapse shortcuts for notebooks (#10944) 2024-08-31 15:44:12 +01:00
Alice
3bca12fd4a Desktop: Seamless-Updates: added latest-mac-arm64.yml (#10941) 2024-08-31 15:42:07 +01:00
Henry Heino
4ccc46e4db Docs: Fix typo: Change "Thee" to "The" (#10945) 2024-08-31 15:40:28 +01:00
renovate[bot]
5062cd1616 Update dependency @bam.tech/react-native-image-resizer to v3.0.10 (#10938) 2024-08-28 20:52:24 +00:00
renovate[bot]
1710155a6f Update dependency @types/jsdom to v21.1.7 (#10939) 2024-08-28 20:52:17 +00:00
Joplin Bot
4cb693b4d6 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-28 00:45:16 +00:00
Joplin Bot
e607a7376f Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-27 18:20:33 +00:00
Laurent Cozic
d1403ba2cb Desktop release v3.1.4 2024-08-27 18:06:16 +01:00
Henry Heino
74be949d33 Desktop: Accessibility: Improve note title focus handling (#10932) 2024-08-27 18:05:48 +01:00
Alice
2afc2ca369 Desktop: Seamless-Updates: implemented flow for prereleases (#10892) 2024-08-27 18:04:18 +01:00
Laurent Cozic
b617a84696 All: Resolves #10407: Added feature flag to disable sync lock support (#10925) 2024-08-23 12:16:19 +01:00
Henry Heino
c691fedc12 Mac: Fixes #10921: Markdown editor: Fix custom arrow-key shortcuts fail to register (#10922) 2024-08-23 11:49:30 +01:00
Henry Heino
12a26023dd Chore: CI: Fix .eslintignore and .gitignore CI check passes when it should fail (#10923) 2024-08-23 11:49:17 +01:00
Laurent Cozic
700ffa2529 Desktop, Mobile, Cli: Make feature flags advanced settings by default 2024-08-23 11:39:40 +01:00
Laurent Cozic
5fdd088504 Desktop, Mobile: Fixed italic support in Fountain documents 2024-08-23 10:05:50 +01:00
Laurent Cozic
fe89b1cf21 Merge branch 'release-3.0' into dev 2024-08-23 09:52:04 +01:00
Henry Heino
3d1bbf3c4d Chore: Web: Fix build fails on Windows (#10919) 2024-08-22 21:53:52 +01:00
Henry Heino
580d2914e7 Mobile,Desktop: Fixes #10914: Fix BMP image rendering in the Markdown viewer (#10915) 2024-08-22 21:53:44 +01:00
Henry Heino
33599324d6 Desktop: Fixes #10737: Fix Fix editing notes in "Conflicts" causes them to temporarily vanish (#10913) 2024-08-22 21:53:39 +01:00
Henry Heino
02bdb7a79c Docs: Include dependency overview (#10911)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-08-22 21:53:27 +01:00
Henry Heino
a1c64eeefc Web: Fix renderer plugin assets fail to load on secondary profiles (#10903) 2024-08-22 21:52:42 +01:00
Henry Heino
78015028ec Desktop, Mobile: Fixes #10891: Markdown editor: Fix toggling bulleted lists when items start with asterisks (#10902) 2024-08-22 21:52:36 +01:00
Henry Heino
beff45e195 Desktop: Fix viewer and panel plugins that check for the presence of exports fail to load (#10900) 2024-08-22 21:52:29 +01:00
Henry Heino
6a0dd4e20d Desktop: Fixes #10895: Fix alt+up/alt+down fails to re-order multiple lines (#10899) 2024-08-22 21:52:22 +01:00
Henry Heino
ea420967c4 Chore: Remove recently-added .gitignored files (#10898) 2024-08-22 21:52:17 +01:00
Henry Heino
c19d0773b3 Mobile,Desktop,Cli: Fixes #10896: Fix "Enable auto-updates" enabled by default and visible on unsupported platforms (#10897) 2024-08-22 21:51:57 +01:00
Henry Heino
59a8c2115c Chore: Fix CI: Add Famegear to cspell dictionary (#10912) 2024-08-21 20:57:11 +01:00
Joplin Bot
88cae93fc4 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-21 18:20:16 +00:00
Laurent Cozic
4eb0fb542e Doc: Update sponsors 2024-08-21 18:26:58 +01:00
Henry Heino
60a1617b22 Chore: Fix compilation errors due to test resources with .ts extensions (#10909) 2024-08-21 18:24:33 +01:00
Henry Heino
e05d7197bd Docs: Improve explanation for using custom OCR language data (#10910) 2024-08-21 18:24:05 +01:00
Joplin Bot
9dc29d034b Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-21 12:23:24 +00:00
renovate[bot]
32fe122e60 Update dependency @fortawesome/react-fontawesome to v0.2.2 (#10905) 2024-08-21 08:45:31 +00:00
renovate[bot]
8fcb377162 Update dependency @rollup/plugin-commonjs to v25.0.8 (#10906) 2024-08-21 08:45:24 +00:00
Laurent Cozic
598677b207 Desktop release v3.0.15 2024-08-21 09:36:44 +01:00
renovate[bot]
b3437fa4cd Update dependency @js-draw/material-icons to v1.20.2 (#10894) 2024-08-19 20:52:32 +00:00
Joplin Bot
0d82584777 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-18 12:23:44 +00:00
Laurent Cozic
e627fb7fad Doc: Update sponsors 2024-08-18 10:41:09 +01:00
Joplin Bot
b60acf038b Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-17 18:18:50 +00:00
renovate[bot]
111820c0cf Update dependency @js-draw/material-icons to v1.20.1 (#10890) 2024-08-17 11:38:01 +00:00
Laurent Cozic
263a7ef076 Desktop release v3.1.3 2024-08-17 12:23:14 +01:00
Henry Heino
255d3e737d Chore: Windows: Fix building the desktop app clears the list of supported Rich Text Editor locales (#10885) 2024-08-17 12:22:52 +01:00
Henry Heino
9cb13af7b0 Chore: Windows: Fix mobile plugin assets change when rebuilding the mobile app (#10886) 2024-08-17 12:22:35 +01:00
Henry Heino
7bc81880e1 Windows: Fix handleCustomProtocols test (#10884) 2024-08-17 12:22:13 +01:00
Henry Heino
eb53c7e3b9 Windows: Fix PDF, video, and audio rendering (#10881) 2024-08-17 12:22:03 +01:00
Henry Heino
b94cf5a107 Desktop: Resolves #5762: Rich Text Editor: Add eight spaces when pressing tab (#10880) 2024-08-17 12:21:43 +01:00
Alice
72163018b4 Desktop: Seamless-Updates - added flow for automatic updates for releases (#10857)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-08-17 12:19:05 +01:00
github-actions[bot]
0cbf8d8ebc @maresmar has signed the CLA in laurent22/joplin#10882 2024-08-16 18:34:22 +00:00
Joplin Bot
bde14f9ddc Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-16 12:23:54 +00:00
Henry Heino
b220413a9b Desktop: Security: Fix HTML parsing bug (#10876) 2024-08-15 19:37:58 +01:00
Henry Heino
72472623cc Merge remote-tracking branch 'origin/release-3.0' into dev 2024-08-15 08:34:56 -07:00
Laurent Cozic
7e599c3a3e Desktop release v3.1.2 2024-08-15 16:09:57 +01:00
Henry Heino
74ba386ae3 Docs: Mobile plugins: Document using the web app to run mobile plugins (#10831) 2024-08-15 16:09:11 +01:00
Henry Heino
06f05d3823 Docs: Add example transformers.js-based plugin (#10840) 2024-08-15 16:08:57 +01:00
Henry Heino
6ada0a0103 Docs: Explain purpose of main Cloud Teams account (#10841)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-08-15 16:08:43 +01:00
Henry Heino
39bd7d3aef Mobile: Fixes #10867: Fix toolbar overflow menu is invisible (#10871) 2024-08-15 16:02:05 +01:00
Henry Heino
b5313067cd Desktop: Allow searching when only the note viewer is visible and sync search with editor (#10866) 2024-08-15 16:01:52 +01:00
Henry Heino
1edef99811 Desktop: Upgrade electron to v29.4.5 (#10847)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-08-15 16:00:46 +01:00
Alice
6518c17679 Desktop: Seamless-Updates: generate latest-mac.yml file (#10869) 2024-08-15 15:59:18 +01:00
Laurent Cozic
3ef07ac39a Clipper release v3.1.1 2024-08-15 10:51:18 +01:00
Laurent Cozic
bfcf3815a4 Chore: Fixing clipper dependencies 2024-08-15 10:50:14 +01:00
renovate[bot]
292479ac28 Update dependency @fortawesome/react-fontawesome to v0.2.1 (#10875) 2024-08-15 05:35:10 +00:00
Laurent Cozic
786e58ed54 Chore: Remove jcenter() dependency, now discontinued (#10868) 2024-08-13 14:40:45 +01:00
Naoki Kawamukai
b88630855f All: Translation: Update ja_JP.po (#10864) 2024-08-12 16:40:07 -04:00
cedecode
b956a21dae All: Translation: Update de_DE.po (#10863) 2024-08-12 16:39:11 -04:00
Henry Heino
c4ebb64fb2 Chore: Mobile,Testing: Fix keychain warning (#10861) 2024-08-12 15:34:36 +01:00
renovate[bot]
fb181cf935 Update dependency @react-native-community/datetimepicker to v8.0.1 (#10862) 2024-08-12 02:56:05 +00:00
Joplin Bot
f6fdc9fe66 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-10 18:18:17 +00:00
Laurent Cozic
23f521de74 iOS 13.1.3 2024-08-10 14:08:45 +01:00
Joplin Bot
88ed7c934f Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-10 12:21:37 +00:00
renovate[bot]
4c7a1f0f88 Update dependency @react-native-community/netinfo to v11.3.2 (#10855) 2024-08-10 12:05:01 +00:00
Laurent Cozic
98b7354373 iOS 13.1.2 2024-08-10 12:51:29 +01:00
Laurent Cozic
8d8cca06eb Android 3.1.2 2024-08-10 12:50:34 +01:00
Henry Heino
45b61e1f49 iOS: Fix crash on opening the sidebar (#10852) 2024-08-09 20:49:10 +01:00
Henry Heino
f58d29a6a6 Mobile: Fixes #10848: Fix WebDAV sync on mobile (#10849) 2024-08-09 20:49:01 +01:00
Joplin Bot
4caca4934e Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-09 12:23:13 +00:00
Laurent Cozic
a8613d1444 lock file 2024-08-09 12:16:58 +01:00
Laurent Cozic
c6377eafbf iOS 13.1.1 2024-08-09 12:15:35 +01:00
Laurent Cozic
aa23d5cdff Android 3.1.1 2024-08-09 12:14:25 +01:00
Laurent Cozic
a97c04c21c Desktop release v3.1.1 2024-08-09 11:48:06 +01:00
Laurent Cozic
9f66d7cfcd Chore: Setup new release 3.1 2024-08-09 11:47:43 +01:00
Laurent Cozic
a52b206dfb Chore: Temporarily disable new auto-update code 2024-08-09 11:45:08 +01:00
Henry Heino
806377e6ee Desktop: Resolves #10835: Allow specifying custom language data URLs (#10846) 2024-08-09 11:29:39 +01:00
Henry Heino
6ce55a5737 Chore: Update ignore files (#10845) 2024-08-08 20:03:02 +01:00
Henry Heino
cd40861ec8 Chore: Generator: Update types (#10844) 2024-08-08 20:02:53 +01:00
Henry Heino
a1aa4f78c9 Desktop: Resolves #10746: Fix "View OCR text" not present in context menu when right-clicking an image (#10842) 2024-08-08 20:02:08 +01:00
Henry Heino
0caecedb8f Desktop: Fixes #10828: Fix error when canceling bulk PDF export (#10839) 2024-08-08 20:00:19 +01:00
Henry Heino
af7e172438 Desktop: Fix crash on opening certain plugin dialogs (#10838) 2024-08-08 20:00:13 +01:00
Henry Heino
1f45252fc6 Docs: Update the Markdown editor plugin tutorial to reflect recent Joplin changes (#10830) 2024-08-08 19:57:56 +01:00
Henry Heino
d2ae02d066 Mobile: Include commit information in version information screen (#10829) 2024-08-08 19:57:32 +01:00
丛林意志
6d98f8102d Chore: i18n: Fixed localization issue and added spaces around words in Chinese-English text (#10821) 2024-08-08 19:56:34 +01:00
Henry Heino
08eab7a73a Desktop: Use Electron safeStorage for keychain support (#10535) 2024-08-08 19:53:43 +01:00
Joplin Bot
8d8c91ef50 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-08 12:23:28 +00:00
Alice
88b3c7f526 Desktop: Seamless-Updates - creation of update notification (#10791) 2024-08-08 10:49:21 +01:00
github-actions[bot]
24731edf92 @rxliuli has signed the CLA in laurent22/joplin#10843 2024-08-08 08:12:21 +00:00
Joplin Bot
85557b6882 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-07 18:19:36 +00:00
Joplin Bot
8dfc873ceb Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-07 12:23:29 +00:00
Laurent Cozic
8961aebc3a Doc: Fixed release cycle 2024-08-06 19:25:48 +01:00
renovate[bot]
500c31565d Update dependency @types/node to v18.19.33 (#10837) 2024-08-06 16:16:30 +00:00
Henry Heino
223a685529 Docs: Fix typo: Change "Table of Content" to "Table of Contents" (#10832) 2024-08-06 17:00:23 +01:00
Andy Piper
135d46f31b Doc: Correct typo and add detail to config_screen.md (#10823) 2024-08-05 21:34:09 +01:00
Henry Heino
9f997c2fb6 Desktop: Accessibility: Fix screen reader doesn't read Goto Anything search results or help button label (#10816) 2024-08-05 19:37:23 +01:00
pedr
a6cf0a3a81 Desktop: Fixes #10740: Improve the reliability of fetching resources (#10826) 2024-08-05 19:36:54 +01:00
renovate[bot]
1c2c071952 Update dependency @types/node to v18.19.32 (#10827) 2024-08-05 15:03:38 +00:00
github-actions[bot]
08348c88eb @shivam1234100 has signed the CLA in laurent22/joplin#10825 2024-08-05 14:38:34 +00:00
github-actions[bot]
70bfb9f18d @conglinyizhi has signed the CLA in laurent22/joplin#10821 2024-08-04 05:17:18 +00:00
Henry Heino
292d2fbc15 Desktop: Accessibility: Restore keyboard focus when closing a dialog (#10817) 2024-08-03 16:43:16 +01:00
Henry Heino
88cf1d6232 Desktop: Fix prompt tag dialog input can be wider than its container (#10818) 2024-08-03 16:43:00 +01:00
Henry Heino
9cf298ef44 Desktop: Accessibility: Improve keyboard navigation in the Markdown and note toolbar (#10819) 2024-08-03 16:42:46 +01:00
Henry Heino
19af6a8722 Desktop,Mobile: Fixes #10785: Fix math is invisible in certain mermaid diagrams (#10820) 2024-08-03 16:42:16 +01:00
pedr
5c8be448ab Desktop: Fixes #10668: Tags and Delete note not being available on Search and on All Notes list (#10729) 2024-08-02 17:44:30 +01:00
Henry Heino
f69dffcf23 Mobile: Support building for web (#10650) 2024-08-02 14:51:49 +01:00
Henry Heino
88271bf1a7 Desktop: Fixes #10815: Fix Enter key submits dialogs even if a button has focus (#10814) 2024-08-02 14:51:25 +01:00
Henry Heino
14cc053094 Desktop: Accessibility: Improve settings screen keyboard navigation and screen reader accessibility (#10812) 2024-08-02 14:49:15 +01:00
Henry Heino
65ef700fdc Mobile, Desktop: Improve RTL support in the Markdown editor (#10810) 2024-08-02 14:47:56 +01:00
Henry Heino
9dbd481f28 Desktop: Fix images fail to render in the preview pane for HTML notes (#10806) 2024-08-02 14:47:43 +01:00
Henry Heino
e5c8b4bb66 Desktop: Resolves #9450: Make the beta markdown editor the default (#10796) 2024-08-02 14:47:26 +01:00
renovate[bot]
ff6d700499 Update dependency @types/nodemailer to v6.4.15 (#10808) 2024-08-01 05:52:33 +00:00
Joplin Bot
fc1699ac91 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-08-01 00:47:00 +00:00
Henry Heino
596bcd8d8b Desktop: Accessibility: Improve focus handling for plugin and prompt dialogs (#10801) 2024-07-31 14:10:58 +01:00
Pavel Koneski
ecc4f3e22a Deskop: Linux: Add option to select installation directory (#10800) 2024-07-31 13:57:04 +01:00
renovate[bot]
c0dc30d0c4 Update dependency @types/markdown-it to v13.0.8 (#10804) 2024-07-31 09:04:19 +00:00
github-actions[bot]
f02af3af3b @BCSharp has signed the CLA in laurent22/joplin#10800 2024-07-30 19:09:01 +00:00
Joplin Bot
8179d3e723 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-30 18:18:57 +00:00
Henry Heino
60e347a782 Android: Fix crash on opening settings on old versions of Android (#10793) 2024-07-29 22:18:16 +01:00
Joplin Bot
06264847cc Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-29 18:19:24 +00:00
Laurent Cozic
3137d5be33 Merge branch 'release-3.0' into dev 2024-07-29 13:47:43 +01:00
Joplin Bot
d4c35b8c0b Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-28 18:18:14 +00:00
Laurent Cozic
39ad1e23a8 iOS 13.0.7 2024-07-28 15:08:29 +01:00
Laurent Cozic
d6dd23e921 Android 3.0.9 2024-07-28 15:06:25 +01:00
Henry Heino
b108bf799d Desktop: Accessibility: Add missing labels and role information to several controls (#10788) 2024-07-28 14:53:32 +01:00
Henry Heino
6d92e982dc Chore: iOS: Fix build by downgrading @react-native-clipboard/clipboard (#10787) 2024-07-28 14:53:17 +01:00
Henry Heino
40bd2dfe21 Desktop: Rich Text Editor: Preserve cursor location when updating editor content (#10781) 2024-07-28 14:49:51 +01:00
Joplin Bot
819de1cfa4 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-28 12:21:12 +00:00
Alice
a6d6e70b3d Chore: Desktop: Seamless-Updates - creation of auto updater service (#10772) 2024-07-28 12:09:30 +01:00
Laurent Cozic
05cf51ec65 Desktop release v3.0.14 2024-07-26 12:44:35 +01:00
Henry Heino
0935b6f697 Desktop: Fixes #10672: External editing: Fix notes often not updated when saved from Vim (#10780) 2024-07-26 12:39:21 +01:00
Henry Heino
dd5240d018 Desktop: Improve focus handling for notebook edit, share, and sync dialogs (#10779) 2024-07-26 12:39:01 +01:00
Henry Heino
3fbb3b6b82 Desktop: Fixes #10768: Make :w trigger sync in the beta editor's Vim mode (#10778) 2024-07-26 12:38:07 +01:00
Henry Heino
77b74daa0e Android: Fix pasting PNG and JPEG images from the clipboard (#10777) 2024-07-26 12:37:55 +01:00
Henry Heino
8c0769fdb3 Chore: Mobile: Add note screen tests (#10766) 2024-07-26 12:35:50 +01:00
Henry Heino
d2028588e8 Desktop: Resolves #10424: Move the note viewer to a separate process (#10678) 2024-07-26 12:22:49 +01:00
Laurent Cozic
4b99c2062c Update dictionary 2024-07-26 08:23:57 +01:00
Joplin Bot
ce0218700e Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-26 00:43:08 +00:00
Laurent Cozic
d63f498f4c Doc: Add sponsor 2024-07-25 22:41:26 +01:00
Laurent Cozic
56d2aced8a Server v3.0.1 2024-07-25 18:18:48 +01:00
Henry Heino
db2a194b69 Merge remote-tracking branch 'origin/release-3.0' into dev 2024-07-25 08:10:48 -07:00
github-actions[bot]
f7a970f466 @Aarya01Patil has signed the CLA in laurent22/joplin#10783 2024-07-25 11:57:59 +00:00
Henry Heino
f7fa7a195f Server: Allow web client sync (#10775) 2024-07-24 23:45:11 +01:00
Laurent Cozic
e6ec27a501 Server: Prevent item size calculation task from failing when a user has been deleted 2024-07-24 19:16:40 +01:00
Henry Heino
331f7ebe5c Mobile: Plugins: Fix incorrect Node exports emulation (#10776) 2024-07-23 20:10:59 +01:00
joe
afcd2d2a39 update zh_TW.po file (#10773) 2024-07-23 12:56:08 +01:00
github-actions[bot]
8129f4a89f @fishpcblog has signed the CLA in laurent22/joplin#10773 2024-07-23 11:08:23 +00:00
github-actions[bot]
72c1bb3724 @BHAV0207 has signed the CLA in laurent22/joplin#10769 2024-07-20 10:59:43 +00:00
cedecode
8fdccd287e Update de_DE.po (#10767) 2024-07-19 21:44:06 +01:00
Joplin Bot
b69a7403bc Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-19 18:19:04 +00:00
Laurent Cozic
bdc9fa9dc3 Doc: Added sponsor 2024-07-19 15:54:41 +01:00
Laurent Cozic
9c07e57e28 lock file 2024-07-18 15:06:47 +01:00
Henry Heino
821daeca94 Chore: Mobile: Add NoteBodyViewer tests (#10747) 2024-07-18 09:44:13 +01:00
ERYpTION
480bf238f6 Update da_DK.po (#10758) 2024-07-18 09:44:00 +01:00
Henry Heino
8ff13e5fc4 Android: Fixes #10681: Fix Dropbox sync for certain device languages (#10759) 2024-07-18 09:43:49 +01:00
Henry Heino
8e1970d08e Desktop: Update bundled Backup plugin to v1.4.2 (#10760)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-07-18 09:43:20 +01:00
Henry Heino
86d92dd302 Chore: Fix indentation in generate-plugin-doc/package.json (#10762)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-07-18 09:43:07 +01:00
Henry Heino
71b466507f Mobile: Upgrade react-native-webview to 13.8.6 to fix CI (#10761) 2024-07-17 22:49:10 +01:00
renovate[bot]
11ce5f6c52 Update dependency nodemailer to v6.9.13 (#10699) 2024-07-17 20:38:53 +00:00
renovate[bot]
630b4061f0 Update dependency pg to v8.11.5 (#10700) 2024-07-17 20:38:51 +00:00
renovate[bot]
912c943114 Update dependency react-native-webview to v13.8.4 (#10702) 2024-07-17 20:38:49 +00:00
renovate[bot]
8e377e0306 Update dependency sharp to v0.33.3 (#10704) 2024-07-17 20:38:13 +00:00
renovate[bot]
1535e020a3 Update dependency style-to-js to v1.1.12 (#10705) 2024-07-17 20:38:11 +00:00
renovate[bot]
23d5d3426d Update dependency tar to v6.2.1 (#10706) 2024-07-17 20:38:04 +00:00
Laurent Cozic
6ab7a0836e Tools: Automerge pull requests with the "automerge" label 2024-07-17 20:51:54 +01:00
Laurent Cozic
278691211d Tools: Automerge pull requests with the "automerge" label 2024-07-17 20:06:26 +01:00
Laurent Cozic
356d4688a0 Tools: Automerge pull requests with the "automerge" label 2024-07-17 19:52:52 +01:00
renovate[bot]
6b1d31387b Update dependency @react-native-community/geolocation to v3.2.1 (#10757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 18:56:46 +00:00
pedr
70bfb26c9a Mobile: Fixes: #10677: Following a link to a previously open note wouldn't work (#10750) 2024-07-16 19:38:54 +01:00
Henry Heino
71f70f4d2c Mobile: Resolves #9017: Support pasting images (#10751) 2024-07-16 19:28:05 +01:00
Henry Heino
64e4ebb1f3 Mobile: Fix manual resource download mode (#10748) 2024-07-16 19:27:08 +01:00
Liffindra Angga Zaaldian
2d984ce9a8 Update Indonesian translation (#10741) 2024-07-16 19:25:54 +01:00
Henry Heino
eaf160e0b1 Docs: Update user-facing plugin documentation to reflect that plugins are now supported on mobile (#10738) 2024-07-16 19:25:38 +01:00
Henry Heino
624bfd9175 Desktop: Fixes #10733: Fix not-yet-created images lost while editing with the Rich Text Editor (#10734) 2024-07-16 19:25:23 +01:00
Henry Heino
9ad1249f11 Chore: Mobile: Migrate shim-init-react to TypeScript (#10731) 2024-07-16 19:23:03 +01:00
sysescool
668849603d Desktop: Fixes #10716: fix joplin install fails because ldconfig not found libfuse2 but it is indeed installed. (#10717) 2024-07-16 19:20:51 +01:00
renovate[bot]
24f4c8e6ab Update dependency terminal-kit to v3.1.1 (#10713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:03:33 +01:00
renovate[bot]
46f5784edc Update dependency react-native-get-random-values to v1.11.0 (#10712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:49 +01:00
renovate[bot]
fae2443481 Update dependency react-native-device-info to v10.13.1 (#10711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:40 +01:00
renovate[bot]
37d65e000a Update dependency jsdom to v23.2.0 (#10709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:17 +01:00
renovate[bot]
6dd90eb03f Update dependency @react-native-community/geolocation to v3.2.0 (#10708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 19:01:07 +01:00
CUI Hao
3d8f713eb7 Chore: Add a sleep in the note duplication test (#10719)
Fixes a test failure on very fast computers.
2024-07-15 07:33:59 -07:00
renovate[bot]
c35efe15d2 Update dependency @react-native-community/slider to v4.5.2 (#10744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-15 02:46:52 +00:00
Henry Heino
1596b46b86 Chore: Fixes #10721: Fix test failure in CI (#10735) 2024-07-11 12:59:56 -07:00
Henry Heino
4de0236194 Chore: Docs: Fix missing closing tag (#10730)
The video element is not a void element. As such, if it's missing a closing tag, it's considered to be unclosed.
See https://developer.mozilla.org/en-US/docs/Glossary/Void_element#self-closing_tags.
2024-07-11 10:16:33 -07:00
Jeremy Kao
2ab9702e32 Docs: Update the UI path for sync status on the mobile app (#10718)
The instructions for how to view synchronisation status on mobile were out of date.
2024-07-11 07:44:09 -07:00
github-actions[bot]
24954bd0f0 @shubhiscoding has signed the CLA in laurent22/joplin#10728 2024-07-09 11:06:20 +00:00
renovate[bot]
2d4322be56 Update dependency @types/node to v18.19.31 (#10727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 10:37:55 +00:00
renovate[bot]
abb069bf50 Update dependency @react-native-community/slider to v4.5.1 (#10726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 10:35:50 +00:00
renovate[bot]
a81d9fe17a Update dependency koa to v2.15.2 (#10698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 08:11:32 +00:00
renovate[bot]
6d44158050 Update dependency glob to v10.3.12 (#10697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 08:10:19 +00:00
Dmitriy Q
a63cf3a90d All: Translation: Update ru_RU.po (#10720) 2024-07-09 04:08:49 -04:00
github-actions[bot]
ddb4f8c45b @cuihaoleo has signed the CLA in laurent22/joplin#10719 2024-07-08 04:28:43 +00:00
github-actions[bot]
d7adab59ef @imsardine has signed the CLA in laurent22/joplin#10718 2024-07-08 03:49:24 +00:00
github-actions[bot]
e41374496e @sysescool has signed the CLA in laurent22/joplin#10717 2024-07-08 03:19:52 +00:00
Arda Kılıçdağı
62d514463c Turkish Translations updated (#10692) 2024-07-06 18:26:25 +02:00
jduar
332078b4ea Update Portuguese pt_PT.po translation. (#10691) 2024-07-06 18:26:13 +02:00
github-actions[bot]
c60e11646d @jduar has signed the CLA in laurent22/joplin#10691 2024-07-06 14:06:06 +00:00
Joplin Bot
c607fe9c75 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-06 12:20:41 +00:00
Laurent Cozic
1a4ba2c74a Merge branch 'release-3.0' into dev 2024-07-06 13:41:53 +02:00
Laurent Cozic
e49bca8315 iOS 13.0.6 2024-07-06 13:23:17 +02:00
Laurent Cozic
636fbdf7d0 Android 3.0.8 2024-07-06 13:21:55 +02:00
Laurent Cozic
ee97434bb0 Tools: Skip more minor changes for changelog generation 2024-07-06 12:19:17 +02:00
Laurent Cozic
599cf5b86f Desktop release v3.0.13 2024-07-06 12:12:46 +02:00
Henry Heino
2fd6a3a2fa Desktop: Fixes #10685: Fix shift-delete asks to permanently delete the current note, rather than cut text, when the editor is selected. (#10687) 2024-07-06 12:05:35 +02:00
Henry Heino
a3e04103de Desktop: Fixes #10679: Fix incorrect text rendering on MacOS by changing the default font to Avenir Next (#10686) 2024-07-05 19:58:09 +02:00
renovate[bot]
731260926d Update dependency @types/node to v18.19.30 (#10684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-05 13:49:54 +02:00
ben-igel
a43635610a Update de_DE.po to make the german translation of the sort lines command better understandable (#10682) 2024-07-05 13:18:58 +02:00
github-actions[bot]
e307459652 @ben-igel has signed the CLA in laurent22/joplin#10682 2024-07-04 17:51:49 +00:00
Henry Heino
c197a83de8 Desktop: Fix error in plugin content scripts generated with Webpack (#10680) 2024-07-04 14:58:26 +02:00
Henry Heino
320d0df60d Desktop, Mobile: Fixes #10674: Fix sidebar performance regression with many nested notebooks (#10676) 2024-07-04 14:56:57 +02:00
Joplin Bot
7e4533d811 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-03 12:22:19 +00:00
cedecode
f32fe63205 All: Update de_DE.po (small improvements) (#10673) 2024-07-03 13:53:32 +02:00
Laurent Cozic
be117bca86 Tools: Run packageJsonLint hook only when JSON files are being committed 2024-07-03 10:54:13 +02:00
Laurent Cozic
2b7bd902f3 Merge branch 'release-3.0' into dev 2024-07-03 10:47:27 +02:00
Laurent Cozic
3e0fb48e44 Api: Do not return deleted notes in folders/:id/notes call 2024-07-03 10:41:31 +02:00
Laurent Cozic
6d7fd19167 Chore: Add word to dic 2024-07-03 00:52:08 +02:00
Laurent Cozic
c3520d9eb1 CLI v3.0.1 2024-07-02 21:00:43 +02:00
Laurent Cozic
5fd3cecc96 Lock file 2024-07-02 21:00:43 +02:00
Laurent Cozic
0d8666c946 Releasing sub-packages 2024-07-02 21:00:42 +02:00
Laurent Cozic
4a475f1b53 CLI v3.0.1 2024-07-02 20:58:38 +02:00
Laurent Cozic
8679cc5704 Lock file 2024-07-02 20:41:46 +02:00
Laurent Cozic
a48c4ba93f Releasing sub-packages 2024-07-02 20:40:42 +02:00
Joplin Bot
12db667128 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-02 18:18:51 +00:00
Laurent Cozic
6215de6080 Doc: Added new sponsor 2024-07-02 19:16:15 +02:00
renovate[bot]
7d2f384475 Update dependency @types/node to v18.19.29 (#10671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 19:13:33 +02:00
Laurent Cozic
6ea1ac09a4 Doc: Fixed news filename 2024-07-02 15:17:20 +02:00
Laurent Cozic
f2841a9a94 Tools: Add script to validate Markdown filenames on commit 2024-07-02 15:16:16 +02:00
Joplin Bot
46ade2e0f8 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-01 18:19:29 +00:00
Henry Heino
d89be23069 Chore: Migrate SQL queries in preparation for web support (#10670) 2024-07-01 19:56:40 +02:00
Laurent Cozic
337d50437b Doc: Add release notes 3.0 2024-07-01 18:16:08 +02:00
Laurent Cozic
2479a8471e Merge branch 'release-3.0' into dev 2024-07-01 18:02:27 +02:00
Laurent Cozic
16e82b5462 iOS 13.0.5 2024-07-01 17:48:06 +02:00
Laurent Cozic
1a82255865 Android 3.0.7 2024-07-01 17:47:33 +02:00
Laurent Cozic
a64d6e3270 Desktop release v3.0.12 2024-07-01 17:40:41 +02:00
Laurent Cozic
ae1620dd50 Update translations 2024-07-01 17:39:00 +02:00
rnbastos
d804e95d06 Update pt_BR.po (#10667) 2024-07-01 17:33:36 +02:00
rnbastos
6c091910cd Update pt_BR.po (#10667) 2024-07-01 17:32:43 +02:00
pedr
78d9a7e636 Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore (#10649) 2024-07-01 17:31:58 +02:00
pedr
a074532497 Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore (#10649) 2024-07-01 17:21:17 +02:00
Joplin Bot
5d2df358ac Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-07-01 00:47:08 +00:00
Laurent Cozic
dfdc2fda27 Fixed tests 2024-06-29 18:38:18 +02:00
Laurent Cozic
a1f9c9c3d8 All: Set min version for synchronising to 3.0.0 2024-06-29 18:38:18 +02:00
Laurent Cozic
3270122419 Fixed tests 2024-06-29 18:36:43 +02:00
Joplin Bot
838da6f161 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-29 12:22:44 +00:00
Laurent Cozic
e4b8976aa0 All: Set min version for synchronising to 3.0.0 2024-06-29 12:29:41 +02:00
Laurent Cozic
a86ee1d34e Merge branch 'release-3.0' into dev 2024-06-29 12:25:11 +02:00
Laurent Cozic
17e1eecb11 iOS 13.0.4 2024-06-29 12:21:51 +02:00
Laurent Cozic
dd9a389711 Android 3.0.6 2024-06-29 12:20:36 +02:00
Laurent Cozic
55eaedb8b2 Desktop release v3.0.11 2024-06-29 11:34:25 +02:00
Laurent Cozic
5f34a1bc92 Doc: Add Multi-factor authentication guide 2024-06-28 19:43:59 +02:00
renovate[bot]
f781face3a Update dependency @types/node to v18.19.28 (#10663)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-28 13:03:05 +02:00
Laurent Cozic
78ecd28d73 Merge branch 'release-3.0' into dev 2024-06-27 11:54:19 +02:00
cedecode
f8d772de87 All: Update de_DE.po (#10660) 2024-06-27 11:43:26 +02:00
qx100
77c39ac084 Translate Update zh_CN.po (#10659) 2024-06-26 18:39:32 +02:00
github-actions[bot]
85e57a3953 @cedecode has signed the CLA in laurent22/joplin#10658 2024-06-25 22:11:34 +00:00
qx100
95968f6690 All: Update zh_CN.po (#10651) 2024-06-25 15:05:06 +02:00
Henry Heino
f0b73ee916 Docs: Document creating and managing to-do notes (#10563) 2024-06-25 15:01:54 +02:00
Henry Heino
a44412ae78 Chore: Increase strength of Settings types (#10605) 2024-06-25 15:01:39 +02:00
Henry Heino
c7116b135f Chore: Refactor mobile plugin logic into locations more consistent with other parts of the app (#10636) 2024-06-25 14:59:59 +02:00
Siddhant Paritosh Rao
801d36c41f Mobile: Fixes #10596: remove search bar from plugins screen (#10648) 2024-06-25 14:59:41 +02:00
Henry Heino
1d46adf801 Mobile: Fix dayjs-related startup error (#10652) 2024-06-24 17:15:57 +02:00
Laurent Cozic
94edaea210 Doc: Removed outdated hot reload information 2024-06-23 09:36:56 +01:00
Henry Heino
3557138c84 iOS: Fix voice typing URL setting incorrectly visible (#10643) 2024-06-21 17:16:25 +01:00
Henry Heino
77e74112ad Mobile: Fixes #10637: Fix refocusing the note editor (#10644) 2024-06-21 17:16:12 +01:00
Henry Heino
5db88995c0 Chore: Fix CI (#10646) 2024-06-21 09:37:25 +01:00
Laurent Cozic
8eda8d3c84 Fixed test 2024-06-20 18:25:51 +01:00
Laurent Cozic
1437dd5f27 Desktop: Use relative time in note list for today and yesterday 2024-06-20 14:01:13 +01:00
Laurent Cozic
9eb4944614 Server: Remove USERS_WITH_REPLICATION env variable 2024-06-19 23:34:00 +01:00
Joplin Bot
b4ef5abb88 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-19 18:18:05 +00:00
Laurent Cozic
4115e2054f lock file 2024-06-19 16:33:21 +01:00
Laurent Cozic
8485277dcf iOS 13.0.3 2024-06-19 16:29:43 +01:00
Laurent Cozic
fac9ea3b42 Android 3.0.5 2024-06-19 16:28:45 +01:00
Laurent Cozic
45f8e27d6a Desktop release v3.0.10 2024-06-19 12:38:09 +01:00
Laurent Cozic
c8a478d970 Update translations 2024-06-19 12:37:57 +01:00
pedr
75dfb0af5f Mobile: Fixes #10252 Not able to change notebook when using 'New Note' quick action (#10588) 2024-06-19 12:33:22 +01:00
Mohammad Ashouri
5e592a3096 All: Update farsi/persian translation fa.po (#10634) 2024-06-19 11:00:31 +01:00
Laurent Cozic
84e46ad874 Updating farsi/persian translation (by mimeyn) 2024-06-19 10:59:51 +01:00
XPhyro
0ec917bb96 All: Don't render empty title page for Fountain (#10631) 2024-06-19 10:55:05 +01:00
Henry Heino
818f9f58d1 Desktop: Fixes #10061: Fix paste adds newlines in the Rich Text Editor when certain plugins are enabled (#10635) 2024-06-19 10:54:34 +01:00
renovate[bot]
e1abe0b4cb Update contributor-assistant/github-action action to v2.3.2 (#10633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 10:46:09 +01:00
Henry Heino
c972ce223e Mobile: Fixes #10593: Fix plugin list not cached in config screen (#10599)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-18 10:02:42 +01:00
Henry Heino
d9dadf28cb Mobile: Resolves #10594: Move mobile plugin setting tabs under a separate section (#10600) 2024-06-18 10:02:11 +01:00
Henry Heino
1fb392ff4e Mobile: Fix cmd-i no longer italicizes text (#10604) 2024-06-18 10:02:01 +01:00
pedr
affa620983 Desktop: Fixes: #10030: Prevent application from crashing when the syncInfoCache is corrupted (#10546) 2024-06-18 10:01:35 +01:00
Henry Heino
ed31d8202b Mobile: Fixes #10589: Fix selected note changes on moving to a different folder (#10630) 2024-06-18 09:59:08 +01:00
renovate[bot]
573ea6051c Update dependency @react-native-community/netinfo to v11.3.1 (#10632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 23:20:07 +01:00
renovate[bot]
f1ec54532f Update dependency jsdom to v23 (#10628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:25:40 +01:00
renovate[bot]
5eb96d71e1 Update dependency sass to v1.71.0 (#10627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:56 +01:00
renovate[bot]
73251bac4a Update dependency react-native-webview to v13.8.1 (#10626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:50 +01:00
renovate[bot]
f40a0da195 Update dependency css-loader to v6.10.0 (#10625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:34 +01:00
Laurent Cozic
8dc1ab2cc5 Update renovate.json5 2024-06-17 21:23:55 +01:00
renovate[bot]
1b46c9f5e7 Update dependency chokidar to v3.6.0 (#10623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:23:07 +01:00
renovate[bot]
483ab55a36 Update dependency @react-native-community/netinfo to v11.3.0 (#10622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:56 +01:00
renovate[bot]
502002f9f6 Update dependency style-to-js to v1.1.11 (#10619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:16 +01:00
renovate[bot]
8d8014511f Update dependency turndown to v7.1.3 (#10620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:09 +01:00
renovate[bot]
28569e652e Update dependency react-native-image-picker to v7.1.1 (#10618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:54 +01:00
renovate[bot]
9acf36d802 Update dependency nodemailer to v6.9.12 (#10617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:45 +01:00
renovate[bot]
06d26767ed Update dependency koa to v2.15.1 (#10615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:26 +01:00
renovate[bot]
88858d4413 Update dependency follow-redirects to v1.15.6 (#10614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:13 +01:00
github-actions[bot]
27309427a1 @XPhyro has signed the CLA in laurent22/joplin#10631 2024-06-17 19:50:09 +00:00
renovate[bot]
e83a18a907 Update dependency @types/node to v18.19.26 (#10613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 14:25:46 +00:00
ERYpTION
9bd8b11f67 Update da_DK.po (#10606) 2024-06-17 14:50:34 +01:00
renovate[bot]
3a14c7ce2d Update dependency @types/node to v18.19.25 (#10610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 09:13:03 +01:00
github-actions[bot]
fe4c9a2401 @kohi9noor has signed the CLA in laurent22/joplin#10607 2024-06-16 08:42:17 +00:00
Arda Kılıçdağı
d2fb19cf6d All: Translation: Update tr_TR.po (#10603) 2024-06-15 14:35:15 -04:00
Laurent Cozic
1f8e3fb620 Update translations 2024-06-15 10:17:54 +01:00
Laurent Cozic
8bbe1d30b4 Update French translation 2024-06-15 10:16:16 +01:00
Laurent Cozic
ec92f716de Chore: Removed unncessary string 2024-06-15 10:15:22 +01:00
Laurent Cozic
ab819d9210 Update translations 2024-06-15 10:06:44 +01:00
Henry Heino
e465b45d6e Mobile: Resolves #10592: Make mobile plugin settings screen UI closer to desktop (#10598) 2024-06-15 10:00:21 +01:00
tiberiusteng
a4a4170d49 Mobile: Resolves #8639: implement callback url (#9803)
Co-authored-by: Tib Teng <661892+tiberiusteng@users.noreply.github.com>
2024-06-15 09:58:23 +01:00
Henry Heino
1dcf528443 Chore: Refactor editor package: Move functions in editorStateUtils into separate files (#10591) 2024-06-14 19:40:07 +01:00
Henry Heino
8cf4ef88b5 Desktop: Fixes #10586: Don't re-order the note list when in search (#10587) 2024-06-14 19:39:26 +01:00
Henry Heino
bf634270be Mobile: Mark plugin support as in beta (#10585) 2024-06-14 19:38:50 +01:00
Henry Heino
56437d3e1b Mobile: Don't show an "expand" arrow by "Installed plugins" when no plugins are installed (#10583)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:38:16 +01:00
Henry Heino
d095ab2be7 All: Fixes #10581: English: Use the plural form of a localization for negative and zero items (#10582)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:37:26 +01:00
Henry Heino
4751b4dd74 Mobile: Plugin settings screen: Fix plugin states not set correctly when installing multiple plugins at once (#10580)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:36:44 +01:00
Henry Heino
ce22d8238c Mobile: Plugin settings: Fix plugins without settings can't be disabled without reinstall (#10579)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:36:26 +01:00
pedr
9e2b9e5b8d Desktop: Fixes error when Joplin Cloud login is finished before the settings are saved (#10575) 2024-06-14 19:31:50 +01:00
pedr
4952980e0a Desktop, Mobile: Start synchronisation just after login is complete (#10574) 2024-06-14 14:24:35 +01:00
Joplin Bot
59989d2735 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-13 12:22:09 +00:00
Laurent Cozic
eb7f2855b0 Chore: Trying to fix Android build on CI by using debug keys 2024-06-13 09:01:45 +01:00
746 changed files with 125121 additions and 34676 deletions

View File

@@ -51,8 +51,10 @@ packages/app-desktop/node_modules
packages/app-desktop/packageInfo.js
packages/app-desktop/services/electron-context-menu.js
packages/app-desktop/vendor/lib/
packages/app-mobile/packageInfo.js
packages/app-mobile/android
packages/app-mobile/**/*.bundle.js
packages/app-mobile/web/public/pluginAssets/**/*
packages/app-mobile/ios
packages/app-mobile/lib/rnInjectedJs/
packages/app-mobile/locales
@@ -167,9 +169,13 @@ packages/app-desktop/gui/Button/Button.js
packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js
packages/app-desktop/gui/ConfigScreen/ConfigScreen.js
packages/app-desktop/gui/ConfigScreen/FontSearch.js
packages/app-desktop/gui/ConfigScreen/Sidebar.js
packages/app-desktop/gui/ConfigScreen/controls/FontSearch.js
packages/app-desktop/gui/ConfigScreen/controls/MissingPasswordHelpLink.js
packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.js
packages/app-desktop/gui/ConfigScreen/controls/SettingDescription.js
packages/app-desktop/gui/ConfigScreen/controls/SettingHeader.js
packages/app-desktop/gui/ConfigScreen/controls/SettingLabel.js
packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
@@ -291,11 +297,15 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js
packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/index.js
@@ -332,8 +342,10 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.js
packages/app-desktop/gui/NoteList/utils/types.js
packages/app-desktop/gui/NoteList/utils/useActiveDescendantId.js
packages/app-desktop/gui/NoteList/utils/useDragAndDrop.js
packages/app-desktop/gui/NoteList/utils/useFocusNote.js
packages/app-desktop/gui/NoteList/utils/useFocusVisible.js
packages/app-desktop/gui/NoteList/utils/useItemCss.js
packages/app-desktop/gui/NoteList/utils/useMoveNote.js
packages/app-desktop/gui/NoteList/utils/useOnKeyDown.js
@@ -354,6 +366,7 @@ packages/app-desktop/gui/NoteListHeader/utils/useContextMenu.js
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.js
packages/app-desktop/gui/NoteListItem/NoteListItem.js
packages/app-desktop/gui/NoteListItem/utils/getNoteElementIdFromJoplinId.js
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.test.js
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.js
@@ -432,6 +445,7 @@ packages/app-desktop/gui/ToolbarButton/ToolbarButton.js
packages/app-desktop/gui/ToolbarButton/styles/index.js
packages/app-desktop/gui/ToolbarSpace.js
packages/app-desktop/gui/TrashNotification/TrashNotification.js
packages/app-desktop/gui/UpdateNotification/UpdateNotification.js
packages/app-desktop/gui/dialogs.js
packages/app-desktop/gui/hooks/useEffectDebugger.js
packages/app-desktop/gui/hooks/useElementHeight.js
@@ -447,28 +461,38 @@ packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/utils/NoteListUtils.js
packages/app-desktop/gui/utils/announceForAccessibility.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
packages/app-desktop/gui/utils/dragAndDrop.js
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gulpfile.js
packages/app-desktop/integration-tests/goToAnything.spec.js
packages/app-desktop/integration-tests/main.spec.js
packages/app-desktop/integration-tests/markdownEditor.spec.js
packages/app-desktop/integration-tests/models/GoToAnything.js
packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
packages/app-desktop/integration-tests/models/NoteList.js
packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/models/Sidebar.js
packages/app-desktop/integration-tests/noteList.spec.js
packages/app-desktop/integration-tests/richTextEditor.spec.js
packages/app-desktop/integration-tests/settings.spec.js
packages/app-desktop/integration-tests/sidebar.spec.js
packages/app-desktop/integration-tests/simpleBackup.spec.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/extendedExpect.js
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/getImageSourceSize.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.js
packages/app-desktop/services/bridge.js
packages/app-desktop/services/commands/stateToWhenClauseContext.js
packages/app-desktop/services/commands/types.js
@@ -500,6 +524,10 @@ packages/app-desktop/utils/7zip/pathToBundled7Zip.js
packages/app-desktop/utils/checkForUpdatesUtils.test.js
packages/app-desktop/utils/checkForUpdatesUtils.js
packages/app-desktop/utils/checkForUpdatesUtilsTestData.js
packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/markupLanguageUtils.js
@@ -513,18 +541,24 @@ packages/app-mobile/commands/openItem.js
packages/app-mobile/commands/openNote.js
packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/components/ActionButton.js
packages/app-mobile/commands/util/showResource.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
packages/app-mobile/components/CameraView.js
packages/app-mobile/components/DialogManager.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
packages/app-mobile/components/ExtendedWebView/types.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
@@ -581,20 +615,44 @@ packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/ScreenHeader/WarningBanner.test.js
packages/app-mobile/components/ScreenHeader/WarningBanner.js
packages/app-mobile/components/ScreenHeader/WarningBox.js
packages/app-mobile/components/ScreenHeader/WebBetaButton.js
packages/app-mobile/components/ScreenHeader/index.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/accessibility/AccessibleModalMenu.js
packages/app-mobile/components/accessibility/AccessibleView.js
packages/app-mobile/components/app-nav.js
packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/buttons/FloatingActionButton.js
packages/app-mobile/components/buttons/TextButton.js
packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/components/plugins/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/components/plugins/backgroundPage/startStopPlugin.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.test.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.js
packages/app-mobile/components/plugins/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/components/plugins/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/components/plugins/dialogs/PluginDialogManager.js
packages/app-mobile/components/plugins/dialogs/PluginDialogWebView.js
packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.js
packages/app-mobile/components/plugins/dialogs/PluginUserWebView.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogSize.js
packages/app-mobile/components/plugins/dialogs/hooks/useViewInfos.js
packages/app-mobile/components/plugins/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/components/plugins/types.js
packages/app-mobile/components/plugins/utils/createOnLogHandler.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js
@@ -610,7 +668,8 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js
packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
@@ -618,9 +677,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
@@ -628,6 +688,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
@@ -642,6 +703,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useUpdateState
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note.test.js
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/NoteTagsDialog.js
packages/app-mobile/components/screens/Notes.js
@@ -656,44 +718,22 @@ packages/app-mobile/components/screens/status.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
packages/app-mobile/gulpfile.js
packages/app-mobile/plugins/PlatformImplementation.js
packages/app-mobile/plugins/PluginRunner/PluginRunner.js
packages/app-mobile/plugins/PluginRunner/PluginRunnerWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializeDialogWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/startStopPlugin.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.test.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/makeSandboxedIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogManager.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginPanelViewer.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginUserWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogSize.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useViewInfos.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/plugins/PluginRunner/types.js
packages/app-mobile/plugins/PluginRunner/utils/createOnLogHandler.js
packages/app-mobile/plugins/hooks/usePlugin.js
packages/app-mobile/plugins/loadPlugins.test.js
packages/app-mobile/plugins/loadPlugins.js
packages/app-mobile/plugins/testing/MockPluginRunner.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/AlarmServiceDriver.web.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/plugins/PlatformImplementation.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
packages/app-mobile/tools/buildInjectedJs/copyJs.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
packages/app-mobile/utils/ShareUtils.test.js
packages/app-mobile/utils/ShareUtils.js
@@ -702,9 +742,13 @@ packages/app-mobile/utils/appDefaultState.js
packages/app-mobile/utils/autodetectTheme.js
packages/app-mobile/utils/checkPermissions.js
packages/app-mobile/utils/createRootStyle.js
packages/app-mobile/utils/database-driver-react-native.js
packages/app-mobile/utils/database-driver-react-native.web.js
packages/app-mobile/utils/debounce.js
packages/app-mobile/utils/fs-driver/constants.js
packages/app-mobile/utils/fs-driver/fs-driver-rn.js
packages/app-mobile/utils/fs-driver/fs-driver-rn.web.js
packages/app-mobile/utils/fs-driver/fs-driver-rn.web.worker.js
packages/app-mobile/utils/fs-driver/runOnDeviceTests.js
packages/app-mobile/utils/fs-driver/tarCreate.js
packages/app-mobile/utils/fs-driver/tarExtract.test.js
@@ -713,17 +757,29 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/lockToSingleInstance.js
packages/app-mobile/utils/makeShowMessageBox.js
packages/app-mobile/utils/pickDocument.js
packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareFile.js
packages/app-mobile/utils/shareHandler.js
packages/app-mobile/utils/showMessageBox.js
packages/app-mobile/utils/shim-init-react/index.js
packages/app-mobile/utils/shim-init-react/index.web.js
packages/app-mobile/utils/shim-init-react/injectedJs.js
packages/app-mobile/utils/shim-init-react/shimInitShared.js
packages/app-mobile/utils/testing/createMockReduxStore.js
packages/app-mobile/utils/testing/getWebViewDomById.js
packages/app-mobile/utils/types.js
packages/app-mobile/web/serviceWorker.js
packages/default-plugins/build.js
packages/default-plugins/buildDefaultPlugins.js
packages/default-plugins/commands/buildAll.js
@@ -749,8 +805,6 @@ packages/editor/CodeMirror/editorCommands/insertLineAfter.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/editorCommands/swapLine.test.js
packages/editor/CodeMirror/editorCommands/swapLine.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/defaultLanguage.js
@@ -765,6 +819,9 @@ packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
@@ -777,10 +834,27 @@ packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/util/editorStateUtils.test.js
packages/editor/CodeMirror/util/editorStateUtils.js
packages/editor/CodeMirror/util/isInSyntaxNode.js
packages/editor/CodeMirror/util/setupVim.js
packages/editor/CodeMirror/utils/biDirectionalTextExtension.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
packages/editor/CodeMirror/utils/formatting/types.js
packages/editor/CodeMirror/utils/getSearchState.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/searchExtension.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/types.js
@@ -879,6 +953,7 @@ packages/lib/geolocation-node.js
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useEventListener.js
packages/lib/hooks/usePlugin.js
packages/lib/hooks/usePrevious.js
packages/lib/htmlUtils.test.js
packages/lib/htmlUtils.js
@@ -925,8 +1000,10 @@ packages/lib/models/Tag.test.js
packages/lib/models/Tag.js
packages/lib/models/dateTimeFormats.test.js
packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/builtInMetadata.js
packages/lib/models/settings/settingValidations.test.js
packages/lib/models/settings/settingValidations.js
packages/lib/models/settings/types.js
packages/lib/models/utils/getCollator.js
packages/lib/models/utils/getConflictFolderId.js
packages/lib/models/utils/isItemId.js
@@ -946,6 +1023,7 @@ packages/lib/ntp.js
packages/lib/onedrive-api.test.js
packages/lib/onedrive-api.js
packages/lib/path-utils.js
packages/lib/reducer.test.js
packages/lib/reducer.js
packages/lib/registry.test.js
packages/lib/registry.js
@@ -984,6 +1062,7 @@ packages/lib/services/commands/commandsToMarkdownTable.js
packages/lib/services/commands/focusEditorIfEditorCommand.js
packages/lib/services/commands/isEditorCommand.js
packages/lib/services/commands/propsHaveChanged.js
packages/lib/services/commands/stateToWhenClauseContext.test.js
packages/lib/services/commands/stateToWhenClauseContext.js
packages/lib/services/contextkey/contextkey.js
packages/lib/services/database/addMigrationFile.js
@@ -1035,8 +1114,10 @@ packages/lib/services/interop/Module.js
packages/lib/services/interop/types.js
packages/lib/services/joplinCloudUtils.js
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
packages/lib/services/keychain/KeychainService.test.js
packages/lib/services/keychain/KeychainService.js
packages/lib/services/keychain/KeychainServiceDriver.dummy.js
packages/lib/services/keychain/KeychainServiceDriver.electron.js
packages/lib/services/keychain/KeychainServiceDriver.mobile.js
packages/lib/services/keychain/KeychainServiceDriver.node.js
packages/lib/services/keychain/KeychainServiceDriverBase.js
@@ -1090,7 +1171,11 @@ packages/lib/services/plugins/api/noteListType.js
packages/lib/services/plugins/api/types.js
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js
packages/lib/services/plugins/loadPlugins.test.js
packages/lib/services/plugins/loadPlugins.js
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/testing/MockPlatformImplementation.js
packages/lib/services/plugins/testing/MockPluginRunner.js
packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
@@ -1232,13 +1317,17 @@ packages/lib/urlUtils.js
packages/lib/utils/ActionLogger.test.js
packages/lib/utils/ActionLogger.js
packages/lib/utils/credentialFiles.js
packages/lib/utils/dom/makeSandboxedIframe.js
packages/lib/utils/focusHandler.js
packages/lib/utils/frontMatter.js
packages/lib/utils/ipc/RemoteMessenger.test.js
packages/lib/utils/ipc/RemoteMessenger.js
packages/lib/utils/ipc/TestMessenger.js
packages/lib/utils/ipc/WindowMessenger.js
packages/lib/utils/ipc/WorkerMessenger.js
packages/lib/utils/ipc/WorkerToWindowMessenger.js
packages/lib/utils/ipc/types.js
packages/lib/utils/ipc/utils/isTransferableObject.js
packages/lib/utils/ipc/utils/mergeCallbacksAndSerializable.test.js
packages/lib/utils/ipc/utils/mergeCallbacksAndSerializable.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializable.test.js
@@ -1249,6 +1338,8 @@ packages/lib/utils/joplinCloud/types.js
packages/lib/utils/processStartFlags.js
packages/lib/utils/replaceUnsupportedCharacters.test.js
packages/lib/utils/replaceUnsupportedCharacters.js
packages/lib/utils/resolvePathWithinDir.test.js
packages/lib/utils/resolvePathWithinDir.js
packages/lib/utils/userFetcher.js
packages/lib/utils/webDAVUtils.test.js
packages/lib/utils/webDAVUtils.js
@@ -1344,7 +1435,23 @@ packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.test.js
packages/tools/git-changelog.js
packages/tools/licenseChecker.js
packages/tools/licenses/getLicenses.js
packages/tools/licenses/licenseChecker.js
packages/tools/licenses/licenseOverrides/fontAwesomeOverride/index.js
packages/tools/licenses/licenseOverrides/fontIsoOverride/index.js
packages/tools/licenses/licenseOverrides/index.js
packages/tools/licenses/licenseOverrides/materialCommunityIconsOverride/index.js
packages/tools/licenses/licenseOverrides/materialIconsOverride/index.js
packages/tools/licenses/licenseOverrides/types.js
packages/tools/licenses/licenseStatementBuilder.js
packages/tools/licenses/licenseText/agplv3.js
packages/tools/licenses/licenseText/apache2.js
packages/tools/licenses/licenseText/mit.js
packages/tools/licenses/utils/equalIgnoringSpacing.js
packages/tools/licenses/utils/matchApache2.test.js
packages/tools/licenses/utils/matchApache2.js
packages/tools/licenses/utils/matchMit.test.js
packages/tools/licenses/utils/matchMit.js
packages/tools/packageJsonLint.js
packages/tools/postPreReleasesToForum.js
packages/tools/release-android.js
@@ -1366,6 +1473,7 @@ packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/translation.js
packages/tools/validateFilenames.js
packages/tools/website/build.js
packages/tools/website/buildTranslations.js
packages/tools/website/processDocs.test.js

View File

@@ -15,6 +15,19 @@ module.exports = {
'globals': {
'Atomics': 'readonly',
'SharedArrayBuffer': 'readonly',
'BufferEncoding': 'readonly',
'AsyncIterable': 'readonly',
'FileSystemFileHandle': 'readonly',
'FileSystemDirectoryHandle': 'readonly',
'ReadableStreamDefaultReader': 'readonly',
'FileSystemCreateWritableOptions': 'readonly',
'FileSystemHandle': 'readonly',
'IDBTransactionMode': 'readonly',
// ServiceWorker
'ExtendableEvent': 'readonly',
'WindowClient': 'readonly',
'FetchEvent': 'readonly',
// Jest variables
'test': 'readonly',

View File

@@ -180,6 +180,10 @@ fi
if [ "$IS_LINUX" == "1" ]; then
echo "Step: Checking for files that should have been ignored..."
# .gitignore and .eslintignore can be modified during yarn install. Reset them
# so that checkIgnoredFiles works.
git restore .gitignore .eslintignore
node packages/tools/checkIgnoredFiles.js
testResult=$?
if [ $testResult -ne 0 ]; then

17
.github/workflows/automerge.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: automerge
on:
schedule:
- cron: '*/10 * * * *'
jobs:
automerge:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- id: automerge
name: automerge
uses: "pascalgn/automerge-action@v0.16.3"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_METHOD: "squash"
LOG: "DEBUG"

View File

@@ -47,5 +47,7 @@ jobs:
- name: Assemble Android Release
run: |
cd packages/app-mobile/android && ./gradlew assembleRelease
cd packages/app-mobile/android
sed -i -- 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/' app/build.gradle
./gradlew assembleRelease

View File

@@ -62,8 +62,10 @@ jobs:
yarn install
cd packages/app-desktop
npm pkg set 'build.mac.artifactName'='${productName}-${version}-${arch}.${ext}'
npm pkg set 'build.mac.target.target'='dmg'
npm pkg set 'build.mac.target.arch[0]'='arm64'
npm pkg set 'build.mac.target[0].target'='dmg'
npm pkg set 'build.mac.target[0].arch[0]'='arm64'
npm pkg set 'build.mac.target[1].target'='zip'
npm pkg set 'build.mac.target[1].arch[0]'='arm64'
if [[ $GIT_TAG_NAME = v* ]]; then
echo "Building and publishing desktop application..."
@@ -81,4 +83,4 @@ jobs:
npm pkg set 'build.mac.identity'=null --json
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn dist --mac --arm64 --publish=never
fi
fi

View File

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

186
.gitignore vendored
View File

@@ -146,9 +146,13 @@ packages/app-desktop/gui/Button/Button.js
packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js
packages/app-desktop/gui/ConfigScreen/ConfigScreen.js
packages/app-desktop/gui/ConfigScreen/FontSearch.js
packages/app-desktop/gui/ConfigScreen/Sidebar.js
packages/app-desktop/gui/ConfigScreen/controls/FontSearch.js
packages/app-desktop/gui/ConfigScreen/controls/MissingPasswordHelpLink.js
packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.js
packages/app-desktop/gui/ConfigScreen/controls/SettingDescription.js
packages/app-desktop/gui/ConfigScreen/controls/SettingHeader.js
packages/app-desktop/gui/ConfigScreen/controls/SettingLabel.js
packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
@@ -270,11 +274,15 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js
packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/index.js
@@ -311,8 +319,10 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.js
packages/app-desktop/gui/NoteList/utils/types.js
packages/app-desktop/gui/NoteList/utils/useActiveDescendantId.js
packages/app-desktop/gui/NoteList/utils/useDragAndDrop.js
packages/app-desktop/gui/NoteList/utils/useFocusNote.js
packages/app-desktop/gui/NoteList/utils/useFocusVisible.js
packages/app-desktop/gui/NoteList/utils/useItemCss.js
packages/app-desktop/gui/NoteList/utils/useMoveNote.js
packages/app-desktop/gui/NoteList/utils/useOnKeyDown.js
@@ -333,6 +343,7 @@ packages/app-desktop/gui/NoteListHeader/utils/useContextMenu.js
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.test.js
packages/app-desktop/gui/NoteListHeader/utils/validateColumns.js
packages/app-desktop/gui/NoteListItem/NoteListItem.js
packages/app-desktop/gui/NoteListItem/utils/getNoteElementIdFromJoplinId.js
packages/app-desktop/gui/NoteListItem/utils/getNoteTitleHtml.js
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.test.js
packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.js
@@ -411,6 +422,7 @@ packages/app-desktop/gui/ToolbarButton/ToolbarButton.js
packages/app-desktop/gui/ToolbarButton/styles/index.js
packages/app-desktop/gui/ToolbarSpace.js
packages/app-desktop/gui/TrashNotification/TrashNotification.js
packages/app-desktop/gui/UpdateNotification/UpdateNotification.js
packages/app-desktop/gui/dialogs.js
packages/app-desktop/gui/hooks/useEffectDebugger.js
packages/app-desktop/gui/hooks/useElementHeight.js
@@ -426,28 +438,38 @@ packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/utils/NoteListUtils.js
packages/app-desktop/gui/utils/announceForAccessibility.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
packages/app-desktop/gui/utils/dragAndDrop.js
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gulpfile.js
packages/app-desktop/integration-tests/goToAnything.spec.js
packages/app-desktop/integration-tests/main.spec.js
packages/app-desktop/integration-tests/markdownEditor.spec.js
packages/app-desktop/integration-tests/models/GoToAnything.js
packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
packages/app-desktop/integration-tests/models/NoteList.js
packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/models/Sidebar.js
packages/app-desktop/integration-tests/noteList.spec.js
packages/app-desktop/integration-tests/richTextEditor.spec.js
packages/app-desktop/integration-tests/settings.spec.js
packages/app-desktop/integration-tests/sidebar.spec.js
packages/app-desktop/integration-tests/simpleBackup.spec.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/extendedExpect.js
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/getImageSourceSize.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.js
packages/app-desktop/services/bridge.js
packages/app-desktop/services/commands/stateToWhenClauseContext.js
packages/app-desktop/services/commands/types.js
@@ -479,6 +501,10 @@ packages/app-desktop/utils/7zip/pathToBundled7Zip.js
packages/app-desktop/utils/checkForUpdatesUtils.test.js
packages/app-desktop/utils/checkForUpdatesUtils.js
packages/app-desktop/utils/checkForUpdatesUtilsTestData.js
packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/markupLanguageUtils.js
@@ -492,18 +518,24 @@ packages/app-mobile/commands/openItem.js
packages/app-mobile/commands/openNote.js
packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/components/ActionButton.js
packages/app-mobile/commands/util/showResource.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
packages/app-mobile/components/CameraView.js
packages/app-mobile/components/DialogManager.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
packages/app-mobile/components/ExtendedWebView/types.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
@@ -560,20 +592,44 @@ packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/ScreenHeader/WarningBanner.test.js
packages/app-mobile/components/ScreenHeader/WarningBanner.js
packages/app-mobile/components/ScreenHeader/WarningBox.js
packages/app-mobile/components/ScreenHeader/WebBetaButton.js
packages/app-mobile/components/ScreenHeader/index.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/accessibility/AccessibleModalMenu.js
packages/app-mobile/components/accessibility/AccessibleView.js
packages/app-mobile/components/app-nav.js
packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/buttons/FloatingActionButton.js
packages/app-mobile/components/buttons/TextButton.js
packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/components/plugins/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/components/plugins/backgroundPage/startStopPlugin.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.test.js
packages/app-mobile/components/plugins/backgroundPage/utils/getFormData.js
packages/app-mobile/components/plugins/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/components/plugins/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/components/plugins/dialogs/PluginDialogManager.js
packages/app-mobile/components/plugins/dialogs/PluginDialogWebView.js
packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.js
packages/app-mobile/components/plugins/dialogs/PluginUserWebView.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/components/plugins/dialogs/hooks/useDialogSize.js
packages/app-mobile/components/plugins/dialogs/hooks/useViewInfos.js
packages/app-mobile/components/plugins/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/components/plugins/types.js
packages/app-mobile/components/plugins/utils/createOnLogHandler.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js
@@ -589,7 +645,8 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js
packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
@@ -597,9 +654,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
@@ -607,6 +665,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
@@ -621,6 +680,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useUpdateState
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note.test.js
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/NoteTagsDialog.js
packages/app-mobile/components/screens/Notes.js
@@ -635,44 +695,22 @@ packages/app-mobile/components/screens/status.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
packages/app-mobile/gulpfile.js
packages/app-mobile/plugins/PlatformImplementation.js
packages/app-mobile/plugins/PluginRunner/PluginRunner.js
packages/app-mobile/plugins/PluginRunner/PluginRunnerWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializeDialogWebView.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/initializePluginBackgroundIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/pluginRunnerBackgroundPage.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/startStopPlugin.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.test.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/getFormData.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/makeSandboxedIframe.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/reportUnhandledErrors.js
packages/app-mobile/plugins/PluginRunner/backgroundPage/utils/wrapConsoleLog.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogManager.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginDialogWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginPanelViewer.js
packages/app-mobile/plugins/PluginRunner/dialogs/PluginUserWebView.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogMessenger.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useDialogSize.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useViewInfos.js
packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/plugins/PluginRunner/types.js
packages/app-mobile/plugins/PluginRunner/utils/createOnLogHandler.js
packages/app-mobile/plugins/hooks/usePlugin.js
packages/app-mobile/plugins/loadPlugins.test.js
packages/app-mobile/plugins/loadPlugins.js
packages/app-mobile/plugins/testing/MockPluginRunner.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/AlarmServiceDriver.web.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/plugins/PlatformImplementation.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
packages/app-mobile/tools/buildInjectedJs/copyJs.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
packages/app-mobile/utils/ShareUtils.test.js
packages/app-mobile/utils/ShareUtils.js
@@ -681,9 +719,13 @@ packages/app-mobile/utils/appDefaultState.js
packages/app-mobile/utils/autodetectTheme.js
packages/app-mobile/utils/checkPermissions.js
packages/app-mobile/utils/createRootStyle.js
packages/app-mobile/utils/database-driver-react-native.js
packages/app-mobile/utils/database-driver-react-native.web.js
packages/app-mobile/utils/debounce.js
packages/app-mobile/utils/fs-driver/constants.js
packages/app-mobile/utils/fs-driver/fs-driver-rn.js
packages/app-mobile/utils/fs-driver/fs-driver-rn.web.js
packages/app-mobile/utils/fs-driver/fs-driver-rn.web.worker.js
packages/app-mobile/utils/fs-driver/runOnDeviceTests.js
packages/app-mobile/utils/fs-driver/tarCreate.js
packages/app-mobile/utils/fs-driver/tarExtract.test.js
@@ -692,17 +734,29 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/lockToSingleInstance.js
packages/app-mobile/utils/makeShowMessageBox.js
packages/app-mobile/utils/pickDocument.js
packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareFile.js
packages/app-mobile/utils/shareHandler.js
packages/app-mobile/utils/showMessageBox.js
packages/app-mobile/utils/shim-init-react/index.js
packages/app-mobile/utils/shim-init-react/index.web.js
packages/app-mobile/utils/shim-init-react/injectedJs.js
packages/app-mobile/utils/shim-init-react/shimInitShared.js
packages/app-mobile/utils/testing/createMockReduxStore.js
packages/app-mobile/utils/testing/getWebViewDomById.js
packages/app-mobile/utils/types.js
packages/app-mobile/web/serviceWorker.js
packages/default-plugins/build.js
packages/default-plugins/buildDefaultPlugins.js
packages/default-plugins/commands/buildAll.js
@@ -728,8 +782,6 @@ packages/editor/CodeMirror/editorCommands/insertLineAfter.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/editorCommands/swapLine.test.js
packages/editor/CodeMirror/editorCommands/swapLine.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/defaultLanguage.js
@@ -744,6 +796,9 @@ packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
@@ -756,10 +811,27 @@ packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/util/editorStateUtils.test.js
packages/editor/CodeMirror/util/editorStateUtils.js
packages/editor/CodeMirror/util/isInSyntaxNode.js
packages/editor/CodeMirror/util/setupVim.js
packages/editor/CodeMirror/utils/biDirectionalTextExtension.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
packages/editor/CodeMirror/utils/formatting/types.js
packages/editor/CodeMirror/utils/getSearchState.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/searchExtension.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/types.js
@@ -858,6 +930,7 @@ packages/lib/geolocation-node.js
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useEventListener.js
packages/lib/hooks/usePlugin.js
packages/lib/hooks/usePrevious.js
packages/lib/htmlUtils.test.js
packages/lib/htmlUtils.js
@@ -904,8 +977,10 @@ packages/lib/models/Tag.test.js
packages/lib/models/Tag.js
packages/lib/models/dateTimeFormats.test.js
packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/builtInMetadata.js
packages/lib/models/settings/settingValidations.test.js
packages/lib/models/settings/settingValidations.js
packages/lib/models/settings/types.js
packages/lib/models/utils/getCollator.js
packages/lib/models/utils/getConflictFolderId.js
packages/lib/models/utils/isItemId.js
@@ -925,6 +1000,7 @@ packages/lib/ntp.js
packages/lib/onedrive-api.test.js
packages/lib/onedrive-api.js
packages/lib/path-utils.js
packages/lib/reducer.test.js
packages/lib/reducer.js
packages/lib/registry.test.js
packages/lib/registry.js
@@ -963,6 +1039,7 @@ packages/lib/services/commands/commandsToMarkdownTable.js
packages/lib/services/commands/focusEditorIfEditorCommand.js
packages/lib/services/commands/isEditorCommand.js
packages/lib/services/commands/propsHaveChanged.js
packages/lib/services/commands/stateToWhenClauseContext.test.js
packages/lib/services/commands/stateToWhenClauseContext.js
packages/lib/services/contextkey/contextkey.js
packages/lib/services/database/addMigrationFile.js
@@ -1014,8 +1091,10 @@ packages/lib/services/interop/Module.js
packages/lib/services/interop/types.js
packages/lib/services/joplinCloudUtils.js
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
packages/lib/services/keychain/KeychainService.test.js
packages/lib/services/keychain/KeychainService.js
packages/lib/services/keychain/KeychainServiceDriver.dummy.js
packages/lib/services/keychain/KeychainServiceDriver.electron.js
packages/lib/services/keychain/KeychainServiceDriver.mobile.js
packages/lib/services/keychain/KeychainServiceDriver.node.js
packages/lib/services/keychain/KeychainServiceDriverBase.js
@@ -1069,7 +1148,11 @@ packages/lib/services/plugins/api/noteListType.js
packages/lib/services/plugins/api/types.js
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js
packages/lib/services/plugins/loadPlugins.test.js
packages/lib/services/plugins/loadPlugins.js
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/testing/MockPlatformImplementation.js
packages/lib/services/plugins/testing/MockPluginRunner.js
packages/lib/services/plugins/utils/createViewHandle.js
packages/lib/services/plugins/utils/executeSandboxCall.js
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
@@ -1211,13 +1294,17 @@ packages/lib/urlUtils.js
packages/lib/utils/ActionLogger.test.js
packages/lib/utils/ActionLogger.js
packages/lib/utils/credentialFiles.js
packages/lib/utils/dom/makeSandboxedIframe.js
packages/lib/utils/focusHandler.js
packages/lib/utils/frontMatter.js
packages/lib/utils/ipc/RemoteMessenger.test.js
packages/lib/utils/ipc/RemoteMessenger.js
packages/lib/utils/ipc/TestMessenger.js
packages/lib/utils/ipc/WindowMessenger.js
packages/lib/utils/ipc/WorkerMessenger.js
packages/lib/utils/ipc/WorkerToWindowMessenger.js
packages/lib/utils/ipc/types.js
packages/lib/utils/ipc/utils/isTransferableObject.js
packages/lib/utils/ipc/utils/mergeCallbacksAndSerializable.test.js
packages/lib/utils/ipc/utils/mergeCallbacksAndSerializable.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializable.test.js
@@ -1228,6 +1315,8 @@ packages/lib/utils/joplinCloud/types.js
packages/lib/utils/processStartFlags.js
packages/lib/utils/replaceUnsupportedCharacters.test.js
packages/lib/utils/replaceUnsupportedCharacters.js
packages/lib/utils/resolvePathWithinDir.test.js
packages/lib/utils/resolvePathWithinDir.js
packages/lib/utils/userFetcher.js
packages/lib/utils/webDAVUtils.test.js
packages/lib/utils/webDAVUtils.js
@@ -1323,7 +1412,23 @@ packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.test.js
packages/tools/git-changelog.js
packages/tools/licenseChecker.js
packages/tools/licenses/getLicenses.js
packages/tools/licenses/licenseChecker.js
packages/tools/licenses/licenseOverrides/fontAwesomeOverride/index.js
packages/tools/licenses/licenseOverrides/fontIsoOverride/index.js
packages/tools/licenses/licenseOverrides/index.js
packages/tools/licenses/licenseOverrides/materialCommunityIconsOverride/index.js
packages/tools/licenses/licenseOverrides/materialIconsOverride/index.js
packages/tools/licenses/licenseOverrides/types.js
packages/tools/licenses/licenseStatementBuilder.js
packages/tools/licenses/licenseText/agplv3.js
packages/tools/licenses/licenseText/apache2.js
packages/tools/licenses/licenseText/mit.js
packages/tools/licenses/utils/equalIgnoringSpacing.js
packages/tools/licenses/utils/matchApache2.test.js
packages/tools/licenses/utils/matchApache2.js
packages/tools/licenses/utils/matchMit.test.js
packages/tools/licenses/utils/matchMit.js
packages/tools/packageJsonLint.js
packages/tools/postPreReleasesToForum.js
packages/tools/release-android.js
@@ -1345,6 +1450,7 @@ packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.js
packages/tools/utils/loadSponsors.js
packages/tools/utils/translation.js
packages/tools/validateFilenames.js
packages/tools/website/build.js
packages/tools/website/buildTranslations.js
packages/tools/website/processDocs.test.js

View File

@@ -0,0 +1,118 @@
# Fixes sync issues caused by locale-sensitive lowercasing
# of HTTP headers.
# See https://github.com/laurent22/joplin/issues/10681
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
index 8ac9e7a855162cefbf99024eb013c8a3b11de1ec..1c639cf9d84821b6ffc132960e2d1c044bedbd48 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
@@ -2,6 +2,7 @@ package com.RNFetchBlob;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
+import java.util.Locale;
class RNFetchBlobConfig {
@@ -33,7 +34,7 @@ class RNFetchBlobConfig {
}
if(options.hasKey("binaryContentTypes"))
this.binaryContentTypes = options.getArray("binaryContentTypes");
- if(this.path != null && path.toLowerCase().contains("?append=true")) {
+ if(this.path != null && path.toLowerCase(Locale.ROOT).contains("?append=true")) {
this.overwrite = false;
}
if(options.hasKey("overwrite"))
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
index a4d70153f41e6c14eec65412b5b59822f1c6750b..d98c439f7b0aeb79afc82ab9f653e9c021086426 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+import java.util.Locale;
class RNFetchBlobFS {
@@ -210,7 +211,7 @@ class RNFetchBlobFS {
return;
}
- switch (encoding.toLowerCase()) {
+ switch (encoding.toLowerCase(Locale.ROOT)) {
case "base64" :
promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
break;
@@ -1050,7 +1051,7 @@ class RNFetchBlobFS {
if(encoding.equalsIgnoreCase("ascii")) {
return data.getBytes(Charset.forName("US-ASCII"));
}
- else if(encoding.toLowerCase().contains("base64")) {
+ else if(encoding.toLowerCase(Locale.ROOT).contains("base64")) {
return Base64.decode(data, Base64.NO_WRAP);
}
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
index a8abd71833879201e3438b2fa51d712a311c4551..b70cc13c004229f69157de5f82ae5ec3abf4358e 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
@@ -49,6 +49,7 @@ import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@@ -300,14 +301,14 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
responseFormat = ResponseFormat.UTF8;
}
else {
- builder.header(key.toLowerCase(), value);
- mheaders.put(key.toLowerCase(), value);
+ builder.header(key.toLowerCase(Locale.ROOT), value);
+ mheaders.put(key.toLowerCase(Locale.ROOT), value);
}
}
}
if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
- String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
+ String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase(Locale.ROOT);
if(rawRequestBodyArray != null) {
requestType = RequestType.Form;
@@ -323,7 +324,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
|| rawRequestBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
requestType = RequestType.SingleFile;
}
- else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) {
+ else if (cType.toLowerCase(Locale.ROOT).contains(";base64") || cType.toLowerCase(Locale.ROOT).startsWith("application/octet")) {
cType = cType.replace(";base64","").replace(";BASE64","");
if(mheaders.containsKey("content-type"))
mheaders.put("content-type", cType);
@@ -686,7 +687,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
boolean isCustomBinary = false;
if(options.binaryContentTypes != null) {
for(int i = 0; i< options.binaryContentTypes.size();i++) {
- if(ctype.toLowerCase().contains(options.binaryContentTypes.getString(i).toLowerCase())) {
+ if(ctype.toLowerCase(Locale.ROOT).contains(options.binaryContentTypes.getString(i).toLowerCase(Locale.ROOT))) {
isCustomBinary = true;
break;
}
@@ -698,13 +699,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
private String getHeaderIgnoreCases(Headers headers, String field) {
String val = headers.get(field);
if(val != null) return val;
- return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
+ return headers.get(field.toLowerCase(Locale.ROOT)) == null ? "" : headers.get(field.toLowerCase(Locale.ROOT));
}
private String getHeaderIgnoreCases(HashMap<String,String> headers, String field) {
String val = headers.get(field);
if(val != null) return val;
- String lowerCasedValue = headers.get(field.toLowerCase());
+ String lowerCasedValue = headers.get(field.toLowerCase(Locale.ROOT));
return lowerCasedValue == null ? "" : lowerCasedValue;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,4 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Fri, 01 Mar 2024 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Fri, 01 Mar 2024 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 2.14]]></title><description><![CDATA[<h2>OCR<a name="ocr" href="#ocr" class="heading-anchor">🔗</a></h2>
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 01 Jul 2024 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.0]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
<h3>Trash folder<a name="trash-folder" href="#trash-folder" class="heading-anchor">🔗</a></h3>
<p>Joplin now support a trash folder - any deleted notes or notebooks will be moved to that folder. You can also choose to have these notes permanently deleted after a number of days.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-trash.png" alt=""></p>
<p>Support for the trash folder has a somewhat long history in Joplin since it's an obvious and important feature to add, yet it can be particularly tricky once you start realising how many parts of the app it's going to impact.</p>
<p>Many attempts have been made over time: my first attempt was based on the note history feature. Indeed since this feature already saves versions of notes, it seems to make sense to use it for the trash feature, and indeed the note history feature <a href="https://joplinapp.org/news/20190523-221026">was designed for this originally</a>. However that approach turned to be needlessly complicated and after modifying hundreds of files just for this, the idea was dropped.</p>
<p>The next one was based on using a <a href="https://github.com/laurent22/joplin/issues/483">special &quot;trash&quot; tag</a> - deleted notes would have this tag attached to them and would appear in a special &quot;trash&quot; folder. This approach also had <a href="https://github.com/laurent22/joplin/issues/483">many issues</a> probably the main one being that notebooks can't be tagged, which means we would have to add support for tagged notebooks and that in itself would also be a massive change.</p>
<p><a href="https://discourse.joplinapp.org/t/trashcan/3998/16">Various</a>, <a href="https://discourse.joplinapp.org/t/poll-trash-bin-plugin/19951">ideas,</a> were also attempted using plugins, by creating a special &quot;trash folder&quot;, but in the end no such plugin was ever created, probably due to limitations of the plugin API.</p>
<p>In the end, turned out that this <a href="https://github.com/laurent22/joplin/issues/483#issuecomment-585655742">old idea</a> of adding a &quot;deleted&quot; property to each note and notebook was the easiest approach. With this it was simpler to get to a working solution relatively quickly, and then it was a matter of ensuring that deleted notes don't appear where they shouldn't, such as search results, etc.</p>
<h3>Joplin Cloud multi-factor authentication<a name="joplin-cloud-multi-factor-authentication" href="#joplin-cloud-multi-factor-authentication" class="heading-anchor">🔗</a></h3>
<p>Multi-factor authentication (MFA), also known as two-factor authentication (2FA) is a security process that requires you to provide two or more verification factors to gain access to a system or account. It typically includes something you know (password), something you have (security token), and something you are (biometric verification).</p>
<p>To better secure your account, Joplin Cloud and all Joplin applications now support MFA. To enable it, go to your Joplin Cloud profile, click on &quot;Enable multi-factor authentication&quot; and follow the instructions. Please note that all your applications will then be disconnected, so you will need to login again (your data of course will remain on the app so you won't have to download it again).</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-mfa.png" alt=""></p>
<h3>Note list with multiple columns<a name="note-list-with-multiple-columns" href="#note-list-with-multiple-columns" class="heading-anchor">🔗</a></h3>
<p>In this release we add support for multiple columns in the note list. You can display various properties of the notes, as well as sort the notes by these properties. As usual this feature can be controlled and customised by plugins so for example it should be possible to display custom columns, and display custom information including thumbnails.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-note-list-multi.png" alt=""></p>
<h3>Plugin API enhancement<a name="plugin-api-enhancement" href="#plugin-api-enhancement" class="heading-anchor">🔗</a></h3>
<p>The plugin API has received several updates to facilitate easy customisation of the app As mentioned above, it is now possible to customise the new note list. Besides this, we've added support for loading PDFs and creating images from them, which can for example be used to create thumbnails.</p>
<p>Many other small enhancements have been made to the plugin API to help you tailor the app to your needs!</p>
<h3>View OCR data<a name="view-ocr-data" href="#view-ocr-data" class="heading-anchor">🔗</a></h3>
<p>Now when you right-click on an image or PDF you have an option to view the OCR (Optical character recognition) data associated with it. That will allow you for example to easily copy and paste the text.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-ocr-data.png" alt=""></p>
<h2>Plugin support on mobile<a name="plugin-support-on-mobile" href="#plugin-support-on-mobile" class="heading-anchor">🔗</a></h2>
<p>As always, most of the above changes also apply to mobile (iOS and Android), for example the trash folder and MFA support.</p>
<p>Additionally the mobile application now adds support for plugins. To enable the feature, go to the settings then to the &quot;Plugins&quot; section. The feature is currently in Beta, in particular it means that some plugins do not work or only partially work. Normally the app should not offer you to install a non-working plugin but that may still happen. In general if you notice any issue with this beta feature please let me us know as we're keen to improve it.</p>
<p>Support for cross-platform plugins in Joplin is great news as it means a lot of new features become available on mobile. As of now, we have checked the following plugins and can confirm that they work on mobile:</p>
<ul>
<li><a href="https://joplinapp.org/plugins/plugin/com.whatever.quick-links/">Quick Links</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/com.whatever.inline-tags/">Inline Tags</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/io.github.personalizedrefrigerator.codemirror6-settings/">CodeMirror 6 settings</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/com.hieuthi.joplin.function-plot/">Function plot</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/joplin.plugin.space-indenter/">Space indenter</a></li>
<li><a href="https://joplinapp.org/plugins/plugin/joplin.plugin.alondmnt.tag-navigator/">Inline Tag Navigator</a></li>
</ul>
<p>Those are just some examples - many more are working!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20240701-mobile-plugins.png" alt=""></p>
<h1>Full changelogs<a name="full-changelogs" href="#full-changelogs" class="heading-anchor">🔗</a></h1>
<p>This is just an overview of the main features. The full changelogs are available there:</p>
<ul>
<li>Desktop: <a href="https://joplinapp.org/help/about/changelog/desktop">https://joplinapp.org/help/about/changelog/desktop</a></li>
<li>Android: <a href="https://joplinapp.org/help/about/changelog/android/">https://joplinapp.org/help/about/changelog/android/</a></li>
<li>iOS: <a href="https://joplinapp.org/help/about/changelog/ios/">https://joplinapp.org/help/about/changelog/ios/</a></li>
</ul>
]]></description><link>https://joplinapp.org/news/20240701-release-3-0</link><guid isPermaLink="false">20240701-release-3-0</guid><pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate><twitter-text>What&apos;s new in Joplin 3.0</twitter-text></item><item><title><![CDATA[What's new in Joplin 2.14]]></title><description><![CDATA[<h2>OCR<a name="ocr" href="#ocr" class="heading-anchor">🔗</a></h2>
<p>Optical Character Recognition (OCR) in Joplin enables the transformation of text-containing images into machine-readable text formats. From this version you can enable OCR in the Configuration screen under the &quot;General&quot; section. Once activated, Joplin scans images and PDFs, extracting text data for searchability.</p>
<p>While OCR search is available on both desktop and mobile apps, document scanning is limited to the desktop due to resource demands. For more information head to the <a href="https://joplinapp.org/help/apps/ocr">OCR official documentation</a>!</p>
<h2>Bundled plugins<a name="bundled-plugins" href="#bundled-plugins" class="heading-anchor">🔗</a></h2>
@@ -394,7 +437,4 @@ sys 0m38.013s</p>
</tr>
</tbody>
</table>
]]></description><link>https://joplinapp.org/news/20220522-gsoc-contributors</link><guid isPermaLink="false">20220522-gsoc-contributors</guid><pubDate>Sun, 22 May 2022 00:00:00 GMT</pubDate><twitter-text>Joplin received 6 Contributor Projects for GSoC 2022! Welcome to our new contributors who will be working on these projects over summer!</twitter-text></item><item><title><![CDATA[GSoC "Contributor Proposals" phase is starting now!]]></title><description><![CDATA[<p>The &quot;Contributor Proposals&quot; phase of GSoC 2022 is starting today! If you would like to be a contributor, now is the time to choose your project idea, write your proposal, and upload it to <a href="https://summerofcode.withgoogle.com/">https://summerofcode.withgoogle.com/</a></p>
<p>When it's done, please also let us know by posting an update on your forum introduction post.</p>
<p>If you haven't created a pull request yet, it's still time to create one. Doing so will greatly increase your chances of being selected!</p>
]]></description><link>https://joplinapp.org/news/20220405-gsoc-contributor-proposals</link><guid isPermaLink="false">20220405-gsoc-contributor-proposals</guid><pubDate>Tue, 05 Apr 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
]]></description><link>https://joplinapp.org/news/20220522-gsoc-contributors</link><guid isPermaLink="false">20220522-gsoc-contributors</guid><pubDate>Sun, 22 May 2022 00:00:00 GMT</pubDate><twitter-text>Joplin received 6 Contributor Projects for GSoC 2022! Welcome to our new contributors who will be working on these projects over summer!</twitter-text></item></channel></rss>

View File

@@ -28,6 +28,7 @@ SILENT=false
ALLOW_ROOT=false
SHOW_CHANGELOG=false
INCLUDE_PRE_RELEASE=false
INSTALL_DIR="${HOME}/.joplin" # default installation directory
print() {
if [[ "${SILENT}" == false ]]; then
@@ -57,6 +58,7 @@ showHelp() {
print "\t" "--force" "\t" "Always download the latest version"
print "\t" "--silent" "\t" "Don't print any output"
print "\t" "--prerelease" "\t" "Check for new Versions including Pre-Releases"
print "\t" "--install-dir" "\t" "Set installation directory; default: \"${INSTALL_DIR}\""
if [[ ! -z $1 ]]; then
print "\n" "${COLOR_RED}ERROR: " "$*" "${COLOR_RESET}" "\n"
@@ -84,6 +86,7 @@ while getopts "${optspec}" OPT; do
force ) FORCE=true ;;
changelog ) SHOW_CHANGELOG=true ;;
prerelease ) INCLUDE_PRE_RELEASE=true ;;
install-dir ) INSTALL_DIR="$OPTARG" ;;
[^\?]* ) showHelp "Illegal option --${OPT}"; exit 2 ;;
\? ) showHelp "Illegal option -${OPTARG}"; exit 2 ;;
esac
@@ -120,9 +123,10 @@ fi
print "Checking dependencies..."
## Check if libfuse2 is present.
if [[ $(command -v ldconfig) ]]; then
LIBFUSE=$(ldconfig -p | grep "libfuse.so.2" || echo '')
else
LIBFUSE=$(find /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib -name "libfuse.so.2" 2>/dev/null | grep "libfuse.so.2" || echo '')
LIBFUSE=$(ldconfig -p | grep "libfuse.so.2" || echo '')
fi
if [[ $LIBFUSE == "" ]]; then
LIBFUSE=$(find /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib -name "libfuse.so.2" 2>/dev/null | grep "libfuse.so.2" || echo '')
fi
if [[ $LIBFUSE == "" ]]; then
print "${COLOR_RED}Error: Can't get libfuse2 on system, please install libfuse2${COLOR_RESET}"
@@ -142,17 +146,17 @@ else
fi
# Check if it's in the latest version
if [[ -e ~/.joplin/VERSION ]] && [[ $(< ~/.joplin/VERSION) == "${RELEASE_VERSION}" ]]; then
if [[ -e "${INSTALL_DIR}/VERSION" ]] && [[ $(< "${INSTALL_DIR}/VERSION") == "${RELEASE_VERSION}" ]]; then
print "${COLOR_GREEN}You already have the latest version${COLOR_RESET} ${RELEASE_VERSION} ${COLOR_GREEN}installed.${COLOR_RESET}"
([[ "$FORCE" == true ]] && print "Forcing installation...") || exit 0
else
[[ -e ~/.joplin/VERSION ]] && CURRENT_VERSION=$(< ~/.joplin/VERSION)
[[ -e "${INSTALL_DIR}/VERSION" ]] && CURRENT_VERSION=$(< "${INSTALL_DIR}/VERSION")
print "The latest version is ${RELEASE_VERSION}, but you have ${CURRENT_VERSION:-no version} installed."
fi
# Check if it's an update or a new install
DOWNLOAD_TYPE="New"
if [[ -f ~/.joplin/Joplin.AppImage ]]; then
if [[ -f "${INSTALL_DIR}/Joplin.AppImage" ]]; then
DOWNLOAD_TYPE="Update"
fi
@@ -165,16 +169,16 @@ wget -O "${TEMP_DIR}/joplin.png" https://joplinapp.org/images/Icon512.png
#-----------------------------------------------------
print 'Installing Joplin...'
# Delete previous version (in future versions joplin.desktop shouldn't exist)
rm -f ~/.joplin/*.AppImage ~/.local/share/applications/joplin.desktop ~/.joplin/VERSION
rm -f "${INSTALL_DIR}"/*.AppImage ~/.local/share/applications/joplin.desktop "${INSTALL_DIR}/VERSION"
# Creates the folder where the binary will be stored
mkdir -p ~/.joplin/
mkdir -p "${INSTALL_DIR}/"
# Download the latest version
mv "${TEMP_DIR}/Joplin.AppImage" ~/.joplin/Joplin.AppImage
mv "${TEMP_DIR}/Joplin.AppImage" "${INSTALL_DIR}/Joplin.AppImage"
# Gives execution privileges
chmod +x ~/.joplin/Joplin.AppImage
chmod +x "${INSTALL_DIR}/Joplin.AppImage"
print "${COLOR_GREEN}OK${COLOR_RESET}"
@@ -253,7 +257,7 @@ if [[ $DESKTOP =~ .*gnome.*|.*kde.*|.*xfce.*|.*mate.*|.*lxqt.*|.*unity.*|.*x-cin
Encoding=UTF-8
Name=Joplin
Comment=Joplin for Desktop
Exec=env APPIMAGELAUNCHER_DISABLE=TRUE ${HOME}/.joplin/Joplin.AppImage ${SANDBOXPARAM} %u
Exec=env APPIMAGELAUNCHER_DISABLE=TRUE "${INSTALL_DIR}/Joplin.AppImage" ${SANDBOXPARAM} %u
Icon=joplin
StartupWMClass=Joplin
Type=Application
@@ -278,7 +282,7 @@ fi
print "${COLOR_GREEN}Joplin version${COLOR_RESET} ${RELEASE_VERSION} ${COLOR_GREEN}installed.${COLOR_RESET}"
# Record version
echo "$RELEASE_VERSION" > ~/.joplin/VERSION
echo "$RELEASE_VERSION" > "${INSTALL_DIR}/VERSION"
#-----------------------------------------------------
if [[ "$SHOW_CHANGELOG" == true ]]; then

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://buyyoutubviews.com"><img title="BYTV" width="256" src="https://joplinapp.org/images/sponsors/BYTV.png"/></a> <a href="https://www.famegear.com"><img title="Famegear" width="256" src="https://joplinapp.org/images/sponsors/Famegear.png"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -61,6 +61,7 @@
"/packages/turndown/config",
"/readme/_i18n",
"/readme/about/changelog/desktop.md",
"/readme/licenses.md",
"/readme/i18n",
"cspell.json",
"node_modules"

View File

@@ -15,7 +15,6 @@
# SLAVE_POSTGRES_USER=joplin
# SLAVE_POSTGRES_PORT=5433
# SLAVE_POSTGRES_HOST=localhost
# USERS_WITH_REPLICATION=ID1,ID2,...
version: '2'

View File

@@ -21,7 +21,8 @@ module.exports = {
// See https://github.com/lint-staged/lint-staged/issues/934#issuecomment-743299357
'*.{js,jsx,ts,tsx,task1}': 'yarn checkIgnoredFiles',
'*.{js,jsx,ts,tsx,task2}': 'yarn spellcheck',
'*.{js,jsx,ts,tsx,task3}': 'yarn packageJsonLint',
'*.{js,jsx,ts,tsx,task4}': 'yarn linter-precommit',
'*.{md,mdx}': 'yarn spellcheck',
'*.{js,jsx,ts,tsx,task3}': 'yarn linter-precommit',
'*.{json,task4}': 'yarn packageJsonLint',
'*.{md,mdx,task5}': 'yarn spellcheck',
'*.{md,mdx,task6}': 'yarn validateFilenames',
};

View File

@@ -59,6 +59,7 @@
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
"updateNews": "node ./packages/tools/website/updateNews",
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
"validateFilenames": "node ./packages/tools/validateFilenames.js",
"watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch",
"watchWebsite": "nodemon --delay 1 --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --watch packages/doc-builder/build --ext md,ts,js,mustache,css,tsx,gif,png,svg --exec \"node packages/tools/website/build.js && http-server --port 8077 ../joplin-website/docs -a localhost\""
},
@@ -82,11 +83,11 @@
"eslint-plugin-react": "7.33.2",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "10.3.10",
"glob": "10.3.16",
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "15.2.2",
"lint-staged": "15.2.5",
"madge": "6.1.0",
"npm-package-json-lint": "7.1.0",
"typescript": "5.2.2"
@@ -109,6 +110,7 @@
"@react-native-community/slider": "patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch",
"husky": "patch:husky@npm%3A3.1.0#./.yarn/patches/husky-npm-3.1.0-5cc13e4e34.patch",
"chokidar@^2.0.0": "3.5.3",
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch"
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch",
"rn-fetch-blob@0.12.0": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch"
}
}

View File

@@ -1,4 +1,4 @@
import Setting, { SettingStorage } from '@joplin/lib/models/Setting';
import Setting, { AppType, SettingStorage } from '@joplin/lib/models/Setting';
import { SettingItemType } from '@joplin/lib/services/plugins/api/types';
import shim from '@joplin/lib/shim';
@@ -61,7 +61,7 @@ class Command extends BaseCommand {
const description: string[] = [];
if (md.label && md.label()) description.push(md.label());
if (md.description && md.description('desktop')) description.push(md.description('desktop'));
if (md.description && md.description(AppType.Desktop)) description.push(md.description(AppType.Desktop));
if (description.length) props.description = description.join('. ');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@@ -35,15 +35,15 @@
],
"owner": "Laurent Cozic"
},
"version": "3.0.0",
"version": "3.1.0",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "~3.0",
"@joplin/renderer": "~3.0",
"@joplin/utils": "~3.0",
"@joplin/lib": "~3.1",
"@joplin/renderer": "~3.1",
"@joplin/utils": "~3.1",
"aws-sdk": "2.1340.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",
@@ -57,23 +57,23 @@
"proper-lockfile": "4.1.2",
"read-chunk": "2.1.0",
"server-destroy": "1.0.1",
"sharp": "0.33.2",
"sharp": "0.33.4",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
"strip-ansi": "6.0.1",
"tcp-port-used": "1.0.2",
"terminal-kit": "3.0.2",
"terminal-kit": "3.1.1",
"tkwidgets": "0.5.27",
"url-parse": "1.5.10",
"word-wrap": "1.2.5",
"yargs-parser": "21.1.1"
},
"devDependencies": {
"@joplin/tools": "~3.0",
"@joplin/tools": "~3.1",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.8",
"@types/node": "18.19.24",
"@types/node": "18.19.34",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",

View File

@@ -2,13 +2,16 @@ import MdToHtml from '@joplin/renderer/MdToHtml';
const { filename } = require('@joplin/lib/path-utils');
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import shim from '@joplin/lib/shim';
import { RenderOptions } from '@joplin/renderer/types';
import { isResourceUrl, resourceUrlToId } from '@joplin/lib/models/utils/resourceUtils';
const { themeStyle } = require('@joplin/lib/theme');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function newTestMdToHtml(options: any = null) {
options = {
ResourceModel: {
isResourceUrl: () => false,
isResourceUrl: isResourceUrl,
urlToId: resourceUrlToId,
},
fsDriver: shim.fsDriver(),
...options,
@@ -39,7 +42,7 @@ describe('MdToHtml', () => {
// if (mdFilename !== 'sanitize_9.md') continue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const mdToHtmlOptions: any = {
const mdToHtmlOptions: RenderOptions = {
bodyOnly: true,
};
@@ -51,6 +54,8 @@ describe('MdToHtml', () => {
};
} else if (mdFilename.startsWith('sourcemap_')) {
mdToHtmlOptions.mapsToLine = true;
} else if (mdFilename.startsWith('resource_')) {
mdToHtmlOptions.resources = {};
}
const markdown = await shim.fsDriver().readFile(mdFilePath);

View File

@@ -0,0 +1,48 @@
<p>Markdown images:</p>
<ul>
<li>
With ALT and title:
<div
class="not-loaded-resource not-loaded-image-resource resource-status-test"
data-original-alt="test"
data-original-title="testing"
data-resource-id="0415d61cc33e47afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
<li>
With neither ALT nor title:
<div
class="not-loaded-resource not-loaded-image-resource resource-status-error"
data-original-alt=""
data-original-title=""
data-resource-id="0a25d61cc33e57afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
</ul>
<p>HTML images:</p>
<ul>
<li>
<div
class="not-loaded-resource not-loaded-image-resource resource-status-error"
data-original-before=" width=&quot;230&quot;"
data-original-after=" style=&quot;border: 32px inset red;&quot;/"
data-resource-id="0415d61cc33e47afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
<li>
<div
class="not-loaded-resource not-loaded-image-resource resource-status-error"
data-original-after="/"
data-resource-id="0415d61cc33e47afa6dde45948c3177f"
>
<img src="data:image/svg+xml;utf8,some-icon-here"/>
</div>
</li>
</ul>

View File

@@ -0,0 +1,9 @@
Markdown images:
- With ALT and title:![test](:/0415d61cc33e47afa6dde45948c3177f "testing")
- With neither ALT nor title:![](:/0a25d61cc33e57afa6dde45948c3177f)
HTML images:
- <img width="230" src=":/0415d61cc33e47afa6dde45948c3177f" style="border: 32px inset red;"/>
- <img src=":/0415d61cc33e47afa6dde45948c3177f" />

View File

@@ -0,0 +1,15 @@
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22345" data-original-alt data-original-title="test" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></div>
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22346" data-original-alt="test" data-original-title contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></div>
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22347" data-original-before=" " data-original-after=" class=&quot;jop-noMdConv&quot;/" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></div>

View File

@@ -0,0 +1,3 @@
![](:/a1test2a1test2a1test2a1test22345 "test")
![test](:/a1test2a1test2a1test2a1test22346)
<img src=":/a1test2a1test2a1test2a1test22347"/>

View File

@@ -0,0 +1,2 @@
<div class="jop-noMdConv">
&lt;.a

View File

@@ -0,0 +1,2 @@
<div>
<.a<iframe src="http://example.com/" >

View File

@@ -0,0 +1,3 @@
dist/
node_modules/
publish/

View File

@@ -0,0 +1,8 @@
*.md
!README.md
/*.jpl
/api
/src
/dist
tsconfig.json
webpack.config.js

View File

@@ -0,0 +1,78 @@
# Plugin development
This documentation describes how to create a plugin, and how to work with the plugin builder framework and API.
## Installation
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g generator-joplin
```
Then generate your new project:
```bash
yo --node-package-manager npm joplin
```
## Structure
The main two files you will want to look at are:
- `/src/index.ts`, which contains the entry point for the plugin source code.
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
## Building the plugin
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
To build the plugin, simply run `npm run dist`.
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
## Updating the manifest version number
You can run `npm run updateVersion` to bump the patch part of the version number, so for example 1.0.3 will become 1.0.4. This script will update both the package.json and manifest.json version numbers so as to keep them in sync.
## Publishing the plugin
To publish the plugin, add it to npmjs.com by running `npm publish`. Later on, a script will pick up your plugin and add it automatically to the Joplin plugin repository as long as the package satisfies these conditions:
- In `package.json`, the name starts with "joplin-plugin-". For example, "joplin-plugin-toc".
- In `package.json`, the keywords include "joplin-plugin".
- In the `publish/` directory, there should be a .jpl and .json file (which are built by `npm run dist`)
In general all this is done automatically by the plugin generator, which will set the name and keywords of package.json, and will put the right files in the "publish" directory. But if something doesn't work and your plugin doesn't appear in the repository, double-check the above conditions.
## Updating the plugin framework
To update the plugin framework, run `npm run update`.
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
## External script files
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplincontentscripts.html) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
## More information
- [Joplin Plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html)
- [Joplin Data API](https://joplinapp.org/help/api/references/rest_api)
- [Joplin Plugin Manifest](https://joplinapp.org/api/references/plugin_manifest/)
- Ask for help on the [forum](https://discourse.joplinapp.org/) or our [Discord channel](https://discord.gg/VSj7AFHvpq)
## License
MIT © Laurent Cozic

View File

@@ -0,0 +1,10 @@
# Worker example
This plugin demonstrates how to create a `Worker` and, within it, use `transformers.js` for text classification.
At present, this plugin only works on desktop. However, it should also be possible to load the `Worker` from a plugin panel or dialog on mobile (but not web).
**Notes**:
- `plugin.config.json` was updated to target **web**. This allows the plugin to load `transformers.js` from the worker without depending on `node-loader`.
- This simplifies the build process and should make it easier to add mobile and web support to this plugin.
- WASM files and other assets are copied by `tools/copyAssets.js`.

View File

@@ -0,0 +1,14 @@
import Plugin from '../Plugin';
import Joplin from './Joplin';
/**
* @ignore
*/
/**
* @ignore
*/
export default class Global {
private joplin_;
constructor(implementation: any, plugin: Plugin, store: any);
get joplin(): Joplin;
get process(): any;
}

View File

@@ -0,0 +1,74 @@
import Plugin from '../Plugin';
import JoplinData from './JoplinData';
import JoplinPlugins from './JoplinPlugins';
import JoplinWorkspace from './JoplinWorkspace';
import JoplinFilters from './JoplinFilters';
import JoplinCommands from './JoplinCommands';
import JoplinViews from './JoplinViews';
import JoplinInterop from './JoplinInterop';
import JoplinSettings from './JoplinSettings';
import JoplinContentScripts from './JoplinContentScripts';
import JoplinClipboard from './JoplinClipboard';
import JoplinWindow from './JoplinWindow';
import BasePlatformImplementation from '../BasePlatformImplementation';
import JoplinImaging from './JoplinImaging';
/**
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
*
* The API is now relatively stable and in general maintaining backward compatibility is a top priority, so you shouldn't except much breakages.
*
* If a breaking change ever becomes needed, best effort will be done to:
*
* - Deprecate features instead of removing them, so as to give you time to fix the issue;
* - Document breaking changes in the changelog;
*
* So if you are developing a plugin, please keep an eye on the changelog as everything will be in there with information about how to update your code.
*/
export default class Joplin {
private data_;
private plugins_;
private imaging_;
private workspace_;
private filters_;
private commands_;
private views_;
private interop_;
private settings_;
private contentScripts_;
private clipboard_;
private window_;
private implementation_;
constructor(implementation: BasePlatformImplementation, plugin: Plugin, store: any);
get data(): JoplinData;
get clipboard(): JoplinClipboard;
get imaging(): JoplinImaging;
get window(): JoplinWindow;
get plugins(): JoplinPlugins;
get workspace(): JoplinWorkspace;
get contentScripts(): JoplinContentScripts;
/**
* @ignore
*
* Not sure if it's the best way to hook into the app
* so for now disable filters.
*/
get filters(): JoplinFilters;
get commands(): JoplinCommands;
get views(): JoplinViews;
get interop(): JoplinInterop;
get settings(): JoplinSettings;
/**
* It is not possible to bundle native packages with a plugin, because they
* need to work cross-platforms. Instead access to certain useful native
* packages is provided using this function.
*
* Currently these packages are available:
*
* - [sqlite3](https://www.npmjs.com/package/sqlite3)
* - [fs-extra](https://www.npmjs.com/package/fs-extra)
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/nativeModule)
*/
require(_path: string): any;
versionInfo(): Promise<import("./types").VersionInfo>;
}

View File

@@ -0,0 +1,23 @@
export default class JoplinClipboard {
private electronClipboard_;
private electronNativeImage_;
constructor(electronClipboard: any, electronNativeImage: any);
readText(): Promise<string>;
writeText(text: string): Promise<void>;
readHtml(): Promise<string>;
writeHtml(html: string): Promise<void>;
/**
* Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
*/
readImage(): Promise<string>;
/**
* Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
*/
writeImage(dataUrl: string): Promise<void>;
/**
* Returns the list available formats (mime types).
*
* For example [ 'text/plain', 'text/html' ]
*/
availableFormats(): Promise<string[]>;
}

View File

@@ -0,0 +1,89 @@
import { Command } from './types';
/**
* This class allows executing or registering new Joplin commands. Commands
* can be executed or associated with
* {@link JoplinViewsToolbarButtons | toolbar buttons} or
* {@link JoplinViewsMenuItems | menu items}.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
*
* ## Executing Joplin's internal commands
*
* It is also possible to execute internal Joplin's commands which, as of
* now, are not well documented. You can find the list directly on GitHub
* though at the following locations:
*
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
*
* To view what arguments are supported, you can open any of these files
* and look at the `execute()` command.
*
* ## Executing editor commands
*
* There might be a situation where you want to invoke editor commands
* without using a {@link JoplinContentScripts | contentScript}. For this
* reason Joplin provides the built in `editor.execCommand` command.
*
* `editor.execCommand` should work with any core command in both the
* [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
* [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
* as well as most functions calls directly on a CodeMirror editor object (extensions).
*
* * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
* * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
*
* `editor.execCommand` supports adding arguments for the commands.
*
* ```typescript
* await joplin.commands.execute('editor.execCommand', {
* name: 'madeUpCommand', // CodeMirror and TinyMCE
* args: [], // CodeMirror and TinyMCE
* ui: false, // TinyMCE only
* value: '', // TinyMCE only
* });
* ```
*
* [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
*
*/
export default class JoplinCommands {
/**
* <span class="platform-desktop">desktop</span> Executes the given
* command.
*
* The command can take any number of arguments, and the supported
* arguments will vary based on the command. For custom commands, this
* is the `args` passed to the `execute()` function. For built-in
* commands, you can find the supported arguments by checking the links
* above.
*
* ```typescript
* // Create a new note in the current notebook:
* await joplin.commands.execute('newNote');
*
* // Create a new sub-notebook under the provided notebook
* // Note: internally, notebooks are called "folders".
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
* ```
*/
execute(commandName: string, ...args: any[]): Promise<any | void>;
/**
* <span class="platform-desktop">desktop</span> Registers a new command.
*
* ```typescript
* // Register a new commmand called "testCommand1"
*
* await joplin.commands.register({
* name: 'testCommand1',
* label: 'My Test Command 1',
* iconName: 'fas fa-music',
* execute: () => {
* alert('Testing plugin command 1');
* },
* });
* ```
*/
register(command: Command): Promise<void>;
}

View File

@@ -0,0 +1,40 @@
import Plugin from '../Plugin';
import { ContentScriptType } from './types';
export default class JoplinContentScripts {
private plugin;
constructor(plugin: Plugin);
/**
* Registers a new content script. Unlike regular plugin code, which runs in
* a separate process, content scripts run within the main process code and
* thus allow improved performances and more customisations in specific
* cases. It can be used for example to load a Markdown or editor plugin.
*
* Note that registering a content script in itself will do nothing - it
* will only be loaded in specific cases by the relevant app modules (eg.
* the Markdown renderer or the code editor). So it is not a way to inject
* and run arbitrary code in the app, which for safety and performance
* reasons is not supported.
*
* The plugin generator provides a way to build any content script you might
* want to package as well as its dependencies. See the [Plugin Generator
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
* for more information.
*
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
*
* See also the [postMessage demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
*
* @param type Defines how the script will be used. See the type definition for more information about each supported type.
* @param id A unique ID for the content script.
* @param scriptPath Must be a path relative to the plugin main script. For example, if your file content_script.js is next to your index.ts file, you would set `scriptPath` to `"./content_script.js`.
*/
register(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
/**
* Listens to a messages sent from the content script using postMessage().
* See {@link ContentScriptType} for more information as well as the
* [postMessage
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
*/
onMessage(contentScriptId: string, callback: any): Promise<void>;
}

View File

@@ -0,0 +1,73 @@
import { ModelType } from '../../../BaseModel';
import Plugin from '../Plugin';
import { Path } from './types';
/**
* This module provides access to the Joplin data API: https://joplinapp.org/help/api/references/rest_api
* This is the main way to retrieve data, such as notes, notebooks, tags, etc.
* or to update them or delete them.
*
* This is also what you would use to search notes, via the `search` endpoint.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/simple)
*
* In general you would use the methods in this class as if you were using a REST API. There are four methods that map to GET, POST, PUT and DELETE calls.
* And each method takes these parameters:
*
* * `path`: This is an array that represents the path to the resource in the form `["resouceName", "resourceId", "resourceLink"]` (eg. ["tags", ":id", "notes"]). The "resources" segment is the name of the resources you want to access (eg. "notes", "folders", etc.). If not followed by anything, it will refer to all the resources in that collection. The optional "resourceId" points to a particular resources within the collection. Finally, an optional "link" can be present, which links the resource to a collection of resources. This can be used in the API for example to retrieve all the notes associated with a tag.
* * `query`: (Optional) The query parameters. In a URL, this is the part after the question mark "?". In this case, it should be an object with key/value pairs.
* * `data`: (Optional) Applies to PUT and POST calls only. The request body contains the data you want to create or modify, for example the content of a note or folder.
* * `files`: (Optional) Used to create new resources and associate them with files.
*
* Please refer to the [Joplin API documentation](https://joplinapp.org/help/api/references/rest_api) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
*
* For example:
*
* ```typescript
* // Get a note ID, title and body
* const noteId = 'some_note_id';
* const note = await joplin.data.get(['notes', noteId], { fields: ['id', 'title', 'body'] });
*
* // Get all folders
* const folders = await joplin.data.get(['folders']);
*
* // Set the note body
* await joplin.data.put(['notes', noteId], null, { body: "New note body" });
*
* // Create a new note under one of the folders
* await joplin.data.post(['notes'], null, { body: "my new note", title: "some title", parent_id: folders[0].id });
* ```
*/
export default class JoplinData {
private api_;
private pathSegmentRegex_;
private plugin;
constructor(plugin: Plugin);
private serializeApiBody;
private pathToString;
get(path: Path, query?: any): Promise<any>;
post(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
put(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
delete(path: Path, query?: any): Promise<any>;
itemType(itemId: string): Promise<ModelType>;
resourcePath(resourceId: string): Promise<string>;
/**
* Gets an item user data. User data are key/value pairs. The `key` can be any
* arbitrary string, while the `value` can be of any type supported by
* [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)
*
* User data is synchronised across devices, and each value wil be merged based on their timestamp:
*
* - If value is modified by client 1, then modified by client 2, it will take the value from client 2
* - If value is modified by client 1, then deleted by client 2, the value will be deleted after merge
* - If value is deleted by client 1, then updated by client 2, the value will be restored and set to the value from client 2 after merge
*/
userDataGet<T>(itemType: ModelType, itemId: string, key: string): Promise<T>;
/**
* Sets a note user data. See {@link JoplinData.userDataGet} for more details.
*/
userDataSet<T>(itemType: ModelType, itemId: string, key: string, value: T): Promise<void>;
/**
* Deletes a note user data. See {@link JoplinData.userDataGet} for more details.
*/
userDataDelete(itemType: ModelType, itemId: string, key: string): Promise<void>;
}

View File

@@ -0,0 +1,10 @@
/**
* @ignore
*
* Not sure if it's the best way to hook into the app
* so for now disable filters.
*/
export default class JoplinFilters {
on(name: string, callback: Function): Promise<void>;
off(name: string, callback: Function): Promise<void>;
}

View File

@@ -0,0 +1,61 @@
import { Rectangle } from './types';
export interface Implementation {
nativeImage: any;
}
export interface CreateFromBufferOptions {
width?: number;
height?: number;
scaleFactor?: number;
}
export interface ResizeOptions {
width?: number;
height?: number;
quality?: 'good' | 'better' | 'best';
}
export type Handle = string;
/**
* Provides imaging functions to resize or process images. You create an image
* using one of the `createFrom` functions, then use the other functions to
* process the image.
*
* Images are associated with a handle which is what will be available to the
* plugin. Once you are done with an image, free it using the `free()` function.
*
* [View the
* example](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/imaging/src/index.ts)
*
*/
export default class JoplinImaging {
private implementation_;
private images_;
constructor(implementation: Implementation);
private createImageHandle;
private imageByHandle;
private cacheImage;
createFromPath(filePath: string): Promise<Handle>;
createFromResource(resourceId: string): Promise<Handle>;
getSize(handle: Handle): Promise<any>;
resize(handle: Handle, options?: ResizeOptions): Promise<string>;
crop(handle: Handle, rectange: Rectangle): Promise<string>;
toPngFile(handle: Handle, filePath: string): Promise<void>;
/**
* Quality is between 0 and 100
*/
toJpgFile(handle: Handle, filePath: string, quality?: number): Promise<void>;
private tempFilePath;
/**
* Creates a new Joplin resource from the image data. The image will be
* first converted to a JPEG.
*/
toJpgResource(handle: Handle, resourceProps: any, quality?: number): Promise<import("../../database/types").ResourceEntity>;
/**
* Creates a new Joplin resource from the image data. The image will be
* first converted to a PNG.
*/
toPngResource(handle: Handle, resourceProps: any): Promise<import("../../database/types").ResourceEntity>;
/**
* Image data is not automatically deleted by Joplin so make sure you call
* this method on the handle once you are done.
*/
free(handle: Handle): Promise<void>;
}

View File

@@ -0,0 +1,17 @@
import { ExportModule, ImportModule } from './types';
/**
* Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)
*
* To implement an import or export module, you would simply define an object with various event handlers that are called
* by the application during the import/export process.
*
* See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.
*
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/help/api/references/rest_api
*/
export default class JoplinInterop {
registerExportModule(module: ExportModule): Promise<void>;
registerImportModule(module: ImportModule): Promise<void>;
}

View File

@@ -0,0 +1,43 @@
import Plugin from '../Plugin';
import { ContentScriptType, Script } from './types';
/**
* This class provides access to plugin-related features.
*/
export default class JoplinPlugins {
private plugin;
constructor(plugin: Plugin);
/**
* Registers a new plugin. This is the entry point when creating a plugin. You should pass a simple object with an `onStart` method to it.
* That `onStart` method will be executed as soon as the plugin is loaded.
*
* ```typescript
* joplin.plugins.register({
* onStart: async function() {
* // Run your plugin code here
* }
* });
* ```
*/
register(script: Script): Promise<void>;
/**
* @deprecated Use joplin.contentScripts.register()
*/
registerContentScript(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
/**
* Gets the plugin own data directory path. Use this to store any
* plugin-related data. Unlike [[installationDir]], any data stored here
* will be persisted.
*/
dataDir(): Promise<string>;
/**
* Gets the plugin installation directory. This can be used to access any
* asset that was packaged with the plugin. This directory should be
* considered read-only because any data you store here might be deleted or
* re-created at any time. To store new persistent data, use [[dataDir]].
*/
installationDir(): Promise<string>;
/**
* @deprecated Use joplin.require()
*/
require(_path: string): any;
}

View File

@@ -0,0 +1,63 @@
import Plugin from '../Plugin';
import { SettingItem, SettingSection } from './types';
export interface ChangeEvent {
/**
* Setting keys that have been changed
*/
keys: string[];
}
export type ChangeHandler = (event: ChangeEvent) => void;
export declare const namespacedKey: (pluginId: string, key: string) => string;
/**
* This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
*
* Settings are essentially key/value pairs.
*
* Note: Currently this API does **not** provide access to Joplin's built-in settings. This is by design as plugins that modify user settings could give unexpected results
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/settings)
*/
export default class JoplinSettings {
private plugin_;
constructor(plugin: Plugin);
/**
* Registers new settings.
* Note that registering a setting item is dynamic and will be gone next time Joplin starts.
* What it means is that you need to register the setting every time the plugin starts (for example in the onStart event).
* The setting value however will be preserved from one launch to the next so there is no risk that it will be lost even if for some
* reason the plugin fails to start at some point.
*/
registerSettings(settings: Record<string, SettingItem>): Promise<void>;
/**
* @deprecated Use joplin.settings.registerSettings()
*
* Registers a new setting.
*/
registerSetting(key: string, settingItem: SettingItem): Promise<void>;
/**
* Registers a new setting section. Like for registerSetting, it is dynamic and needs to be done every time the plugin starts.
*/
registerSection(name: string, section: SettingSection): Promise<void>;
/**
* Gets a setting value (only applies to setting you registered from your plugin)
*/
value(key: string): Promise<any>;
/**
* Sets a setting value (only applies to setting you registered from your plugin)
*/
setValue(key: string, value: any): Promise<void>;
/**
* Gets a global setting value, including app-specific settings and those set by other plugins.
*
* The list of available settings is not documented yet, but can be found by looking at the source code:
*
* https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142
*/
globalValue(key: string): Promise<any>;
/**
* Called when one or multiple settings of your plugin have been changed.
* - For performance reasons, this event is triggered with a delay.
* - You will only get events for your own plugin settings.
*/
onChange(handler: ChangeHandler): Promise<void>;
}

View File

@@ -0,0 +1,31 @@
import Plugin from '../Plugin';
import JoplinViewsDialogs from './JoplinViewsDialogs';
import JoplinViewsMenuItems from './JoplinViewsMenuItems';
import JoplinViewsMenus from './JoplinViewsMenus';
import JoplinViewsToolbarButtons from './JoplinViewsToolbarButtons';
import JoplinViewsPanels from './JoplinViewsPanels';
import JoplinViewsNoteList from './JoplinViewsNoteList';
/**
* This namespace provides access to view-related services.
*
* All view services provide a `create()` method which you would use to create the view object, whether it's a dialog, a toolbar button or a menu item.
* In some cases, the `create()` method will return a [[ViewHandle]], which you would use to act on the view, for example to set certain properties or call some methods.
*/
export default class JoplinViews {
private store;
private plugin;
private panels_;
private menuItems_;
private menus_;
private toolbarButtons_;
private dialogs_;
private noteList_;
private implementation_;
constructor(implementation: any, plugin: Plugin, store: any);
get dialogs(): JoplinViewsDialogs;
get panels(): JoplinViewsPanels;
get menuItems(): JoplinViewsMenuItems;
get menus(): JoplinViewsMenus;
get toolbarButtons(): JoplinViewsToolbarButtons;
get noteList(): JoplinViewsNoteList;
}

View File

@@ -0,0 +1,74 @@
import Plugin from '../Plugin';
import { ButtonSpec, ViewHandle, DialogResult } from './types';
/**
* Allows creating and managing dialogs. A dialog is modal window that
* contains a webview and a row of buttons. You can update the
* webview using the `setHtml` method. Dialogs are hidden by default and
* you need to call `open()` to open them. Once the user clicks on a
* button, the `open` call will return an object indicating what button was
* clicked on.
*
* ## Retrieving form values
*
* If your HTML content included one or more forms, a `formData` object
* will also be included with the key/value for each form.
*
* ## Special button IDs
*
* The following buttons IDs have a special meaning:
*
* - `ok`, `yes`, `submit`, `confirm`: They are considered "submit" buttons
* - `cancel`, `no`, `reject`: They are considered "dismiss" buttons
*
* This information is used by the application to determine what action
* should be done when the user presses "Enter" or "Escape" within the
* dialog. If they press "Enter", the first "submit" button will be
* automatically clicked. If they press "Escape" the first "dismiss" button
* will be automatically clicked.
*
* [View the demo
* plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)
*/
export default class JoplinViewsDialogs {
private store;
private plugin;
private implementation_;
constructor(implementation: any, plugin: Plugin, store: any);
private controller;
/**
* Creates a new dialog
*/
create(id: string): Promise<ViewHandle>;
/**
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
*/
showMessageBox(message: string): Promise<number>;
/**
* Displays a dialog to select a file or a directory. Same options and
* output as
* https://www.electronjs.org/docs/latest/api/dialog#dialogshowopendialogbrowserwindow-options
*/
showOpenDialog(options: any): Promise<any>;
/**
* Sets the dialog HTML content
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the dialog.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Sets the dialog buttons.
*/
setButtons(handle: ViewHandle, buttons: ButtonSpec[]): Promise<ButtonSpec[]>;
/**
* Opens the dialog
*/
open(handle: ViewHandle): Promise<DialogResult>;
/**
* Toggle on whether to fit the dialog size to the content or not.
* When set to false, the dialog is set to 90vw and 80vh
* @default true
*/
setFitToContent(handle: ViewHandle, status: boolean): Promise<boolean>;
}

View File

@@ -0,0 +1,16 @@
import { CreateMenuItemOptions, MenuItemLocation } from './types';
import Plugin from '../Plugin';
/**
* Allows creating and managing menu items.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
*/
export default class JoplinViewsMenuItems {
private store;
private plugin;
constructor(plugin: Plugin, store: any);
/**
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
*/
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
}

View File

@@ -0,0 +1,18 @@
import { MenuItem, MenuItemLocation } from './types';
import Plugin from '../Plugin';
/**
* Allows creating menus.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/menu)
*/
export default class JoplinViewsMenus {
private store;
private plugin;
constructor(plugin: Plugin, store: any);
private registerCommandAccelerators;
/**
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
* menu as a sub-menu of the application build-in menus.
*/
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
}

View File

@@ -0,0 +1,24 @@
import { Store } from 'redux';
import Plugin from '../Plugin';
import { ListRenderer } from './noteListType';
/**
* This API allows you to customise how each note in the note list is rendered.
* The renderer you implement follows a unidirectional data flow.
*
* The app provides the required dependencies whenever a note is updated - you
* process these dependencies, and return some props, which are then passed to
* your template and rendered. See [[[ListRenderer]]] for a detailed description
* of each property of the renderer.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/note_list_renderer)
*
* The default list renderer is implemented using the same API, so it worth checking it too:
*
* [Default list renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultListRenderer.ts)
*/
export default class JoplinViewsNoteList {
private plugin_;
private store_;
constructor(plugin: Plugin, store: Store);
registerRenderer(renderer: ListRenderer): Promise<void>;
}

View File

@@ -0,0 +1,78 @@
import Plugin from '../Plugin';
import { ViewHandle } from './types';
/**
* Allows creating and managing view panels. View panels currently are
* displayed at the right of the sidebar and allows displaying any HTML
* content (within a webview) and update it in real-time. For example it
* could be used to display a table of content for the active note, or
* display various metadata or graph.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
*/
export default class JoplinViewsPanels {
private store;
private plugin;
constructor(plugin: Plugin, store: any);
private controller;
/**
* Creates a new panel
*/
create(id: string): Promise<ViewHandle>;
/**
* Sets the panel webview HTML
*/
setHtml(handle: ViewHandle, html: string): Promise<string>;
/**
* Adds and loads a new JS or CSS files into the panel.
*/
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
/**
* Called when a message is sent from the webview (using postMessage).
*
* To post a message from the webview to the plugin use:
*
* ```javascript
* const response = await webviewApi.postMessage(message);
* ```
*
* - `message` can be any JavaScript object, string or number
* - `response` is whatever was returned by the `onMessage` handler
*
* Using this mechanism, you can have two-way communication between the
* plugin and webview.
*
* See the [postMessage
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) for more details.
*
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
/**
* Sends a message to the webview.
*
* The webview must have registered a message handler prior, otherwise the message is ignored. Use;
*
* ```javascript
* webviewApi.onMessage((message) => { ... });
* ```
*
* - `message` can be any JavaScript object, string or number
*
* The view API may have only one onMessage handler defined.
* This method is fire and forget so no response is returned.
*
* It is particularly useful when the webview needs to react to events emitted by the plugin or the joplin api.
*/
postMessage(handle: ViewHandle, message: any): void;
/**
* Shows the panel
*/
show(handle: ViewHandle, show?: boolean): Promise<void>;
/**
* Hides the panel
*/
hide(handle: ViewHandle): Promise<void>;
/**
* Tells whether the panel is visible or not
*/
visible(handle: ViewHandle): Promise<boolean>;
}

View File

@@ -0,0 +1,16 @@
import { ToolbarButtonLocation } from './types';
import Plugin from '../Plugin';
/**
* Allows creating and managing toolbar buttons.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
*/
export default class JoplinViewsToolbarButtons {
private store;
private plugin;
constructor(plugin: Plugin, store: any);
/**
* Creates a new toolbar button and associate it with the given command.
*/
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
}

View File

@@ -0,0 +1,24 @@
import Plugin from '../Plugin';
export interface Implementation {
injectCustomStyles(elementId: string, cssFilePath: string): Promise<void>;
}
export default class JoplinWindow {
private plugin_;
private store_;
private implementation_;
constructor(implementation: Implementation, plugin: Plugin, store: any);
/**
* Loads a chrome CSS file. It will apply to the window UI elements, except
* for the note viewer. It is the same as the "Custom stylesheet for
* Joplin-wide app styles" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)
* for an example.
*/
loadChromeCssFile(filePath: string): Promise<void>;
/**
* Loads a note CSS file. It will apply to the note viewer, as well as any
* exported or printed note. It is the same as the "Custom stylesheet for
* rendered Markdown" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)
* for an example.
*/
loadNoteCssFile(filePath: string): Promise<void>;
}

View File

@@ -0,0 +1,87 @@
import { FolderEntity } from '../../database/types';
import { Disposable, MenuItem } from './types';
export interface EditContextMenuFilterObject {
items: MenuItem[];
}
type FilterHandler<T> = (object: T) => Promise<void>;
declare enum ItemChangeEventType {
Create = 1,
Update = 2,
Delete = 3
}
interface ItemChangeEvent {
id: string;
event: ItemChangeEventType;
}
interface SyncStartEvent {
withErrors: boolean;
}
interface ResourceChangeEvent {
id: string;
}
type ItemChangeHandler = (event: ItemChangeEvent) => void;
type SyncStartHandler = (event: SyncStartEvent) => void;
type ResourceChangeHandler = (event: ResourceChangeEvent) => void;
/**
* The workspace service provides access to all the parts of Joplin that
* are being worked on - i.e. the currently selected notes or notebooks as
* well as various related events, such as when a new note is selected, or
* when the note content changes.
*
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins)
*/
export default class JoplinWorkspace {
private store;
constructor(store: any);
/**
* Called when a new note or notes are selected.
*/
onNoteSelectionChange(callback: Function): Promise<Disposable>;
/**
* Called when the content of a note changes.
* @deprecated Use `onNoteChange()` instead, which is reliably triggered whenever the note content, or any note property changes.
*/
onNoteContentChange(callback: Function): Promise<void>;
/**
* Called when the content of the current note changes.
*/
onNoteChange(handler: ItemChangeHandler): Promise<Disposable>;
/**
* Called when a resource is changed. Currently this handled will not be
* called when a resource is added or deleted.
*/
onResourceChange(handler: ResourceChangeHandler): Promise<void>;
/**
* Called when an alarm associated with a to-do is triggered.
*/
onNoteAlarmTrigger(handler: Function): Promise<Disposable>;
/**
* Called when the synchronisation process is starting.
*/
onSyncStart(handler: SyncStartHandler): Promise<Disposable>;
/**
* Called when the synchronisation process has finished.
*/
onSyncComplete(callback: Function): Promise<Disposable>;
/**
* Called just before the editor context menu is about to open. Allows
* adding items to it.
*/
filterEditorContextMenu(handler: FilterHandler<EditContextMenuFilterObject>): void;
/**
* Gets the currently selected note
*/
selectedNote(): Promise<any>;
/**
* Gets the currently selected folder. In some cases, for example during
* search or when viewing a tag, no folder is actually selected in the user
* interface. In that case, that function would return the last selected
* folder.
*/
selectedFolder(): Promise<FolderEntity>;
/**
* Gets the IDs of the selected notes (can be zero, one, or many). Use the data API to retrieve information about these notes.
*/
selectedNoteIds(): Promise<string[]>;
}
export {};

View File

@@ -0,0 +1,5 @@
import type Joplin from './Joplin';
declare const joplin: Joplin;
export default joplin;

View File

@@ -0,0 +1,156 @@
import { Size } from './types';
type ListRendererDatabaseDependency = 'folder.created_time' | 'folder.encryption_applied' | 'folder.encryption_cipher_text' | 'folder.icon' | 'folder.id' | 'folder.is_shared' | 'folder.master_key_id' | 'folder.parent_id' | 'folder.share_id' | 'folder.title' | 'folder.updated_time' | 'folder.user_created_time' | 'folder.user_data' | 'folder.user_updated_time' | 'folder.type_' | 'note.altitude' | 'note.application_data' | 'note.author' | 'note.body' | 'note.conflict_original_id' | 'note.created_time' | 'note.encryption_applied' | 'note.encryption_cipher_text' | 'note.id' | 'note.is_conflict' | 'note.is_shared' | 'note.is_todo' | 'note.latitude' | 'note.longitude' | 'note.markup_language' | 'note.master_key_id' | 'note.order' | 'note.parent_id' | 'note.share_id' | 'note.source' | 'note.source_application' | 'note.source_url' | 'note.title' | 'note.todo_completed' | 'note.todo_due' | 'note.updated_time' | 'note.user_created_time' | 'note.user_data' | 'note.user_updated_time' | 'note.type_';
export declare enum ItemFlow {
TopToBottom = "topToBottom",
LeftToRight = "leftToRight"
}
export type RenderNoteView = Record<string, any>;
export interface OnChangeEvent {
elementId: string;
value: any;
noteId: string;
}
export type OnRenderNoteHandler = (props: any) => Promise<RenderNoteView>;
export type OnChangeHandler = (event: OnChangeEvent) => Promise<void>;
/**
* Most of these are the built-in note properties, such as `note.title`,
* `note.todo_completed`, etc.
*
* Additionally, the `item.*` properties are specific to the rendered item. The
* most important being `item.selected`, which you can use to display the
* selected note in a different way.
*
* Finally some special properties are provided to make it easier to render
* notes. In particular, if possible prefer `note.titleHtml` to `note.title`
* since some important processing has already been done on the string, such as
* handling the search highlighter and escaping. Since it's HTML and already
* escaped you would insert it using `{{{titleHtml}}}` (triple-mustache syntax,
* which disables escaping).
*
* `notes.tag` gives you the list of tags associated with the note.
*
* `note.isWatched` tells you if the note is currently opened in an external
* editor. In which case you would generally display some indicator.
*/
export type ListRendererDepependency = ListRendererDatabaseDependency | 'item.size.width' | 'item.size.height' | 'item.selected' | 'note.titleHtml' | 'note.isWatched' | 'note.tags';
export interface ListRenderer {
/**
* It must be unique to your plugin.
*/
id: string;
/**
* Can be top to bottom or left to right. Left to right gives you more
* option to set the size of the items since you set both its width and
* height.
*/
flow: ItemFlow;
/**
* The size of each item must be specified in advance for performance
* reasons, and cannot be changed afterwards. If the item flow is top to
* bottom, you only need to specificy the item height (the width will be
* ignored).
*/
itemSize: Size;
/**
* The CSS is relative to the list item container. What will appear in the
* page is essentially `.note-list-item { YOUR_CSS; }`. It means you can use
* child combinator with guarantee it will only apply to your own items. In
* this example, the styling will apply to `.note-list-item > .content`:
*
* ```css
* > .content {
* padding: 10px;
* }
* ```
*
* In order to get syntax highlighting working here, it's recommended
* installing an editor extension such as [es6-string-html VSCode
* extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
*/
itemCss?: string;
/**
* List the dependencies that your plugin needs to render the note list
* items. Only these will be passed to your `onRenderNote` handler. Ensure
* that you do not add more than what you need since there is a performance
* penalty for each property.
*/
dependencies: ListRendererDepependency[];
/**
* This is the HTML template that will be used to render the note list item.
* This is a [Mustache template](https://github.com/janl/mustache.js) and it
* will receive the variable you return from `onRenderNote` as tags. For
* example, if you return a property named `formattedDate` from
* `onRenderNote`, you can insert it in the template using `Created date:
* {{formattedDate}}`.
*
* In order to get syntax highlighting working here, it's recommended
* installing an editor extension such as [es6-string-html VSCode
* extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
*/
itemTemplate: string;
/**
* This user-facing text is used for example in the View menu, so that your
* renderer can be selected.
*/
label: () => Promise<string>;
/**
* This is where most of the real-time processing will happen. When a note
* is rendered for the first time and every time it changes, this handler
* receives the properties specified in the `dependencies` property. You can
* then process them, load any additional data you need, and once done you
* need to return the properties that are needed in the `itemTemplate` HTML.
* Again, to use the formatted date example, you could have such a renderer:
*
* ```typescript
* dependencies: [
* 'note.title',
* 'note.created_time',
* ],
*
* itemTemplate: // html
* `
* <div>
* Title: {{note.title}}<br/>
* Date: {{formattedDate}}
* </div>
* `,
*
* onRenderNote: async (props: any) => {
* const formattedDate = dayjs(props.note.created_time).format();
* return {
* // Also return the props, so that note.title is available from the
* // template
* ...props,
* formattedDate,
* }
* },
* ```
*/
onRenderNote: OnRenderNoteHandler;
/**
* This handler allows adding some interacivity to the note renderer -
* whenever an input element within the item is changed (for example, when a
* checkbox is clicked, or a text input is changed), this `onChange` handler
* is going to be called.
*
* You can inspect `event.elementId` to know which element had some changes,
* and `event.value` to know the new value. `event.noteId` also tells you
* what note is affected, so that you can potentially apply changes to it.
*
* You specify the element ID, by setting a `data-id` attribute on the
* input.
*
* For example, if you have such a template:
*
* ```html
* <div>
* <input type="text" value="{{note.title}}" data-id="noteTitleInput"/>
* </div>
* ```
*
* The event handler will receive an event with `elementId` set to
* `noteTitleInput`.
*/
onChange?: OnChangeHandler;
}
export {};

View File

@@ -0,0 +1,181 @@
/* eslint-disable multiline-comment-style */
import { Size } from './types';
// AUTO-GENERATED by generate-database-type
type ListRendererDatabaseDependency = 'folder.created_time' | 'folder.encryption_applied' | 'folder.encryption_cipher_text' | 'folder.icon' | 'folder.id' | 'folder.is_shared' | 'folder.master_key_id' | 'folder.parent_id' | 'folder.share_id' | 'folder.title' | 'folder.updated_time' | 'folder.user_created_time' | 'folder.user_data' | 'folder.user_updated_time' | 'folder.type_' | 'note.altitude' | 'note.application_data' | 'note.author' | 'note.body' | 'note.conflict_original_id' | 'note.created_time' | 'note.encryption_applied' | 'note.encryption_cipher_text' | 'note.id' | 'note.is_conflict' | 'note.is_shared' | 'note.is_todo' | 'note.latitude' | 'note.longitude' | 'note.markup_language' | 'note.master_key_id' | 'note.order' | 'note.parent_id' | 'note.share_id' | 'note.source' | 'note.source_application' | 'note.source_url' | 'note.title' | 'note.todo_completed' | 'note.todo_due' | 'note.updated_time' | 'note.user_created_time' | 'note.user_data' | 'note.user_updated_time' | 'note.type_';
// AUTO-GENERATED by generate-database-type
export enum ItemFlow {
TopToBottom = 'topToBottom',
LeftToRight = 'leftToRight',
}
export type RenderNoteView = Record<string, any>;
export interface OnChangeEvent {
elementId: string;
value: any;
noteId: string;
}
export type OnRenderNoteHandler = (props: any)=> Promise<RenderNoteView>;
export type OnChangeHandler = (event: OnChangeEvent)=> Promise<void>;
/**
* Most of these are the built-in note properties, such as `note.title`,
* `note.todo_completed`, etc.
*
* Additionally, the `item.*` properties are specific to the rendered item. The
* most important being `item.selected`, which you can use to display the
* selected note in a different way.
*
* Finally some special properties are provided to make it easier to render
* notes. In particular, if possible prefer `note.titleHtml` to `note.title`
* since some important processing has already been done on the string, such as
* handling the search highlighter and escaping. Since it's HTML and already
* escaped you would insert it using `{{{titleHtml}}}` (triple-mustache syntax,
* which disables escaping).
*
* `notes.tag` gives you the list of tags associated with the note.
*
* `note.isWatched` tells you if the note is currently opened in an external
* editor. In which case you would generally display some indicator.
*/
export type ListRendererDepependency =
ListRendererDatabaseDependency |
'item.size.width' |
'item.size.height' |
'item.selected' |
'note.titleHtml' |
'note.isWatched' |
'note.tags';
export interface ListRenderer {
/**
* It must be unique to your plugin.
*/
id: string;
/**
* Can be top to bottom or left to right. Left to right gives you more
* option to set the size of the items since you set both its width and
* height.
*/
flow: ItemFlow;
/**
* The size of each item must be specified in advance for performance
* reasons, and cannot be changed afterwards. If the item flow is top to
* bottom, you only need to specificy the item height (the width will be
* ignored).
*/
itemSize: Size;
/**
* The CSS is relative to the list item container. What will appear in the
* page is essentially `.note-list-item { YOUR_CSS; }`. It means you can use
* child combinator with guarantee it will only apply to your own items. In
* this example, the styling will apply to `.note-list-item > .content`:
*
* ```css
* > .content {
* padding: 10px;
* }
* ```
*
* In order to get syntax highlighting working here, it's recommended
* installing an editor extension such as [es6-string-html VSCode
* extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
*/
itemCss?: string;
/**
* List the dependencies that your plugin needs to render the note list
* items. Only these will be passed to your `onRenderNote` handler. Ensure
* that you do not add more than what you need since there is a performance
* penalty for each property.
*/
dependencies: ListRendererDepependency[];
/**
* This is the HTML template that will be used to render the note list item.
* This is a [Mustache template](https://github.com/janl/mustache.js) and it
* will receive the variable you return from `onRenderNote` as tags. For
* example, if you return a property named `formattedDate` from
* `onRenderNote`, you can insert it in the template using `Created date:
* {{formattedDate}}`.
*
* In order to get syntax highlighting working here, it's recommended
* installing an editor extension such as [es6-string-html VSCode
* extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
*/
itemTemplate: string;
/**
* This user-facing text is used for example in the View menu, so that your
* renderer can be selected.
*/
label: ()=> Promise<string>;
/**
* This is where most of the real-time processing will happen. When a note
* is rendered for the first time and every time it changes, this handler
* receives the properties specified in the `dependencies` property. You can
* then process them, load any additional data you need, and once done you
* need to return the properties that are needed in the `itemTemplate` HTML.
* Again, to use the formatted date example, you could have such a renderer:
*
* ```typescript
* dependencies: [
* 'note.title',
* 'note.created_time',
* ],
*
* itemTemplate: // html
* `
* <div>
* Title: {{note.title}}<br/>
* Date: {{formattedDate}}
* </div>
* `,
*
* onRenderNote: async (props: any) => {
* const formattedDate = dayjs(props.note.created_time).format();
* return {
* // Also return the props, so that note.title is available from the
* // template
* ...props,
* formattedDate,
* }
* },
* ```
*/
onRenderNote: OnRenderNoteHandler;
/**
* This handler allows adding some interacivity to the note renderer -
* whenever an input element within the item is changed (for example, when a
* checkbox is clicked, or a text input is changed), this `onChange` handler
* is going to be called.
*
* You can inspect `event.elementId` to know which element had some changes,
* and `event.value` to know the new value. `event.noteId` also tells you
* what note is affected, so that you can potentially apply changes to it.
*
* You specify the element ID, by setting a `data-id` attribute on the
* input.
*
* For example, if you have such a template:
*
* ```html
* <div>
* <input type="text" value="{{note.title}}" data-id="noteTitleInput"/>
* </div>
* ```
*
* The event handler will receive an event with `elementId` set to
* `noteTitleInput`.
*/
onChange?: OnChangeHandler;
}

View File

@@ -0,0 +1,748 @@
/* eslint-disable multiline-comment-style */
// =================================================================
// Command API types
// =================================================================
export interface Command {
/**
* Name of command - must be globally unique
*/
name: string;
/**
* Label to be displayed on menu items or keyboard shortcut editor for example.
* If it is missing, it's assumed it's a private command, to be called programmatically only.
* In that case the command will not appear in the shortcut editor or command panel, and logically
* should not be used as a menu item.
*/
label?: string;
/**
* Icon to be used on toolbar buttons for example
*/
iconName?: string;
/**
* Code to be ran when the command is executed. It may return a result.
*/
execute(...args: any[]): Promise<any | void>;
/**
* Defines whether the command should be enabled or disabled, which in turns
* affects the enabled state of any associated button or menu item.
*
* The condition should be expressed as a "when-clause" (as in Visual Studio
* Code). It's a simple boolean expression that evaluates to `true` or
* `false`. It supports the following operators:
*
* Operator | Symbol | Example
* -- | -- | --
* Equality | == | "editorType == markdown"
* Inequality | != | "currentScreen != config"
* Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
* And | && | "oneNoteSelected && !inConflictFolder"
*
* Joplin, unlike VSCode, also supports parentheses, which allows creating
* more complex expressions such as `cond1 || (cond2 && cond3)`. Only one
* level of parentheses is possible (nested ones aren't supported).
*
* Currently the supported context variables aren't documented, but you can
* find the list below:
*
* - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts)
* - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts)
*
* Note: Commands are enabled by default unless you use this property.
*/
enabledCondition?: string;
}
// =================================================================
// Interop API types
// =================================================================
export enum FileSystemItem {
File = 'file',
Directory = 'directory',
}
export enum ImportModuleOutputFormat {
Markdown = 'md',
Html = 'html',
}
/**
* Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export) for an example.
*
* In general, all the event handlers you'll need to implement take a `context` object as a first argument. This object will contain the export or import path as well as various optional properties, such as which notes or notebooks need to be exported.
*
* To get a better sense of what it will contain it can be useful to print it using `console.info(context)`.
*/
export interface ExportModule {
/**
* The format to be exported, eg "enex", "jex", "json", etc.
*/
format: string;
/**
* The description that will appear in the UI, for example in the menu item.
*/
description: string;
/**
* Whether the module will export a single file or multiple files in a directory. It affects the open dialog that will be presented to the user when using your exporter.
*/
target: FileSystemItem;
/**
* Only applies to single file exporters or importers
* It tells whether the format can package multiple notes into one file.
* For example JEX or ENEX can, but HTML cannot.
*/
isNoteArchive: boolean;
/**
* The extensions of the files exported by your module. For example, it is `["htm", "html"]` for the HTML module, and just `["jex"]` for the JEX module.
*/
fileExtensions?: string[];
/**
* Called when the export process starts.
*/
onInit(context: ExportContext): Promise<void>;
/**
* Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc.
*/
onProcessItem(context: ExportContext, itemType: number, item: any): Promise<void>;
/**
* Called when a resource file needs to be exported.
*/
onProcessResource(context: ExportContext, resource: any, filePath: string): Promise<void>;
/**
* Called when the export process is done.
*/
onClose(context: ExportContext): Promise<void>;
}
export interface ImportModule {
/**
* The format to be exported, eg "enex", "jex", "json", etc.
*/
format: string;
/**
* The description that will appear in the UI, for example in the menu item.
*/
description: string;
/**
* Only applies to single file exporters or importers
* It tells whether the format can package multiple notes into one file.
* For example JEX or ENEX can, but HTML cannot.
*/
isNoteArchive: boolean;
/**
* The type of sources that are supported by the module. Tells whether the module can import files or directories or both.
*/
sources: FileSystemItem[];
/**
* Tells the file extensions of the exported files.
*/
fileExtensions?: string[];
/**
* Tells the type of notes that will be generated, either HTML or Markdown (default).
*/
outputFormat?: ImportModuleOutputFormat;
/**
* Called when the import process starts. There is only one event handler within which you should import the complete data.
*/
onExec(context: ImportContext): Promise<void>;
}
export interface ExportOptions {
format?: string;
path?: string;
sourceFolderIds?: string[];
sourceNoteIds?: string[];
// modulePath?: string;
target?: FileSystemItem;
}
export interface ExportContext {
destPath: string;
options: ExportOptions;
/**
* You can attach your own custom data using this propery - it will then be passed to each event handler, allowing you to keep state from one event to the next.
*/
userData?: any;
}
export interface ImportContext {
sourcePath: string;
options: any;
warnings: string[];
}
// =================================================================
// Misc types
// =================================================================
export interface Script {
onStart?(event: any): Promise<void>;
}
export interface Disposable {
// dispose():void;
}
export enum ModelType {
Note = 1,
Folder = 2,
Setting = 3,
Resource = 4,
Tag = 5,
NoteTag = 6,
Search = 7,
Alarm = 8,
MasterKey = 9,
ItemChange = 10,
NoteResource = 11,
ResourceLocalState = 12,
Revision = 13,
Migration = 14,
SmartFilter = 15,
Command = 16,
}
export interface VersionInfo {
version: string;
profileVersion: number;
syncVersion: number;
}
// =================================================================
// Menu types
// =================================================================
export interface CreateMenuItemOptions {
accelerator: string;
}
export enum MenuItemLocation {
File = 'file',
Edit = 'edit',
View = 'view',
Note = 'note',
Tools = 'tools',
Help = 'help',
/**
* @deprecated Do not use - same as NoteListContextMenu
*/
Context = 'context',
// If adding an item here, don't forget to update isContextMenuItemLocation()
/**
* When a command is called from the note list context menu, the
* command will receive the following arguments:
*
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
*/
NoteListContextMenu = 'noteListContextMenu',
EditorContextMenu = 'editorContextMenu',
/**
* When a command is called from a folder context menu, the
* command will receive the following arguments:
*
* - `folderId:string`: ID of the folder that was right-clicked on
*/
FolderContextMenu = 'folderContextMenu',
/**
* When a command is called from a tag context menu, the
* command will receive the following arguments:
*
* - `tagId:string`: ID of the tag that was right-clicked on
*/
TagContextMenu = 'tagContextMenu',
}
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
return [
MenuItemLocation.Context,
MenuItemLocation.NoteListContextMenu,
MenuItemLocation.EditorContextMenu,
MenuItemLocation.FolderContextMenu,
MenuItemLocation.TagContextMenu,
].includes(location);
}
export interface MenuItem {
/**
* Command that should be associated with the menu item. All menu item should
* have a command associated with them unless they are a sub-menu.
*/
commandName?: string;
/**
* Arguments that should be passed to the command. They will be as rest
* parameters.
*/
commandArgs?: any[];
/**
* Set to "separator" to create a divider line
*/
type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio');
/**
* Accelerator associated with the menu item
*/
accelerator?: string;
/**
* Menu items that should appear below this menu item. Allows creating a menu tree.
*/
submenu?: MenuItem[];
/**
* Menu item label. If not specified, the command label will be used instead.
*/
label?: string;
}
// =================================================================
// View API types
// =================================================================
export interface ButtonSpec {
id: ButtonId;
title?: string;
onClick?(): void;
}
export type ButtonId = string;
export enum ToolbarButtonLocation {
/**
* This toolbar in the top right corner of the application. It applies to the note as a whole, including its metadata.
*/
NoteToolbar = 'noteToolbar',
/**
* This toolbar is right above the text editor. It applies to the note body only.
*/
EditorToolbar = 'editorToolbar',
}
export type ViewHandle = string;
export interface EditorCommand {
name: string;
value?: any;
}
export interface DialogResult {
id: ButtonId;
formData?: any;
}
export interface Size {
width?: number;
height?: number;
}
export interface Rectangle {
x?: number;
y?: number;
width?: number;
height?: number;
}
// =================================================================
// Settings types
// =================================================================
export enum SettingItemType {
Int = 1,
String = 2,
Bool = 3,
Array = 4,
Object = 5,
Button = 6,
}
export enum SettingItemSubType {
FilePathAndArgs = 'file_path_and_args',
FilePath = 'file_path', // Not supported on mobile!
DirectoryPath = 'directory_path', // Not supported on mobile!
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
Cli = 'cli',
}
export enum SettingStorage {
Database = 1,
File = 2,
}
// Redefine a simplified interface to mask internal details
// and to remove function calls as they would have to be async.
export interface SettingItem {
value: any;
type: SettingItemType;
/**
* Currently only used to display a file or directory selector. Always set
* `type` to `SettingItemType.String` when using this property.
*/
subType?: SettingItemSubType;
label: string;
description?: string;
/**
* A public setting will appear in the Configuration screen and will be
* modifiable by the user. A private setting however will not appear there,
* and can only be changed programmatically. You may use this to store some
* values that you do not want to directly expose.
*/
public: boolean;
/**
* You would usually set this to a section you would have created
* specifically for the plugin.
*/
section?: string;
/**
* To create a setting with multiple options, set this property to `true`.
* That setting will render as a dropdown list in the configuration screen.
*/
isEnum?: boolean;
/**
* This property is required when `isEnum` is `true`. In which case, it
* should contain a map of value => label.
*/
options?: Record<any, any>;
/**
* Reserved property. Not used at the moment.
*/
appTypes?: AppType[];
/**
* Set this to `true` to store secure data, such as passwords. Any such
* setting will be stored in the system keychain if one is available.
*/
secure?: boolean;
/**
* An advanced setting will be moved under the "Advanced" button in the
* config screen.
*/
advanced?: boolean;
/**
* Set the min, max and step values if you want to restrict an int setting
* to a particular range.
*/
minimum?: number;
maximum?: number;
step?: number;
/**
* Either store the setting in the database or in settings.json. Defaults to database.
*/
storage?: SettingStorage;
}
export interface SettingSection {
label: string;
iconName?: string;
description?: string;
name?: string;
}
// =================================================================
// Data API types
// =================================================================
/**
* An array of at least one element and at most three elements.
*
* - **[0]**: Resource name (eg. "notes", "folders", "tags", etc.)
* - **[1]**: (Optional) Resource ID.
* - **[2]**: (Optional) Resource link.
*/
export type Path = string[];
// =================================================================
// Content Script types
// =================================================================
export type PostMessageHandler = (message: any)=> Promise<any>;
/**
* When a content script is initialised, it receives a `context` object.
*/
export interface ContentScriptContext {
/**
* The plugin ID that registered this content script
*/
pluginId: string;
/**
* The content script ID, which may be necessary to post messages
*/
contentScriptId: string;
/**
* Can be used by CodeMirror content scripts to post a message to the plugin
*/
postMessage: PostMessageHandler;
}
export interface ContentScriptModuleLoadedEvent {
userData?: any;
}
export interface ContentScriptModule {
onLoaded?: (event: ContentScriptModuleLoadedEvent)=> void;
plugin: ()=> any;
assets?: ()=> void;
}
export interface MarkdownItContentScriptModule extends Omit<ContentScriptModule, 'plugin'> {
plugin: (markdownIt: any, options: any)=> any;
}
type EditorCommandCallback = (...args: any[])=> any;
export interface CodeMirrorControl {
/** Points to a CodeMirror 6 EditorView instance. */
editor: any;
cm6: any;
/** `extension` should be a [CodeMirror 6 extension](https://codemirror.net/docs/ref/#state.Extension). */
addExtension(extension: any|any[]): void;
supportsCommand(name: string): boolean;
execCommand(name: string, ...args: any[]): any;
registerCommand(name: string, callback: EditorCommandCallback): void;
joplinExtensions: {
/**
* Returns a [CodeMirror 6 extension](https://codemirror.net/docs/ref/#state.Extension) that
* registers the given [CompletionSource](https://codemirror.net/docs/ref/#autocomplete.CompletionSource).
*
* Use this extension rather than the built-in CodeMirror [`autocompletion`](https://codemirror.net/docs/ref/#autocomplete.autocompletion)
* if you don't want to use [langaugeData-based autocompletion](https://codemirror.net/docs/ref/#autocomplete.autocompletion^config.override).
*
* Using `autocompletion({ override: [ ... ]})` causes errors when done by multiple plugins.
*/
completionSource(completionSource: any): any;
/**
* Creates an extension that enables or disables [`languageData`-based autocompletion](https://codemirror.net/docs/ref/#autocomplete.autocompletion^config.override).
*/
enableLanguageDataAutocomplete: { of: (enabled: boolean)=> any };
};
}
export interface MarkdownEditorContentScriptModule extends Omit<ContentScriptModule, 'plugin'> {
plugin: (editorControl: CodeMirrorControl)=> void;
}
export enum ContentScriptType {
/**
* Registers a new Markdown-It plugin, which should follow the template
* below.
*
* ```javascript
* module.exports = {
* default: function(context) {
* return {
* plugin: function(markdownIt, pluginOptions) {
* // ...
* },
* assets: {
* // ...
* },
* }
* }
* }
* ```
*
* See [the
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
* for a simple Markdown-it plugin example.
*
* ## Exported members
*
* - The `context` argument is currently unused but could be used later on
* to provide access to your own plugin so that the content script and
* plugin can communicate.
*
* - The **required** `plugin` key is the actual Markdown-It plugin - check
* the [official doc](https://github.com/markdown-it/markdown-it) for more
* information.
*
* - Using the **optional** `assets` key you may specify assets such as JS
* or CSS that should be loaded in the rendered HTML document. Check for
* example the Joplin [Mermaid
* plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
* to see how the data should be structured.
*
* ## Getting the settings from the renderer
*
* You can access your plugin settings from the renderer by calling
* `pluginOptions.settingValue("your-setting-key')`.
*
* ## Posting messages from the content script to your plugin
*
* The application provides the following function to allow executing
* commands from the rendered HTML code:
*
* ```javascript
* const response = await webviewApi.postMessage(contentScriptId, message);
* ```
*
* - `contentScriptId` is the ID you've defined when you registered the
* content script. You can retrieve it from the
* {@link ContentScriptContext | context}.
* - `message` can be any basic JavaScript type (number, string, plain
* object), but it cannot be a function or class instance.
*
* When you post a message, the plugin can send back a `response` thus
* allowing two-way communication:
*
* ```javascript
* await joplin.contentScripts.onMessage(contentScriptId, (message) => {
* // Process message
* return response; // Can be any object, string or number
* });
* ```
*
* See {@link JoplinContentScripts.onMessage} for more details, as well as
* the [postMessage
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
*
* ## Registering an existing Markdown-it plugin
*
* To include a regular Markdown-It plugin, that doesn't make use of any
* Joplin-specific features, you would simply create a file such as this:
*
* ```javascript
* module.exports = {
* default: function(context) {
* return {
* plugin: require('markdown-it-toc-done-right');
* }
* }
* }
* ```
*/
MarkdownItPlugin = 'markdownItPlugin',
/**
* Registers a new CodeMirror plugin, which should follow the template
* below.
*
* ```javascript
* module.exports = {
* default: function(context) {
* return {
* plugin: function(CodeMirror) {
* // ...
* },
* codeMirrorResources: [],
* codeMirrorOptions: {
* // ...
* },
* assets: {
* // ...
* },
* }
* }
* }
* ```
*
* - The `context` argument is currently unused but could be used later on
* to provide access to your own plugin so that the content script and
* plugin can communicate.
*
* - The `plugin` key is your CodeMirror plugin. This is where you can
* register new commands with CodeMirror or interact with the CodeMirror
* instance as needed.
*
* - The `codeMirrorResources` key is an array of CodeMirror resources that
* will be loaded and attached to the CodeMirror module. These are made up
* of addons, keymaps, and modes. For example, for a plugin that want's to
* enable clojure highlighting in code blocks. `codeMirrorResources` would
* be set to `['mode/clojure/clojure']`.
*
* - The `codeMirrorOptions` key contains all the
* [CodeMirror](https://codemirror.net/doc/manual.html#config) options
* that will be set or changed by this plugin. New options can alse be
* declared via
* [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption),
* and then have their value set here. For example, a plugin that enables
* line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`.
*
* - Using the **optional** `assets` key you may specify **only** CSS assets
* that should be loaded in the rendered HTML document. Check for example
* the Joplin [Mermaid
* plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
* to see how the data should be structured.
*
* One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys
* must be provided for the plugin to be valid. Having multiple or all
* provided is also okay.
*
* See also the [demo
* plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
* for an example of all these keys being used in one plugin.
*
* ## Posting messages from the content script to your plugin
*
* In order to post messages to the plugin, you can use the postMessage
* function passed to the {@link ContentScriptContext | context}.
*
* ```javascript
* const response = await context.postMessage('messageFromCodeMirrorContentScript');
* ```
*
* When you post a message, the plugin can send back a `response` thus
* allowing two-way communication:
*
* ```javascript
* await joplin.contentScripts.onMessage(contentScriptId, (message) => {
* // Process message
* return response; // Can be any object, string or number
* });
* ```
*
* See {@link JoplinContentScripts.onMessage} for more details, as well as
* the [postMessage
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
*
*/
CodeMirrorPlugin = 'codeMirrorPlugin',
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
{
"name": "joplin-plugin-worker-example",
"version": "1.0.0",
"scripts": {
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && npm run copyAssets && webpack --env joplin-plugin-config=createArchive",
"copyAssets": "node tools/copyAssets.js",
"prepare": "npm run dist",
"updateVersion": "webpack --env joplin-plugin-config=updateVersion",
"update": "npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force"
},
"license": "MIT",
"keywords": [
"joplin-plugin"
],
"files": [
"publish"
],
"devDependencies": {
"@types/node": "^18.7.13",
"chalk": "^4.1.0",
"copy-webpack-plugin": "^11.0.0",
"fs-extra": "^10.1.0",
"glob": "^8.0.3",
"tar": "^6.1.11",
"ts-loader": "^9.3.1",
"typescript": "^4.8.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"overrides": {
"@xenova/transformers": {
"sharp": "0.33.4"
}
},
"dependencies": {
"@xenova/transformers": "^2.17.2"
}
}

View File

@@ -0,0 +1,6 @@
{
"extraScripts": [ "worker.ts" ],
"webpackOverrides": {
"target": "web"
}
}

View File

@@ -0,0 +1,39 @@
import joplin from 'api';
import { ToolbarButtonLocation } from 'api/types';
joplin.plugins.register({
onStart: async function() {
const worker = new Worker(`${await joplin.plugins.installationDir()}/worker.js`);
worker.onmessage = event => {
if ('response' in event.data) {
alert(
JSON.stringify(event.data.response)
);
}
};
joplin.commands.register({
name: 'classifySelection',
label: 'Run sentiment analysis on selected text',
iconName: 'fas fa-robot',
execute: async () => {
const selectedText = await joplin.commands.execute('selectedText');
if (!selectedText) {
alert('No text selected!');
return;
}
worker.postMessage({
type: 'classify',
text: selectedText,
});
},
});
await joplin.views.toolbarButtons.create(
'classifySelectionButton',
'classifySelection',
ToolbarButtonLocation.EditorToolbar,
);
},
});

View File

@@ -0,0 +1,15 @@
{
"manifest_version": 1,
"id": "com.example.worker",
"app_min_version": "3.0",
"version": "1.0.0",
"name": "Web Worker",
"description": "Shows how to load a web worker from a plugin.",
"author": "",
"homepage_url": "",
"repository_url": "",
"keywords": [],
"categories": [],
"screenshots": [],
"icons": {}
}

View File

@@ -0,0 +1,30 @@
// @ts-ignore -- no types available for transformers.js
import { pipeline, env } from '@xenova/transformers';
// Use code similar to the following to use local models
// See https://github.com/xenova/transformers.js?tab=readme-ov-file#settings
// env.localModelPath = './models/';
// env.allowRemoteModels = false;
// Don't fetch ONNX WASM from the internet (see tools/copyModels.js).
env.backends.onnx.wasm.wasmPaths = './onnx-dist/';
let pipe: (text: string)=>Promise<unknown>;
const classify = async (text: string) => {
pipe ??= await pipeline('sentiment-analysis');
const classification = await pipe(text);
console.debug('classified', text, 'as', classification);
return classification;
};
self.addEventListener('message', async (event) => {
console.debug('got message', event.data);
if (event.data.type === 'classify') {
postMessage({
response: await classify(event.data.text),
});
}
});

View File

@@ -0,0 +1,12 @@
const fs = require('fs-extra');
const path = require('path');
// Copy WASM files
// See https://github.com/xenova/transformers.js/issues/367#issuecomment-1777743129
const onnxDistFiles = path.dirname(require.resolve('onnxruntime-web'));
const baseDir = path.dirname(__dirname);
fs.copy(onnxDistFiles, path.join(baseDir, 'dist', 'onnx-dist'));
// Predownload model files -- TODO.
// See https://huggingface.co/docs/transformers.js/custom_usage#use-custom-models

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "./dist/",
"module": "commonjs",
"target": "es2015",
"jsx": "react",
"allowJs": true,
"baseUrl": "."
}
}

View File

@@ -0,0 +1,403 @@
// -----------------------------------------------------------------------------
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
// is recommended not to edit this file as it would be overwritten when updating
// the plugin framework. If you do make some changes, consider using an external
// JS file and requiring it here to minimize the changes. That way when you
// update, you can easily restore the functionality you've added.
// -----------------------------------------------------------------------------
/* eslint-disable no-console */
const path = require('path');
const crypto = require('crypto');
const fs = require('fs-extra');
const chalk = require('chalk');
const CopyPlugin = require('copy-webpack-plugin');
const tar = require('tar');
const glob = require('glob');
const execSync = require('child_process').execSync;
// AUTO-GENERATED by updateCategories
const allPossibleCategories = [{ 'name': 'appearance' }, { 'name': 'developer tools' }, { 'name': 'productivity' }, { 'name': 'themes' }, { 'name': 'integrations' }, { 'name': 'viewer' }, { 'name': 'search' }, { 'name': 'tags' }, { 'name': 'editor' }, { 'name': 'files' }, { 'name': 'personal knowledge management' }];
// AUTO-GENERATED by updateCategories
const rootDir = path.resolve(__dirname);
const userConfigFilename = './plugin.config.json';
const userConfigPath = path.resolve(rootDir, userConfigFilename);
const distDir = path.resolve(rootDir, 'dist');
const srcDir = path.resolve(rootDir, 'src');
const publishDir = path.resolve(rootDir, 'publish');
const userConfig = { extraScripts: [], ...(fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {}) };
const manifestPath = `${srcDir}/manifest.json`;
const packageJsonPath = `${rootDir}/package.json`;
const allPossibleScreenshotsType = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
const manifest = readManifest(manifestPath);
const pluginArchiveFilePath = path.resolve(publishDir, `${manifest.id}.jpl`);
const pluginInfoFilePath = path.resolve(publishDir, `${manifest.id}.json`);
const { builtinModules } = require('node:module');
// Webpack5 doesn't polyfill by default and displays a warning when attempting to require() built-in
// node modules. Set these to false to prevent Webpack from warning about not polyfilling these modules.
// We don't need to polyfill because the plugins run in Electron's Node environment.
const moduleFallback = {};
for (const moduleName of builtinModules) {
moduleFallback[moduleName] = false;
}
const getPackageJson = () => {
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
};
function validatePackageJson() {
const content = getPackageJson();
if (!content.name || content.name.indexOf('joplin-plugin-') !== 0) {
console.warn(chalk.yellow(`WARNING: To publish the plugin, the package name should start with "joplin-plugin-" (found "${content.name}") in ${packageJsonPath}`));
}
if (!content.keywords || content.keywords.indexOf('joplin-plugin') < 0) {
console.warn(chalk.yellow(`WARNING: To publish the plugin, the package keywords should include "joplin-plugin" (found "${JSON.stringify(content.keywords)}") in ${packageJsonPath}`));
}
if (content.scripts && content.scripts.postinstall) {
console.warn(chalk.yellow(`WARNING: package.json contains a "postinstall" script. It is recommended to use a "prepare" script instead so that it is executed before publish. In ${packageJsonPath}`));
}
}
function fileSha256(filePath) {
const content = fs.readFileSync(filePath);
return crypto.createHash('sha256').update(content).digest('hex');
}
function currentGitInfo() {
try {
let branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
const commit = execSync('git rev-parse HEAD', { stdio: 'pipe' }).toString().trim();
if (branch === 'HEAD') branch = 'master';
return `${branch}:${commit}`;
} catch (error) {
const messages = error.message ? error.message.split('\n') : [''];
console.info(chalk.cyan('Could not get git commit (not a git repo?):', messages[0].trim()));
console.info(chalk.cyan('Git information will not be stored in plugin info file'));
return '';
}
}
function validateCategories(categories) {
if (!categories) return null;
if ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');
// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
categories.forEach(category => {
if (!allPossibleCategories.map(category => { return category.name; }).includes(category)) throw new Error(`${category} is not a valid category. Please make sure that the category name is lowercase. Valid categories are: \n${allPossibleCategories.map(category => { return category.name; })}\n`);
});
}
function validateScreenshots(screenshots) {
if (!screenshots) return null;
for (const screenshot of screenshots) {
if (!screenshot.src) throw new Error('You must specify a src for each screenshot');
// Avoid attempting to download and verify URL screenshots.
if (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {
continue;
}
const screenshotType = screenshot.src.split('.').pop();
if (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \n${allPossibleScreenshotsType}\n`);
const screenshotPath = path.resolve(rootDir, screenshot.src);
// Max file size is 1MB
const fileMaxSize = 1024;
const fileSize = fs.statSync(screenshotPath).size / 1024;
if (fileSize > fileMaxSize) throw new Error(`Max screenshot file size is ${fileMaxSize}KB. ${screenshotPath} is ${fileSize}KB`);
}
}
function readManifest(manifestPath) {
const content = fs.readFileSync(manifestPath, 'utf8');
const output = JSON.parse(content);
if (!output.id) throw new Error(`Manifest plugin ID is not set in ${manifestPath}`);
validateCategories(output.categories);
validateScreenshots(output.screenshots);
return output;
}
function createPluginArchive(sourceDir, destPath) {
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true, windowsPathsNoEscape: true })
.map(f => f.substr(sourceDir.length + 1));
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
fs.removeSync(destPath);
tar.create(
{
strict: true,
portable: true,
file: destPath,
cwd: sourceDir,
sync: true,
},
distFiles,
);
console.info(chalk.cyan(`Plugin archive has been created in ${destPath}`));
}
const writeManifest = (manifestPath, content) => {
fs.writeFileSync(manifestPath, JSON.stringify(content, null, '\t'), 'utf8');
};
function createPluginInfo(manifestPath, destPath, jplFilePath) {
const contentText = fs.readFileSync(manifestPath, 'utf8');
const content = JSON.parse(contentText);
content._publish_hash = `sha256:${fileSha256(jplFilePath)}`;
content._publish_commit = currentGitInfo();
writeManifest(destPath, content);
}
function onBuildCompleted() {
try {
fs.removeSync(path.resolve(publishDir, 'index.js'));
createPluginArchive(distDir, pluginArchiveFilePath);
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
validatePackageJson();
} catch (error) {
console.error(chalk.red(error.message));
}
}
const baseConfig = {
mode: 'production',
target: 'node',
stats: 'errors-only',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
...userConfig.webpackOverrides,
};
const pluginConfig = { ...baseConfig, entry: './src/index.ts',
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
fallback: moduleFallback,
// JSON files can also be required from scripts so we include this.
// https://github.com/joplin/plugin-bibtex/pull/2
extensions: ['.js', '.tsx', '.ts', '.json'],
},
output: {
filename: 'index.js',
path: distDir,
},
plugins: [
new CopyPlugin({
patterns: [
{
from: '**/*',
context: path.resolve(__dirname, 'src'),
to: path.resolve(__dirname, 'dist'),
globOptions: {
ignore: [
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
'**/*.ts',
'**/*.tsx',
],
},
},
],
}),
] };
// These libraries can be included with require(...) or
// joplin.require(...) from content scripts.
const externalContentScriptLibraries = [
'@codemirror/view',
'@codemirror/state',
'@codemirror/search',
'@codemirror/language',
'@codemirror/autocomplete',
'@codemirror/commands',
'@codemirror/highlight',
'@codemirror/lint',
'@codemirror/lang-html',
'@codemirror/lang-markdown',
'@codemirror/language-data',
'@lezer/common',
'@lezer/markdown',
'@lezer/highlight',
];
const extraScriptExternals = {};
for (const library of externalContentScriptLibraries) {
extraScriptExternals[library] = { commonjs: library };
}
const extraScriptConfig = {
...baseConfig,
resolve: {
alias: {
api: path.resolve(__dirname, 'api'),
},
fallback: moduleFallback,
extensions: ['.js', '.tsx', '.ts', '.json'],
},
// We support requiring @codemirror/... libraries through require('@codemirror/...')
externalsType: 'commonjs',
externals: extraScriptExternals,
};
const createArchiveConfig = {
stats: 'errors-only',
entry: './dist/index.js',
resolve: {
fallback: moduleFallback,
},
output: {
filename: 'index.js',
path: publishDir,
},
plugins: [{
apply(compiler) {
compiler.hooks.done.tap('archiveOnBuildListener', onBuildCompleted);
},
}],
};
function resolveExtraScriptPath(name) {
const relativePath = `./src/${name}`;
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
const s = name.split('.');
s.pop();
const nameNoExt = s.join('.');
return {
entry: relativePath,
output: {
filename: `${nameNoExt}.js`,
path: distDir,
library: 'default',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
};
}
function buildExtraScriptConfigs(userConfig) {
if (!userConfig.extraScripts.length) return [];
const output = [];
for (const scriptName of userConfig.extraScripts) {
const scriptPaths = resolveExtraScriptPath(scriptName);
output.push({ ...extraScriptConfig, entry: scriptPaths.entry,
output: scriptPaths.output });
}
return output;
}
const increaseVersion = version => {
try {
const s = version.split('.');
const d = Number(s[s.length - 1]) + 1;
s[s.length - 1] = `${d}`;
return s.join('.');
} catch (error) {
error.message = `Could not parse version number: ${version}: ${error.message}`;
throw error;
}
};
const updateVersion = () => {
const packageJson = getPackageJson();
packageJson.version = increaseVersion(packageJson.version);
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
const manifest = readManifest(manifestPath);
manifest.version = increaseVersion(manifest.version);
writeManifest(manifestPath, manifest);
if (packageJson.version !== manifest.version) {
console.warn(chalk.yellow(`Version numbers have been updated but they do not match: package.json (${packageJson.version}), manifest.json (${manifest.version}). Set them to the required values to get them in sync.`));
}
};
function main(environ) {
const configName = environ['joplin-plugin-config'];
if (!configName) throw new Error('A config file must be specified via the --joplin-plugin-config flag');
// Webpack configurations run in parallel, while we need them to run in
// sequence, and to do that it seems the only way is to run webpack multiple
// times, with different config each time.
const configs = {
// Builds the main src/index.ts and copy the extra content from /src to
// /dist including scripts, CSS and any other asset.
buildMain: [pluginConfig],
// Builds the extra scripts as defined in plugin.config.json. When doing
// so, some JavaScript files that were copied in the previous might be
// overwritten here by the compiled version. This is by design. The
// result is that JS files that don't need compilation, are simply
// copied to /dist, while those that do need it are correctly compiled.
buildExtraScripts: buildExtraScriptConfigs(userConfig),
// Ths config is for creating the .jpl, which is done via the plugin, so
// it doesn't actually need an entry and output, however webpack won't
// run without this. So we give it an entry that we know is going to
// exist and output in the publish dir. Then the plugin will delete this
// temporary file before packaging the plugin.
createArchive: [createArchiveConfig],
};
// If we are running the first config step, we clean up and create the build
// directories.
if (configName === 'buildMain') {
fs.removeSync(distDir);
fs.removeSync(publishDir);
fs.mkdirpSync(publishDir);
}
if (configName === 'updateVersion') {
updateVersion();
return [];
}
return configs[configName];
}
module.exports = (env) => {
let exportedConfigs = [];
try {
exportedConfigs = main(env);
} catch (error) {
console.error(error.message);
process.exit(1);
}
if (!exportedConfigs.length) {
// Nothing to do - for example where there are no external scripts to
// compile.
process.exit(0);
}
return exportedConfigs;
};

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Joplin Web Clipper [DEV]",
"version": "3.0.0",
"version": "3.1.1",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": {
@@ -54,8 +54,9 @@
}
},
"background": {
"scripts": ["service_worker.mjs"],
"scripts": [
"service_worker.mjs"
],
"service_worker": "service_worker.mjs",
"type": "module"
},

View File

@@ -18,6 +18,7 @@
"@babel/preset-react": "7.23.3",
"babel-loader": "9.1.3",
"css-loader": "6.8.1",
"fs-extra": "11.2.0",
"html-webpack-plugin": "5.6.0",
"react-redux": "9.0.4",
"redux": "5.0.1",
@@ -2980,6 +2981,20 @@
"flat": "cli.js"
}
},
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3279,6 +3294,18 @@
"node": ">=6"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -4217,6 +4244,15 @@
"node": ">=4"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -6579,6 +6615,17 @@
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"dev": true
},
"fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -6780,6 +6827,16 @@
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -7456,6 +7513,12 @@
"integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
"dev": true
},
"universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true
},
"update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",

View File

@@ -18,6 +18,7 @@
"@babel/preset-react": "7.23.3",
"babel-loader": "9.1.3",
"css-loader": "6.8.1",
"fs-extra": "11.2.0",
"html-webpack-plugin": "5.6.0",
"react-redux": "9.0.4",
"redux": "5.0.1",

View File

@@ -1,6 +1,8 @@
import Logger from '@joplin/utils/Logger';
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
import { PluginMessage } from './services/plugins/PluginRunner';
import shim from '@joplin/lib/shim';
import AutoUpdaterService, { defaultUpdateInterval, initialUpdateStartup } from './services/autoUpdater/AutoUpdaterService';
import type ShimType from '@joplin/lib/shim';
const shim: typeof ShimType = require('@joplin/lib/shim').default;
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import { BrowserWindow, Tray, screen } from 'electron';
@@ -13,6 +15,7 @@ const fs = require('fs-extra');
import { dialog, ipcMain } from 'electron';
import { _ } from '@joplin/lib/locale';
import restartInSafeModeFromMain from './utils/restartInSafeModeFromMain';
import handleCustomProtocols, { CustomProtocolHandler } from './utils/customProtocols/handleCustomProtocols';
import { clearTimeout, setTimeout } from 'timers';
interface RendererProcessQuitReply {
@@ -40,6 +43,9 @@ export default class ElectronAppWrapper {
private rendererProcessQuitReply_: RendererProcessQuitReply = null;
private pluginWindows_: PluginWindows = {};
private initialCallbackUrl_: string = null;
private updaterService_: AutoUpdaterService = null;
private customProtocolHandler_: CustomProtocolHandler = null;
private updatePollInterval_: ReturnType<typeof setTimeout>|null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public constructor(electronApp: any, env: string, profilePath: string|null, isDebugMode: boolean, initialCallbackUrl: string) {
@@ -322,6 +328,10 @@ export default class ElectronAppWrapper {
}
});
ipcMain.on('apply-update-now', () => {
this.updaterService_.updateApp();
});
// Let us register listeners on the window, so we can update the state
// automatically (the listeners will be removed when the window is closed)
// and restore the maximized or full screen state
@@ -354,6 +364,7 @@ export default class ElectronAppWrapper {
}
public quit() {
this.stopPeriodicUpdateCheck();
this.electronApp_.quit();
}
@@ -454,6 +465,39 @@ export default class ElectronAppWrapper {
return false;
}
public initializeCustomProtocolHandler(logger: LoggerWrapper) {
this.customProtocolHandler_ ??= handleCustomProtocols(logger);
}
// Electron's autoUpdater has to be init from the main process
public async initializeAutoUpdaterService(logger: LoggerWrapper, devMode: boolean, includePreReleases: boolean) {
if (shim.isWindows() || shim.isMac()) {
if (!this.updaterService_) {
this.updaterService_ = new AutoUpdaterService(this.win_, logger, devMode, includePreReleases);
this.startPeriodicUpdateCheck();
}
}
}
private startPeriodicUpdateCheck = (updateInterval: number = defaultUpdateInterval): void => {
this.stopPeriodicUpdateCheck();
this.updatePollInterval_ = setInterval(() => {
void this.updaterService_.checkForUpdates();
}, updateInterval);
setTimeout(this.updaterService_.checkForUpdates, initialUpdateStartup);
};
private stopPeriodicUpdateCheck = (): void => {
if (this.updatePollInterval_) {
clearInterval(this.updatePollInterval_);
this.updatePollInterval_ = null;
}
};
public getCustomProtocolHandler() {
return this.customProtocolHandler_;
}
public async start() {
// Since we are doing other async things before creating the window, we might miss
// the "ready" event. So we use the function below to make sure that the app is ready.

View File

@@ -2,13 +2,14 @@ const fs = require('fs');
const path = require('path');
const os = require('os');
const sha512 = require('js-sha512');
const distDirName = 'dist';
const distPath = path.join(__dirname, distDirName);
const generateChecksumFile = () => {
if (os.platform() !== 'linux') {
return []; // SHA-512 is only for AppImage
}
const distDirName = 'dist';
const distPath = path.join(__dirname, distDirName);
let appImageName = '';
const files = fs.readdirSync(distPath);
for (const key in files) {
@@ -30,4 +31,8 @@ const generateChecksumFile = () => {
return [sha512FilePath];
};
exports.default = generateChecksumFile;
const mainHook = () => {
generateChecksumFile();
};
exports.default = mainHook;

View File

@@ -5,7 +5,8 @@ import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/Plug
import resourceEditWatcherReducer, { defaultState as resourceEditWatcherDefaultState } from '@joplin/lib/services/ResourceEditWatcher/reducer';
import PluginRunner from './services/plugins/PluginRunner';
import PlatformImplementation from './services/plugins/PlatformImplementation';
import shim from '@joplin/lib/shim';
import type ShimType from '@joplin/lib/shim';
const shim: typeof ShimType = require('@joplin/lib/shim').default;
import AlarmService from '@joplin/lib/services/AlarmService';
import AlarmServiceDriverNode from '@joplin/lib/services/AlarmServiceDriverNode';
import Logger, { TargetType } from '@joplin/utils/Logger';
@@ -71,6 +72,7 @@ import OcrService from '@joplin/lib/services/ocr/OcrService';
import OcrDriverTesseract from '@joplin/lib/services/ocr/drivers/OcrDriverTesseract';
import SearchEngine from '@joplin/lib/services/search/SearchEngine';
import { PackageInfo } from '@joplin/lib/versionInfo';
import { CustomProtocolHandler } from './utils/customProtocols/handleCustomProtocols';
import { refreshFolders } from '@joplin/lib/folders-screen-utils';
const pluginClasses = [
@@ -88,6 +90,7 @@ class Application extends BaseApplication {
private checkAllPluginStartedIID_: any = null;
private initPluginServiceDone_ = false;
private ocrService_: OcrService;
private protocolHandler_: CustomProtocolHandler;
public constructor() {
super();
@@ -129,7 +132,7 @@ class Application extends BaseApplication {
}
if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'ocr.enabled' || action.type === 'SETTING_UPDATE_ALL') {
this.setupOcrService();
void this.setupOcrService();
}
if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'style.editor.fontFamily' || action.type === 'SETTING_UPDATE_ALL') {
@@ -167,6 +170,12 @@ class Application extends BaseApplication {
this.handleThemeAutoDetect();
}
if (action.type === 'PLUGIN_ADD') {
const plugin = PluginService.instance().pluginById(action.plugin.id);
this.protocolHandler_.allowReadAccessToDirectory(plugin.baseDir);
this.protocolHandler_.allowReadAccessToDirectory(plugin.dataDir);
}
return result;
}
@@ -204,7 +213,7 @@ class Application extends BaseApplication {
public updateEditorFont() {
const fontFamilies = [];
if (Setting.value('style.editor.fontFamily')) fontFamilies.push(`"${Setting.value('style.editor.fontFamily')}"`);
fontFamilies.push('Avenir, Arial, sans-serif');
fontFamilies.push('\'Avenir Next\', Avenir, Arial, sans-serif');
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
// https://github.com/laurent22/joplin/issues/155
@@ -353,16 +362,29 @@ class Application extends BaseApplication {
Setting.setValue('wasClosedSuccessfully', false);
}
private setupOcrService() {
private async setupOcrService() {
if (Setting.value('ocr.clearLanguageDataCache')) {
Setting.setValue('ocr.clearLanguageDataCache', false);
try {
await OcrDriverTesseract.clearLanguageDataCache();
} catch (error) {
this.logger().warn('OCR: Failed to clear language data cache.', error);
}
}
if (Setting.value('ocr.enabled')) {
if (!this.ocrService_) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const Tesseract = (window as any).Tesseract;
const driver = new OcrDriverTesseract(
{ createWorker: Tesseract.createWorker },
`${bridge().buildDir()}/tesseract.js/worker.min.js`,
`${bridge().buildDir()}/tesseract.js-core`,
{
workerPath: `${bridge().buildDir()}/tesseract.js/worker.min.js`,
corePath: `${bridge().buildDir()}/tesseract.js-core`,
languageDataPath: Setting.value('ocr.languageDataPath') || null,
},
);
this.ocrService_ = new OcrService(driver);
@@ -382,6 +404,16 @@ class Application extends BaseApplication {
eventManager.on(EventName.ResourceChange, handleResourceChange);
}
private async setupAutoUpdaterService() {
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
await bridge().electronApp().initializeAutoUpdaterService(
Logger.create('AutoUpdaterService'),
Setting.value('env') === 'dev',
Setting.value('autoUpdate.includePreReleases'),
);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async start(argv: string[], startOptions: StartOptions = null): Promise<any> {
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
@@ -420,12 +452,27 @@ class Application extends BaseApplication {
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
reg.setDispatch(this.dispatch.bind(this));
reg.setShowErrorMessageBoxHandler((message: string) => { bridge().showErrorMessageBox(message); });
if (Setting.value('flagOpenDevTools')) {
bridge().openDevTools();
}
bridge().electronApp().initializeCustomProtocolHandler(
Logger.create('handleCustomProtocols'),
);
this.protocolHandler_ = bridge().electronApp().getCustomProtocolHandler();
this.protocolHandler_.allowReadAccessToDirectory(__dirname); // App bundle directory
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('cacheDir'));
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('resourceDir'));
// this.protocolHandler_.allowReadAccessTo(Setting.value('tempDir'));
// For now, this doesn't seem necessary:
// this.protocolHandler_.allowReadAccessTo(Setting.value('profileDir'));
// If it is needed, note that they decrease the security of the protcol
// handler, and, as such, it may make sense to also limit permissions of
// allowed pages with a Content Security Policy.
PluginManager.instance().dispatch_ = this.dispatch.bind(this);
PluginManager.instance().setLogger(reg.logger());
PluginManager.instance().register(pluginClasses);
@@ -533,17 +580,19 @@ class Application extends BaseApplication {
// Note: Auto-update is a misnomer in the code.
// The code below only checks, if a new version is available.
// We only allow Windows and macOS users to automatically check for updates
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
void checkForUpdates(true, bridge().window(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
if (!Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
void checkForUpdates(true, bridge().window(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
// Initial check on startup
shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
// Initial check on startup
shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
}
}
initializeUserFetcher();
@@ -649,6 +698,8 @@ class Application extends BaseApplication {
SearchEngine.instance().scheduleSyncTables();
});
await this.setupAutoUpdaterService();
// setTimeout(() => {
// void populateDatabase(reg.db(), {
// clearDatabase: true,

View File

@@ -0,0 +1,25 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const distDirName = 'dist';
const distPath = path.join(__dirname, distDirName);
const renameLatestYmlFile = () => {
if (os.platform() === 'darwin' && process.arch === 'arm64') {
const latestMacFilePath = path.join(distPath, 'latest-mac.yml');
const renamedMacFilePath = path.join(distPath, 'latest-mac-arm64.yml');
if (fs.existsSync(latestMacFilePath)) {
fs.renameSync(latestMacFilePath, renamedMacFilePath);
return [renamedMacFilePath];
} else {
throw new Error('latest-mac.yml not found!');
}
}
};
const mainHook = () => {
renameLatestYmlFile();
};
exports.default = mainHook;

View File

@@ -1,7 +1,7 @@
import ElectronAppWrapper from './ElectronAppWrapper';
import shim from '@joplin/lib/shim';
import { _, setLocale } from '@joplin/lib/locale';
import { BrowserWindow, nativeTheme, nativeImage, shell, dialog, MessageBoxSyncOptions } from 'electron';
import { BrowserWindow, nativeTheme, nativeImage, shell, dialog, MessageBoxSyncOptions, safeStorage } from 'electron';
import { dirname, toSystemSlashes } from '@joplin/lib/path-utils';
import { fileUriToPath } from '@joplin/utils/url';
import { urlDecode } from '@joplin/lib/string-utils';
@@ -485,6 +485,21 @@ export class Bridge {
return nativeImage.createFromPath(path);
}
public safeStorage = {
isEncryptionAvailable() {
return safeStorage.isEncryptionAvailable();
},
encryptString(data: string) {
return safeStorage.encryptString(data).toString('base64');
},
decryptString(base64Data: string) {
return safeStorage.decryptString(Buffer.from(base64Data, 'base64'));
},
getSelectedStorageBackend() {
return safeStorage.getSelectedStorageBackend();
},
};
}
let bridge_: Bridge = null;

View File

@@ -4,14 +4,18 @@ export const declaration: CommandDeclaration = {
name: 'focusElement',
};
export interface FocusElementOptions {
moveCursorToStart: boolean;
}
export const runtime = (): CommandRuntime => {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
execute: async (_context: any, target: string) => {
if (target === 'noteBody') return CommandService.instance().execute('focusElementNoteBody');
execute: async (_context: any, target: string, options?: FocusElementOptions) => {
if (target === 'noteBody') return CommandService.instance().execute('focusElementNoteBody', options);
if (target === 'noteList') return CommandService.instance().execute('focusElementNoteList');
if (target === 'sideBar') return CommandService.instance().execute('focusElementSideBar');
if (target === 'noteTitle') return CommandService.instance().execute('focusElementNoteTitle');
if (target === 'noteTitle') return CommandService.instance().execute('focusElementNoteTitle', options);
throw new Error(`Invalid focus target: ${target}`);
},
};

View File

@@ -36,6 +36,9 @@ interface Props {
isSquare?: boolean;
iconOnly?: boolean;
fontSize?: number;
'aria-controls'?: string;
'aria-expanded'?: string;
}
const StyledTitle = styled.span`
@@ -220,7 +223,14 @@ const Button = React.forwardRef((props: Props, ref: any) => {
function renderIcon() {
if (!props.iconName) return null;
return <StyledIcon aria-label={props.iconLabel} animation={props.iconAnimation} mr={iconOnly ? '0' : '6px'} color={props.color} className={props.iconName}/>;
return <StyledIcon
aria-label={props.iconLabel ?? ''}
animation={props.iconAnimation}
mr={iconOnly ? '0' : '6px'}
color={props.color}
className={props.iconName}
role='img'
/>;
}
function renderTitle() {
@@ -234,7 +244,22 @@ const Button = React.forwardRef((props: Props, ref: any) => {
}
return (
<StyledButton ref={ref} fontSize={props.fontSize} isSquare={props.isSquare} size={props.size} style={props.style} disabled={props.disabled} title={props.tooltip} className={props.className} iconOnly={iconOnly} onClick={onClick}>
<StyledButton
ref={ref}
fontSize={props.fontSize}
isSquare={props.isSquare}
size={props.size}
style={props.style}
disabled={props.disabled}
title={props.tooltip}
className={props.className}
iconOnly={iconOnly}
onClick={onClick}
aria-disabled={props.disabled}
aria-expanded={props['aria-expanded']}
aria-controls={props['aria-controls']}
>
{renderIcon()}
{renderTitle()}
</StyledButton>

View File

@@ -1,16 +1,14 @@
import * as React from 'react';
import Sidebar from './Sidebar';
import ButtonBar from './ButtonBar';
import Button, { ButtonLevel, ButtonSize } from '../Button/Button';
import Button, { ButtonLevel } from '../Button/Button';
import { _ } from '@joplin/lib/locale';
import bridge from '../../services/bridge';
import Setting, { AppType, SettingItemSubType, SyncStartupOperation } from '@joplin/lib/models/Setting';
import control_PluginsStates from './controls/plugins/PluginsStates';
import Setting, { AppType, SettingValueType, SyncStartupOperation } from '@joplin/lib/models/Setting';
import EncryptionConfigScreen from '../EncryptionConfigScreen/EncryptionConfigScreen';
import { reg } from '@joplin/lib/registry';
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
import * as pathUtils from '@joplin/lib/path-utils';
import { themeStyle } from '@joplin/lib/theme';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import * as shared from '@joplin/lib/components/shared/config/config-shared.js';
import ClipperConfigScreen from '../ClipperConfigScreen';
@@ -20,12 +18,8 @@ import ToggleAdvancedSettingsButton from './controls/ToggleAdvancedSettingsButto
import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning';
import MacOSMissingPasswordHelpLink from './controls/MissingPasswordHelpLink';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
import FontSearch from './FontSearch';
import SettingComponent, { UpdateSettingValueEvent } from './controls/SettingComponent';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const settingKeyToControl: any = {
'plugins.states': control_PluginsStates,
};
interface Font {
family: string;
@@ -34,6 +28,7 @@ interface Font {
declare global {
interface Window {
queryLocalFonts(): Promise<Font[]>;
openChangelogLink: ()=> void;
}
}
@@ -67,9 +62,6 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.onCancelClick = this.onCancelClick.bind(this);
this.onSaveClick = this.onSaveClick.bind(this);
this.onApplyClick = this.onApplyClick.bind(this);
this.renderLabel = this.renderLabel.bind(this);
this.renderDescription = this.renderDescription.bind(this);
this.renderHeader = this.renderHeader.bind(this);
this.handleSettingButton = this.handleSettingButton.bind(this);
}
@@ -113,6 +105,10 @@ class ConfigScreenComponent extends React.Component<any, any> {
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData);
await Setting.saveAll();
await restart();
} else if (key === 'ocr.clearLanguageDataCacheButton') {
if (!confirm(this.restartMessage())) return;
Setting.setValue('ocr.clearLanguageDataCache', true);
await restart();
} else if (key === 'sync.openSyncWizard') {
this.props.dispatch({
type: 'DIALOG_OPEN',
@@ -237,7 +233,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
if (syncTargetMd.supportsConfigCheck) {
const messages = shared.checkSyncConfigMessages(this);
const statusComp = !messages.length ? null : (
<div style={statusStyle}>
<div style={statusStyle} aria-live='polite'>
{messages[0]}
{messages.length >= 1 ? <p>{messages[1]}</p> : null}
</div>
@@ -277,12 +273,14 @@ class ConfigScreenComponent extends React.Component<any, any> {
let advancedSettingsButton = null;
const advancedSettingsSectionStyle = { display: 'none' };
const advancedSettingsGroupId = `advanced_settings_${key}`;
if (advancedSettingComps.length) {
advancedSettingsButton = (
<ToggleAdvancedSettingsButton
onClick={() => shared.advancedSettingsButton_click(this)}
advancedSettingsVisible={this.state.showAdvancedSettings}
aria-controls={advancedSettingsGroupId}
/>
);
advancedSettingsSectionStyle.display = this.state.showAdvancedSettings ? 'block' : 'none';
@@ -293,425 +291,35 @@ class ConfigScreenComponent extends React.Component<any, any> {
{this.renderSectionDescription(section)}
<div>{settingComps}</div>
{advancedSettingsButton}
<div style={advancedSettingsSectionStyle}>{advancedSettingComps}</div>
<div
style={advancedSettingsSectionStyle}
id={advancedSettingsGroupId}
role='group'
>{advancedSettingComps}</div>
</div>
);
}
private labelStyle(themeId: number) {
const theme = themeStyle(themeId);
return { ...theme.textStyle, display: 'block',
color: theme.color,
fontSize: theme.fontSize * 1.083333,
fontWeight: 500,
marginBottom: theme.mainPadding / 2 };
}
private descriptionStyle(themeId: number) {
const theme = themeStyle(themeId);
return { ...theme.textStyle, color: theme.colorFaded,
fontStyle: 'italic',
maxWidth: '70em',
marginTop: 5 };
}
private renderLabel(themeId: number, label: string) {
const labelStyle = this.labelStyle(themeId);
return (
<div style={labelStyle}>
<label>{label}</label>
</div>
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
private renderHeader(themeId: number, label: string, style: any = null) {
const theme = themeStyle(themeId);
const labelStyle = { ...theme.textStyle, display: 'block',
color: theme.color,
fontSize: theme.fontSize * 1.25,
fontWeight: 500,
marginBottom: theme.mainPadding,
...style };
return (
<div style={labelStyle}>
<label>{label}</label>
</div>
);
}
private renderDescription(themeId: number, description: string) {
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public settingToComponent(key: string, value: any) {
const theme = themeStyle(this.props.themeId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const output: any = null;
const rowStyle = {
marginBottom: theme.mainPadding * 1.5,
};
const labelStyle = this.labelStyle(this.props.themeId);
const subLabel = { ...labelStyle, display: 'block',
opacity: 0.7,
marginBottom: labelStyle.marginBottom };
const checkboxLabelStyle = { ...labelStyle, marginLeft: 8,
display: 'inline',
backgroundColor: 'transparent' };
const controlStyle = {
display: 'inline-block',
color: theme.color,
fontFamily: theme.fontFamily,
backgroundColor: theme.backgroundColor,
};
const textInputBaseStyle = { ...controlStyle, fontFamily: theme.fontFamily,
border: '1px solid',
padding: '4px 6px',
boxSizing: 'border-box',
borderColor: theme.borderColor4,
borderRadius: 3,
paddingLeft: 6,
paddingRight: 6,
paddingTop: 4,
paddingBottom: 4 };
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const updateSettingValue = (key: string, value: any) => {
const md = Setting.settingMetadata(key);
if (md.needRestart) {
this.setState({ needRestart: true });
}
shared.updateSettingValue(this, key, value);
};
private onUpdateSettingValue = ({ key, value }: UpdateSettingValueEvent) => {
const md = Setting.settingMetadata(key);
const descriptionText = Setting.keyDescription(key, AppType.Desktop);
const descriptionComp = this.renderDescription(this.props.themeId, descriptionText);
if (settingKeyToControl[key]) {
const SettingComponent = settingKeyToControl[key];
const label = md.label ? this.renderLabel(this.props.themeId, md.label()) : null;
return (
<div key={key} style={rowStyle}>
{label}
{this.renderDescription(this.props.themeId, md.description ? md.description() : null)}
<SettingComponent
metadata={md}
value={value}
themeId={this.props.themeId}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
updateSettingValue(key, event.value);
}}
renderLabel={this.renderLabel}
renderDescription={this.renderDescription}
renderHeader={this.renderHeader}
/>
</div>
);
} else if (md.isEnum) {
const items = [];
const settingOptions = md.options();
const array = Setting.enumOptionsToValueLabels(settingOptions, md.optionsOrder ? md.optionsOrder() : [], {
valueKey: 'key',
labelKey: 'label',
});
for (let i = 0; i < array.length; i++) {
const e = array[i];
items.push(
<option value={e.key.toString()} key={e.key}>
{settingOptions[e.key]}
</option>,
);
}
const selectStyle = { ...controlStyle, paddingLeft: 6,
paddingRight: 6,
paddingTop: 4,
paddingBottom: 4,
borderColor: theme.borderColor4,
borderRadius: 3 };
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
<label>{md.label()}</label>
</div>
<select
value={value}
style={selectStyle}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
updateSettingValue(key, event.target.value);
}}
>
{items}
</select>
{descriptionComp}
</div>
);
} else if (md.type === Setting.TYPE_BOOL) {
const onCheckboxClick = () => {
updateSettingValue(key, !value);
};
const checkboxSize = theme.fontSize * 1.1666666666666;
// Hack: The {key+value.toString()} is needed as otherwise the checkbox doesn't update when the state changes.
// There's probably a better way to do this but can't figure it out.
return (
<div key={key + (`${value}`).toString()} style={rowStyle}>
<div style={{ ...controlStyle, backgroundColor: 'transparent', display: 'flex', alignItems: 'center' }}>
<input
id={`setting_checkbox_${key}`}
type="checkbox"
checked={!!value}
onChange={() => {
onCheckboxClick();
}}
style={{ marginLeft: 0, width: checkboxSize, height: checkboxSize }}
/>
<label
onClick={() => {
onCheckboxClick();
}}
style={{ ...checkboxLabelStyle, marginLeft: 5, marginBottom: 0 }}
htmlFor={`setting_checkbox_${key}`}
>
{md.label()}
</label>
</div>
{descriptionComp}
</div>
);
} else if (md.type === Setting.TYPE_STRING) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const inputStyle: any = { ...textInputBaseStyle, width: '50%',
minWidth: '20em' };
const inputType = md.secure === true ? 'password' : 'text';
if (md.subType === 'file_path_and_args' || md.subType === 'file_path' || md.subType === 'directory_path') {
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];
};
const joinCmd = (cmdArray: string[]) => {
if (!cmdArray[0] && !cmdArray[1]) return '';
let cmdString = pathUtils.quotePath(cmdArray[0]);
if (!cmdString) cmdString = '""';
if (cmdArray[1]) cmdString += ` ${cmdArray[1]}`;
return cmdString;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onPathChange = (event: any) => {
if (md.subType === 'file_path_and_args') {
const cmd = splitCmd(this.state.settings[key]);
cmd[0] = event.target.value;
updateSettingValue(key, joinCmd(cmd));
} else {
updateSettingValue(key, event.target.value);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onArgsChange = (event: any) => {
const cmd = splitCmd(this.state.settings[key]);
cmd[1] = event.target.value;
updateSettingValue(key, joinCmd(cmd));
};
const browseButtonClick = async () => {
if (md.subType === 'directory_path') {
const paths = await bridge().showOpenDialog({
properties: ['openDirectory'],
});
if (!paths || !paths.length) return;
updateSettingValue(key, paths[0]);
} else {
const paths = await bridge().showOpenDialog();
if (!paths || !paths.length) return;
if (md.subType === 'file_path') {
updateSettingValue(key, paths[0]);
} else {
const cmd = splitCmd(this.state.settings[key]);
cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd));
}
}
};
const cmd = splitCmd(this.state.settings[key]);
const path = md.subType === 'file_path_and_args' ? cmd[0] : this.state.settings[key];
const argComp = md.subType !== 'file_path_and_args' ? null : (
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div style={subLabel}>{_('Arguments:')}</div>
<input
type={inputType}
style={inputStyle}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
onArgsChange(event);
}}
value={cmd[1]}
spellCheck={false}
/>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
);
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
<label>{md.label()}</label>
</div>
<div style={{ display: 'flex' }}>
<div style={{ flex: 1 }}>
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div style={subLabel}>{_('Path:')}</div>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom }}>
<input
type={inputType}
style={{ ...inputStyle, marginBottom: 0, marginRight: 5 }}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
onPathChange(event);
}}
value={path}
spellCheck={false}
/>
<Button
level={ButtonLevel.Secondary}
title={_('Browse...')}
onClick={browseButtonClick}
size={ButtonSize.Small}
/>
</div>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
</div>
</div>
{argComp}
</div>
);
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onTextChange = (event: any) => {
updateSettingValue(key, event.target.value);
};
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
<label>{md.label()}</label>
</div>
{
md.subType === SettingItemSubType.FontFamily || md.subType === SettingItemSubType.MonospaceFontFamily ?
<FontSearch
type={inputType}
style={inputStyle}
value={this.state.settings[key]}
availableFonts={this.state.fonts}
onChange={fontFamily => updateSettingValue(key, fontFamily)}
subtype={md.subType}
/> :
<input
type={inputType}
style={inputStyle}
value={this.state.settings[key]}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
onTextChange(event);
}}
spellCheck={false}
/>
}
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
);
}
} else if (md.type === Setting.TYPE_INT) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onNumChange = (event: any) => {
updateSettingValue(key, event.target.value);
};
const label = [md.label()];
if (md.unitLabel) label.push(`(${md.unitLabel()})`);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const inputStyle: any = { ...textInputBaseStyle };
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
<label>{label.join(' ')}</label>
</div>
<input
type="number"
style={inputStyle}
value={this.state.settings[key]}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
onNumChange(event);
}}
min={md.minimum}
max={md.maximum}
step={md.step}
spellCheck={false}
/>
{descriptionComp}
</div>
);
} else if (md.type === Setting.TYPE_BUTTON) {
const labelComp = md.hideLabel ? null : (
<div style={labelStyle}>
<label>{md.label()}</label>
</div>
);
return (
<div key={key} style={rowStyle}>
{labelComp}
<Button level={ButtonLevel.Secondary} title={md.label()} onClick={md.onClick ? md.onClick : () => this.handleSettingButton(key)}/>
{descriptionComp}
</div>
);
} else {
console.warn(`Type not implemented: ${key}`);
if (md.needRestart) {
this.setState({ needRestart: true });
}
shared.updateSettingValue(this, key, value);
};
return output;
public settingToComponent<T extends string>(key: T, value: SettingValueType<T>) {
return (
<SettingComponent
themeId={this.props.themeId}
key={key}
settingKey={key}
value={value}
fonts={this.state.fonts}
onUpdateSettingValue={this.onUpdateSettingValue}
onSettingButtonClick={this.handleSettingButton}
/>
);
}
private restartMessage() {
@@ -768,7 +376,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
const settings = this.state.settings;
const containerStyle = {
const containerStyle: React.CSSProperties = {
overflow: 'auto',
padding: theme.configScreenPadding,
paddingTop: 0,
@@ -800,6 +408,35 @@ class ConfigScreenComponent extends React.Component<any, any> {
const rightStyle = { ...style, flex: 1 };
delete style.width;
const tabComponents: React.ReactNode[] = [];
for (const section of sections) {
const sectionId = `setting-section-${section.name}`;
let content = null;
const visible = section.name === this.state.selectedSectionName;
if (visible) {
content = (
<>
{screenComp}
<div style={containerStyle}>{settingComps}</div>
</>
);
}
tabComponents.push(
<div
key={sectionId}
id={sectionId}
className={`setting-tab-panel ${!visible ? '-hidden' : ''}`}
hidden={!visible}
aria-labelledby={`setting-tab-${section.name}`}
tabIndex={0}
role='tabpanel'
>
{content}
</div>,
);
}
return (
<div className="config-screen" style={{ display: 'flex', flexDirection: 'row', height: this.props.style.height }}>
<Sidebar
@@ -808,9 +445,8 @@ class ConfigScreenComponent extends React.Component<any, any> {
sections={sections}
/>
<div style={rightStyle}>
{screenComp}
{needRestartComp}
<div style={containerStyle}>{settingComps}</div>
{tabComponents}
<ButtonBar
hasChanges={hasChanges}
backButtonTitle={hasChanges && !screenComp ? _('Cancel') : _('Back')}

View File

@@ -1,18 +1,22 @@
import { AppType, SettingSectionSource } from '@joplin/lib/models/Setting';
import { AppType, MetadataBySection, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting';
import * as React from 'react';
import Setting from '@joplin/lib/models/Setting';
import { _ } from '@joplin/lib/locale';
import { useCallback, useRef } from 'react';
import { focus } from '@joplin/lib/utils/focusHandler';
const styled = require('styled-components').default;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
type StyleProps = any;
interface SectionChangeEvent {
section: SettingMetadataSection;
}
interface Props {
selection: string;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onSelectionChange: Function;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
sections: any[];
onSelectionChange: (event: SectionChangeEvent)=> void;
sections: MetadataBySection;
}
export const StyledRoot = styled.div`
@@ -73,24 +77,63 @@ export const StyledListItemIcon = styled.i`
`;
export default function Sidebar(props: Props) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
const buttons: any[] = [];
const buttonRefs = useRef<HTMLElement[]>([]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
function renderButton(section: any) {
// Making a tabbed region accessible involves supporting keyboard interaction.
// See https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ for details
const onKeyDown: React.KeyboardEventHandler<HTMLElement> = useCallback((event) => {
const selectedIndex = props.sections.findIndex(section => section.name === props.selection);
let newIndex = selectedIndex;
if (event.code === 'ArrowUp') {
newIndex --;
} else if (event.code === 'ArrowDown') {
newIndex ++;
} else if (event.code === 'Home') {
newIndex = 0;
} else if (event.code === 'End') {
newIndex = props.sections.length - 1;
}
if (newIndex < 0) newIndex += props.sections.length;
newIndex %= props.sections.length;
if (newIndex !== selectedIndex) {
event.preventDefault();
props.onSelectionChange({ section: props.sections[newIndex] });
const targetButton = buttonRefs.current[newIndex];
if (targetButton) {
focus('Sidebar', targetButton);
}
}
}, [props.sections, props.selection, props.onSelectionChange]);
const buttons: React.ReactNode[] = [];
function renderButton(section: SettingMetadataSection, index: number) {
const selected = props.selection === section.name;
return (
<StyledListItem
key={section.name}
href='#'
role='tab'
ref={(item: HTMLElement) => { buttonRefs.current[index] = item; }}
id={`setting-tab-${section.name}`}
aria-controls={`setting-section-${section.name}`}
aria-selected={selected}
tabIndex={selected ? 0 : -1}
isSubSection={Setting.isSubSection(section.name)}
selected={selected}
onClick={() => { props.onSelectionChange({ section: section }); }}
onKeyDown={onKeyDown}
>
<StyledListItemIcon
aria-label=''
className={Setting.sectionNameToIcon(section.name, AppType.Desktop)}
role='img'
/>
<StyledListItemLabel>
{Setting.sectionNameToLabel(section.name)}
@@ -109,13 +152,15 @@ export default function Sidebar(props: Props) {
let pluginDividerAdded = false;
let index = 0;
for (const section of props.sections) {
if (section.source === SettingSectionSource.Plugin && !pluginDividerAdded) {
buttons.push(renderDivider('divider-plugins'));
pluginDividerAdded = true;
}
buttons.push(renderButton(section));
buttons.push(renderButton(section, index));
index ++;
}
return (

View File

@@ -9,6 +9,7 @@ interface Props {
style: CSSProperties;
value: string;
availableFonts: string[];
inputId: string;
onChange: (font: string)=> void;
subtype: string;
}
@@ -108,6 +109,7 @@ const FontSearch = (props: Props) => {
onFocus={handleFocus}
onBlur={handleBlur}
spellCheck={false}
id={props.inputId}
ref={fontInputRef}
/>
<div

View File

@@ -0,0 +1,381 @@
import Setting, { AppType, SettingItemSubType } from '@joplin/lib/models/Setting';
import { themeStyle } from '@joplin/lib/theme';
import * as React from 'react';
import { useCallback, useId } from 'react';
import control_PluginsStates from './plugins/PluginsStates';
import bridge from '../../../services/bridge';
import { _ } from '@joplin/lib/locale';
import Button, { ButtonLevel, ButtonSize } from '../../Button/Button';
import FontSearch from './FontSearch';
import * as pathUtils from '@joplin/lib/path-utils';
import SettingLabel from './SettingLabel';
import SettingDescription from './SettingDescription';
const settingKeyToControl: Record<string, typeof control_PluginsStates> = {
'plugins.states': control_PluginsStates,
};
export interface UpdateSettingValueEvent {
key: string;
value: unknown;
}
interface Props {
themeId: number;
settingKey: string;
value: unknown;
fonts: string[];
onUpdateSettingValue: (event: UpdateSettingValueEvent)=> void;
onSettingButtonClick: (key: string)=> void;
}
const SettingComponent: React.FC<Props> = props => {
const theme = themeStyle(props.themeId);
const output: React.ReactNode = null;
const updateSettingValue = useCallback((key: string, value: unknown) => {
props.onUpdateSettingValue({ key, value });
}, [props.onUpdateSettingValue]);
const rowStyle = {
marginBottom: theme.mainPadding * 1.5,
};
const controlStyle = {
display: 'inline-block',
color: theme.color,
fontFamily: theme.fontFamily,
backgroundColor: theme.backgroundColor,
};
const textInputBaseStyle: React.CSSProperties = {
...controlStyle,
fontFamily: theme.fontFamily,
border: '1px solid',
padding: '4px 6px',
boxSizing: 'border-box',
borderColor: theme.borderColor4,
borderRadius: 3,
paddingLeft: 6,
paddingRight: 6,
paddingTop: 4,
paddingBottom: 4,
};
const key = props.settingKey;
const md = Setting.settingMetadata(key);
const descriptionText = Setting.keyDescription(key, AppType.Desktop);
const inputId = useId();
const descriptionId = useId();
const descriptionComp = <SettingDescription id={descriptionId} text={descriptionText}/>;
if (key in settingKeyToControl) {
const CustomSettingComponent = settingKeyToControl[key];
const label = md.label ? <SettingLabel text={md.label()} htmlFor={null} /> : null;
return (
<div style={rowStyle}>
{label}
<SettingDescription id={descriptionId} text={md.description ? md.description(AppType.Desktop) : null}/>
<CustomSettingComponent
value={props.value}
themeId={props.themeId}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(event: any) => {
updateSettingValue(key, event.value);
}}
/>
</div>
);
} else if (md.isEnum) {
const value = props.value as string;
const items = [];
const settingOptions = md.options();
const array = Setting.enumOptionsToValueLabels(settingOptions, md.optionsOrder ? md.optionsOrder() : [], {
valueKey: 'key',
labelKey: 'label',
});
for (let i = 0; i < array.length; i++) {
const e = array[i];
items.push(
<option value={e.key.toString()} key={e.key}>
{settingOptions[e.key]}
</option>,
);
}
const selectStyle = { ...controlStyle, paddingLeft: 6,
paddingRight: 6,
paddingTop: 4,
paddingBottom: 4,
borderColor: theme.borderColor4,
borderRadius: 3 };
return (
<div style={rowStyle}>
<SettingLabel htmlFor={inputId} text={md.label()}/>
<select
value={value}
style={selectStyle}
onChange={(event) => {
updateSettingValue(key, event.target.value);
}}
id={inputId}
aria-describedby={descriptionId}
>
{items}
</select>
{descriptionComp}
</div>
);
} else if (md.type === Setting.TYPE_BOOL) {
const value = props.value as boolean;
const checkboxSize = theme.fontSize * 1.1666666666666;
return (
<div style={rowStyle}>
<div style={{ ...controlStyle, backgroundColor: 'transparent', display: 'flex', alignItems: 'center' }}>
<input
id={inputId}
type="checkbox"
checked={!!value}
onChange={event => updateSettingValue(key, event.target.checked)}
style={{ marginLeft: 0, width: checkboxSize, height: checkboxSize }}
// Prefer aria-details to aria-describedby for checkbox inputs --
// on MacOS, VoiceOver reads "checked"/"unchecked" only after reading the
// potentially-lengthy description. For other input types, the input value
// is read first.
aria-details={descriptionId}
/>
<label
className='setting-label -for-checkbox'
htmlFor={inputId}
>
{md.label()}
</label>
</div>
{descriptionComp}
</div>
);
} else if (md.type === Setting.TYPE_STRING) {
const value = props.value as string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const inputStyle: any = { ...textInputBaseStyle, width: '50%',
minWidth: '20em' };
const inputType = md.secure === true ? 'password' : 'text';
if (md.subType === 'file_path_and_args' || md.subType === 'file_path' || md.subType === 'directory_path') {
inputStyle.marginBottom = theme.mainPadding / 2;
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];
};
const joinCmd = (cmdArray: string[]) => {
if (!cmdArray[0] && !cmdArray[1]) return '';
let cmdString = pathUtils.quotePath(cmdArray[0]);
if (!cmdString) cmdString = '""';
if (cmdArray[1]) cmdString += ` ${cmdArray[1]}`;
return cmdString;
};
const onPathChange: React.ChangeEventHandler<HTMLInputElement> = event => {
if (md.subType === 'file_path_and_args') {
const cmd = splitCmd(value);
cmd[0] = event.target.value;
updateSettingValue(key, joinCmd(cmd));
} else {
updateSettingValue(key, event.target.value);
}
};
const onArgsChange: React.ChangeEventHandler<HTMLInputElement> = event => {
const cmd = splitCmd(value);
cmd[1] = event.target.value;
updateSettingValue(key, joinCmd(cmd));
};
const browseButtonClick = async () => {
if (md.subType === 'directory_path') {
const paths = await bridge().showOpenDialog({
properties: ['openDirectory'],
});
if (!paths || !paths.length) return;
updateSettingValue(key, paths[0]);
} else {
const paths = await bridge().showOpenDialog();
if (!paths || !paths.length) return;
if (md.subType === 'file_path') {
updateSettingValue(key, paths[0]);
} else {
const cmd = splitCmd(value);
cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd));
}
}
};
const cmd = splitCmd(value);
const path = md.subType === 'file_path_and_args' ? cmd[0] : value;
const argInputId = `setting_path_arg_${key}`;
const argComp = md.subType !== 'file_path_and_args' ? null : (
<div style={{ ...rowStyle, marginBottom: 5 }}>
<label
className='setting-label -sub-label'
htmlFor={argInputId}
>{_('Arguments:')}</label>
<input
type={inputType}
style={inputStyle}
onChange={onArgsChange}
value={cmd[1]}
spellCheck={false}
id={argInputId}
aria-describedby={descriptionId}
/>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
);
const pathDescriptionId = `setting_path_label_${key}`;
return (
<div style={rowStyle}>
<SettingLabel text={md.label()} htmlFor={inputId}/>
<div style={{ display: 'flex' }}>
<div style={{ flex: 1 }}>
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div
className='setting-label -sub-label'
id={pathDescriptionId}
>{_('Path:')}</div>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom }}>
<input
type={inputType}
style={{ ...inputStyle, marginBottom: 0, marginRight: 5 }}
onChange={onPathChange}
value={path}
spellCheck={false}
id={inputId}
aria-describedby={pathDescriptionId}
aria-details={descriptionId}
/>
<Button
level={ButtonLevel.Secondary}
title={_('Browse...')}
onClick={browseButtonClick}
size={ButtonSize.Small}
/>
</div>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
</div>
</div>
{argComp}
</div>
);
} else {
const onTextChange: React.ChangeEventHandler<HTMLInputElement> = event => {
updateSettingValue(key, event.target.value);
};
return (
<div style={rowStyle}>
<SettingLabel text={md.label()} htmlFor={inputId}/>
{
md.subType === SettingItemSubType.FontFamily || md.subType === SettingItemSubType.MonospaceFontFamily ?
<FontSearch
type={inputType}
style={inputStyle}
value={props.value as string}
availableFonts={props.fonts}
onChange={fontFamily => updateSettingValue(key, fontFamily)}
subtype={md.subType}
inputId={inputId}
/> :
<input
type={inputType}
style={inputStyle}
value={props.value as string|number}
onChange={onTextChange}
spellCheck={false}
id={inputId}
aria-describedby={descriptionId}
/>
}
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
);
}
} else if (md.type === Setting.TYPE_INT) {
const value = props.value as number;
const onNumChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
updateSettingValue(key, event.target.value);
};
const label = [md.label()];
if (md.unitLabel) label.push(`(${md.unitLabel(md.value)})`);
return (
<div style={rowStyle}>
<SettingLabel htmlFor={inputId} text={label.join(' ')}/>
<input
type="number"
style={textInputBaseStyle}
value={value}
onChange={onNumChange}
min={md.minimum}
max={md.maximum}
step={md.step}
spellCheck={false}
id={inputId}
aria-describedby={descriptionId}
/>
{descriptionComp}
</div>
);
} else if (md.type === Setting.TYPE_BUTTON) {
const labelComp = md.hideLabel ? null : (
<SettingLabel text={md.label()} htmlFor={null} />
);
return (
<div style={rowStyle}>
{labelComp}
<Button
level={ButtonLevel.Secondary}
title={md.label()}
onClick={md.onClick ? md.onClick : () => props.onSettingButtonClick(key)}
/>
{descriptionComp}
</div>
);
} else {
console.warn(`Type not implemented: ${key}`);
}
return output;
};
export default SettingComponent;

View File

@@ -0,0 +1,12 @@
import * as React from 'react';
interface Props {
text: string;
id?: string;
}
const SettingDescription: React.FC<Props> = props => {
return props.text ? <div className='setting-description' id={props.id}>{props.text}</div> : null;
};
export default SettingDescription;

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
interface Props {
text: string;
}
const SettingHeader: React.FC<Props> = props => {
return (
<div className='setting-header'>
<label>{props.text}</label>
</div>
);
};
export default SettingHeader;

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
interface Props {
htmlFor: string|null;
text: string;
}
const SettingLabel: React.FC<Props> = props => {
return (
<div className='setting-label'>
<label htmlFor={props.htmlFor}>{props.text}</label>
</div>
);
};
export default SettingLabel;

View File

@@ -6,6 +6,7 @@ import { _ } from '@joplin/lib/locale';
interface Props {
onClick: ()=> void;
advancedSettingsVisible: boolean;
'aria-controls': string;
}
const ToggleAdvancedSettingsButton: React.FunctionComponent<Props> = props => {
@@ -16,6 +17,10 @@ const ToggleAdvancedSettingsButton: React.FunctionComponent<Props> = props => {
level={ButtonLevel.Secondary}
onClick={props.onClick}
iconName={iconName}
aria-controls={props['aria-controls']}
aria-expanded={props.advancedSettingsVisible}
title={_('Show Advanced Settings')}
/>
</div>

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useMemo } from 'react';
import { useCallback, useId, useMemo } from 'react';
import { _ } from '@joplin/lib/locale';
import styled from 'styled-components';
import ToggleButton from '../../../lib/ToggleButton/ToggleButton';
@@ -173,6 +173,7 @@ export default function(props: Props) {
themeId={props.themeId}
value={item.enabled}
onToggle={() => props.onToggle({ item })}
aria-label={_('Enabled')}
/>;
}
@@ -256,10 +257,17 @@ export default function(props: Props) {
return <RecommendedBadge href="#" title={_('The Joplin team has vetted this plugin and it meets our standards for security and performance.')} onClick={onRecommendedClick}><i className="fas fa-crown"></i></RecommendedBadge>;
}
const nameLabelId = useId();
return (
<CellRoot isCompatible={props.isCompatible}>
<CellRoot isCompatible={props.isCompatible} role='group' aria-labelledby={nameLabelId}>
<CellTop>
<StyledNameAndVersion mb={'5px'}><StyledName onClick={onNameClick} href="#" style={{ marginRight: 5 }}>{item.manifest.name} {item.deleted ? _('(%s)', 'Deleted') : ''}</StyledName><StyledVersion>v{item.manifest.version}</StyledVersion></StyledNameAndVersion>
<StyledNameAndVersion mb={'5px'}>
<StyledName onClick={onNameClick} href="#" style={{ marginRight: 5 }} id={nameLabelId}>
{item.manifest.name} {item.deleted ? _('(%s)', 'Deleted') : ''}
</StyledName>
<StyledVersion>v{item.manifest.version}</StyledVersion>
</StyledNameAndVersion>
{renderToggleButton()}
{renderRecommendedBadge()}
</CellTop>

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import PluginService, { defaultPluginSetting, Plugins, PluginSetting, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { _ } from '@joplin/lib/locale';
import styled from 'styled-components';
@@ -17,6 +17,8 @@ import useOnDeleteHandler from '@joplin/lib/components/shared/config/plugins/use
import Logger from '@joplin/utils/Logger';
import StyledMessage from '../../../style/StyledMessage';
import StyledLink from '../../../style/StyledLink';
import SettingHeader from '../SettingHeader';
import SettingDescription from '../SettingDescription';
const { space } = require('styled-system');
const logger = Logger.create('PluginState');
@@ -51,12 +53,6 @@ interface Props {
themeId: number;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onChange: Function;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
renderLabel: Function;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
renderDescription: Function;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
renderHeader: Function;
}
let repoApi_: RepositoryApi = null;
@@ -214,8 +210,11 @@ export default function(props: Props) {
props.onChange({ value: pluginService.serializePluginSettings(event.value) });
}, [pluginService, props.onChange]);
const onDelete = useOnDeleteHandler(pluginSettings, onPluginSettingsChange, false);
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettings, repoApi, onPluginSettingsChange, true);
const pluginSettingsRef = useRef(pluginSettings);
pluginSettingsRef.current = pluginSettings;
const onDelete = useOnDeleteHandler(pluginSettingsRef, onPluginSettingsChange, false);
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettingsRef, repoApi, onPluginSettingsChange, true);
const onToolsClick = useCallback(async () => {
const template = [
@@ -278,7 +277,7 @@ export default function(props: Props) {
if (!pluginItems.length || allDeleted) {
return (
<UserPluginsRoot mb={'10px'}>
{props.renderDescription(props.themeId, _('You do not have any installed plugin.'))}
<SettingDescription text={_('You do not have any installed plugin.')}/>
</UserPluginsRoot>
);
} else {
@@ -308,7 +307,6 @@ export default function(props: Props) {
pluginSettings={pluginSettings}
onSearchQueryChange={onSearchQueryChange}
onPluginSettingsChange={onSearchPluginSettingsChange}
renderDescription={props.renderDescription}
repoApi={repoApi}
/>
</div>
@@ -330,7 +328,7 @@ export default function(props: Props) {
<div style={{ display: 'flex', flexDirection: 'row', maxWidth }}>
<ToolsButton size={ButtonSize.Small} tooltip={_('Plugin tools')} iconName="fas fa-cog" level={ButtonLevel.Secondary} onClick={onToolsClick}/>
<div style={{ display: 'flex', flex: 1 }}>
{props.renderHeader(props.themeId, _('Manage your plugins'))}
<SettingHeader text={_('Manage your plugins')}/>
</div>
</div>
{renderUserPlugins(pluginItems)}

View File

@@ -10,6 +10,7 @@ import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/Plug
import { _ } from '@joplin/lib/locale';
import useOnInstallHandler from '@joplin/lib/components/shared/config/plugins/useOnInstallHandler';
import { themeStyle } from '@joplin/lib/theme';
import SettingDescription from '../SettingDescription';
const Root = styled.div`
`;
@@ -26,8 +27,6 @@ interface Props {
pluginSettings: PluginSettings;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onPluginSettingsChange(event: any): void;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
renderDescription: Function;
maxWidth: number;
repoApi(): RepositoryApi;
disabled: boolean;
@@ -40,7 +39,10 @@ export default function(props: Props) {
const [installingPluginsIds, setInstallingPluginIds] = useState<Record<string, boolean>>({});
const [searchResultCount, setSearchResultCount] = useState(null);
const onInstall = useOnInstallHandler(setInstallingPluginIds, props.pluginSettings, props.repoApi, props.onPluginSettingsChange, false);
const pluginSettingsRef = useRef(props.pluginSettings);
pluginSettingsRef.current = props.pluginSettings;
const onInstall = useOnInstallHandler(setInstallingPluginIds, pluginSettingsRef, props.repoApi, props.onPluginSettingsChange, false);
useEffect(() => {
setSearchResultCount(null);
@@ -78,7 +80,7 @@ export default function(props: Props) {
function renderResults(query: string, manifests: PluginManifest[]) {
if (query && !manifests.length) {
if (searchResultCount === null) return ''; // Search in progress
return props.renderDescription(props.themeId, _('No results'));
return <SettingDescription text={_('No results')}/>;
} else {
const output = [];

View File

@@ -1,3 +1,5 @@
@use "./styles/index.scss";
.config-screen-content-wrapper {
padding: 24px;
overflow: auto;

View File

@@ -0,0 +1,5 @@
@use "./setting-description.scss";
@use "./setting-label.scss";
@use "./setting-header.scss";
@use "./setting-tab-panel.scss";

View File

@@ -0,0 +1,9 @@
.setting-description {
color: var(--joplin-color-faded);
font-size: var(--joplin-font-size);
line-height: var(--joplin-line-height);
font-style: italic;
max-width: 70em;
margin-top: 5px;
}

View File

@@ -0,0 +1,9 @@
.setting-header {
display: block;
color: var(--joplin-color);
font-size: calc(var(--joplin-font-size) * 1.25);
font-weight: 500;
margin-bottom: var(--joplin-main-padding);
line-height: var(--joplin-line-height);
}

View File

@@ -0,0 +1,20 @@
.setting-label {
display: block;
color: var(--joplin-color);
font-size: calc(var(--joplin-font-size) * 1.083333);
font-weight: 500;
margin-bottom: calc(var(--joplin-main-padding) / 2);
line-height: var(--joplin-line-height);
&.-sub-label {
opacity: 0.7;
}
&.-for-checkbox {
margin-left: 5px;
margin-bottom: 0;
display: inline;
background-color: transparent;
}
}

View File

@@ -0,0 +1,18 @@
.setting-tab-panel {
display: flex;
flex-grow: 1;
flex-shrink: 1;
min-height: 0;
&.-hidden {
display: none;
}
&:focus-visible {
// Use a border rather than an outline -- an outline would be shown outside of the screen
// and thus invisible.
border: 1px solid var(--joplin-focus-outline-color);
outline: none;
}
}

View File

@@ -1,49 +1,128 @@
import styled from 'styled-components';
import * as React from 'react';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { blur, focus } from '@joplin/lib/utils/focusHandler';
const DialogModalLayer = styled.div`
z-index: 9999;
display: flex;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.6);
align-items: flex-start;
justify-content: center;
overflow: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
`;
const DialogRoot = styled.div`
background-color: ${props => props.theme.backgroundColor};
padding: 16px;
box-shadow: 6px 6px 20px rgba(0,0,0,0.5);
margin: 20px;
min-height: fit-content;
display: flex;
flex-direction: column;
border-radius: 10px;
`;
type OnCancelListener = ()=> void;
interface Props {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
renderContent: Function;
className?: string;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onClose?: Function;
onCancel?: OnCancelListener;
contentStyle?: React.CSSProperties;
children: ReactNode;
}
export default function Dialog(props: Props) {
return (
<DialogModalLayer className={props.className}>
<DialogRoot>
{props.renderContent()}
</DialogRoot>
</DialogModalLayer>
const Dialog: React.FC<Props> = props => {
// For correct focus handling, the dialog element needs to be managed separately from React. In particular,
// just after creating the dialog, we need to call .showModal() and just **before** closing the dialog, we
// need to call .close(). This second requirement is particularly difficult, as this needs to happen even
// if the dialog is closed by removing its parent from the React DOM.
//
// Because useEffect cleanup can happen after an element is removed from the HTML DOM, the dialog is managed
// using native HTML APIs. This allows us to call .close() while the dialog is still attached to the DOM, which
// allows the browser to restore the focus from before the dialog was opened.
const dialogElement = useDialogElement(props.onCancel);
useDialogClassNames(dialogElement, props.className);
const [contentRendered, setContentRendered] = useState(false);
useEffect(() => {
if (!dialogElement || !contentRendered) return;
if (!dialogElement.open) {
dialogElement.showModal();
}
}, [dialogElement, contentRendered]);
if (dialogElement && !contentRendered) {
setContentRendered(true);
}
const content = (
<div className='content' style={props.contentStyle}>
{props.children}
</div>
);
}
return <>
{dialogElement && createPortal(content, dialogElement)}
</>;
};
const useDialogElement = (onCancel: undefined|OnCancelListener) => {
const [dialogElement, setDialogElement] = useState<HTMLDialogElement|null>(null);
const onCancelRef = useRef(onCancel);
onCancelRef.current = onCancel;
useEffect(() => {
const dialog = document.createElement('dialog');
dialog.addEventListener('click', event => {
const onCancel = onCancelRef.current;
const isBackgroundClick = event.target === dialog;
if (isBackgroundClick && onCancel) {
onCancel();
}
});
dialog.classList.add('dialog-modal-layer');
dialog.addEventListener('cancel', event => {
const canCancel = !!onCancelRef.current;
if (!canCancel) {
// Prevents [Escape] from closing the dialog. In many places, this is handled
// by external logic.
// See https://stackoverflow.com/a/61021326
event.preventDefault();
}
});
const removedReturnValue = 'removed-from-dom';
dialog.addEventListener('close', () => {
const closedByCancel = dialog.returnValue !== removedReturnValue;
if (closedByCancel) {
onCancelRef.current?.();
}
// Work around what seems to be an Electron bug -- if an input or contenteditable region is refocused after
// dismissing a dialog, it won't be editable.
// Note: While this addresses the issue in the note title input, it does not address the issue in the Rich Text Editor.
if (document.activeElement?.tagName === 'INPUT') {
const element = document.activeElement as HTMLElement;
blur('Dialog', element);
focus('Dialog', element);
}
});
document.body.appendChild(dialog);
setDialogElement(dialog);
return () => {
if (dialog.open) {
// .close: Instructs the browser to restore keyboard focus to whatever was focused
// before the dialog.
dialog.close(removedReturnValue);
}
dialog.remove();
};
}, []);
return dialogElement;
};
const useDialogClassNames = (dialogElement: HTMLElement|null, classNames: undefined|string) => {
useEffect(() => {
if (!dialogElement || !classNames) {
return () => {};
}
// The React className prop can include multiple space-separated classes
const newClassNames = classNames
.split(/\s+/)
.filter(name => name.length && !dialogElement.classList.contains(name));
dialogElement.classList.add(...newClassNames);
return () => {
dialogElement.classList.remove(...newClassNames);
};
}, [dialogElement, classNames]);
};
export default Dialog;

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