Compare commits

...

352 Commits

Author SHA1 Message Date
renovate[bot] 9fc76f4e4c
Update dependency pg to v8.16.0 (#13223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:27:13 +01:00
renovate[bot] 981f15d85c
Update dependency node-mocks-http to v1.17.2 (#13221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:26:43 +01:00
renovate[bot] a59594db3b
Update dependency nodejs to v23.11.0 (#13222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 16:26:35 +01:00
JZou-Code 8c8190e2e9
Desktop: Fixes #12239: Prevent the default cut action handler to avoid double deletion (#13208) 2025-09-16 13:22:26 +01:00
Henry Heino d7e7ff77e8
Chore: Mobile: Add additional logging to help debug toolbar issue (#13224)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-16 13:18:12 +01:00
Henry Heino e33c142c5a
Chore: Web: Skip secondary single-instance check in dev mode (#13225)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-16 12:52:43 +01:00
Laurent Cozic 97d3a8243d
Chore: Fix CI (#13227) 2025-09-16 08:50:28 +01:00
renovate[bot] f1716a3edb
Update dependency @pmmmwh/react-refresh-webpack-plugin to ^0.6.0 (#13219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 18:39:20 +01:00
renovate[bot] 1436f5867d
Update dependency androidx.documentfile:documentfile to v1.1.0 (#13220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 18:37:53 +01:00
renovate[bot] d754b8fe0c
Update dependency @rollup/plugin-commonjs to v28.0.6 (#13218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 17:05:58 +01:00
renovate[bot] 4f58055cc1
Update dependency @react-native/metro-config to v0.79.4 (#13217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 16:38:15 +01:00
renovate[bot] 98697e1db4
Update dependency style-to-js to v1.1.17 (#13209)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-15 16:30:10 +01:00
renovate[bot] 8ac65a08c1
Update dependency @react-native/babel-preset to v0.79.4 (#13213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 15:06:33 +01:00
renovate[bot] 2b86d83290
Update dependency @babel/plugin-transform-export-namespace-from to v7.27.1 (#13212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 13:18:34 +01:00
renovate[bot] 09cafe99d1
Update bitnami/postgresql Docker tag to v17.4.0 (#13211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 11:33:22 +01:00
renovate[bot] 6fce844cbf
Update dependency webpack-dev-server to v5.2.2 (#13210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 09:01:07 +01:00
renovate[bot] 52de8c071f
Update dependency glob to v11.0.3 (#13205)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-15 09:00:01 +01:00
renovate[bot] 537543cc8a
Update dependency react-native-zip-archive to v7.0.2 (#13207)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 00:29:44 +00:00
renovate[bot] ff16453299
Update dependency js-draw to v1.30.1 (#13206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 00:27:57 +00:00
renovate[bot] 210deec495
Update dependency form-data to v4.0.3 (#13203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 19:03:16 +01:00
renovate[bot] e96baea005
Update dependency @types/tar-stream to v3.1.4 (#13202)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 19:03:05 +01:00
renovate[bot] ae24b91f25
Update dependency @types/node to v18.19.112 (#13204)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 19:02:56 +01:00
renovate[bot] f2e5118bf5
Update dependency @types/serviceworker to v0.0.139 (#13201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 12:36:40 +00:00
renovate[bot] 72698ec573
Update dependency @react-native/metro-config to v0.79.3 (#13198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-14 09:35:05 +01:00
renovate[bot] 68abc27c6a
Update dependency @types/react to v18.3.23 (#13200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-14 09:34:56 +01:00
renovate[bot] 1acb3d0726
Update dependency @rollup/plugin-commonjs to v28.0.5 (#13199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-14 09:34:48 +01:00
Joplin Bot 5bf97dc3b8 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-14 01:38:57 +00:00
Laurent Cozic e0e04fbc91 Chore: Fixed type error 2025-09-14 00:44:36 +01:00
Laurent Cozic 625cd1221c Doc: Update sponsors 2025-09-14 00:41:03 +01:00
Henry Heino 110d5bde2d
Desktop: Fix error dialogs fail to appear in certain cases (#13179) 2025-09-13 14:21:38 +01:00
renovate[bot] 93a85b3207
Update dependency @types/node to v18.19.111 (#13165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-13 14:18:04 +01:00
renovate[bot] ff305f42fd
Update dependency @js-draw/material-icons to v1.30.1 (#13164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-13 14:17:50 +01:00
Henry Heino 99ba854ee1
Chore: Cli: Fix CLI app integration tests (#13089) 2025-09-13 14:17:40 +01:00
mrjo118 38b368e997
Mobile: Resolves #12936: Allow expanding and collapsing the title field across multiple lines (#13016) 2025-09-13 14:08:31 +01:00
Henry Heino f9ffe6c4e6
Desktop,Mobile,Cli: Fix notes are moved to the conflict folder when a folder is unshared (#12993)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-13 14:08:19 +01:00
pedr 5adc0170fc
All: Resolves #8718: Delete all note revisions when the note is permanently deleted (#12609) 2025-09-13 14:06:56 +01:00
mrjo118 f54c364b4d
Desktop, Mobile: Automatically retrigger the sync if there are more unsynced outgoing changes when sync completes (#12989) 2025-09-13 14:05:31 +01:00
mrjo118 9f541b9b9d
Desktop, Mobile: Add support for mixed case tags (#12931)
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
2025-09-13 14:01:33 +01:00
Henry Heino bd0af08c57
Docs: REST API: Add descriptions for "is_shared" and "share_id" (#13186) 2025-09-13 13:52:46 +01:00
Henry Heino ac06c6750d
Android: Fixes #13113: Fix compatibility with 16-KB-page-size devices: Remove Vosk (#13189) 2025-09-13 13:52:12 +01:00
renovate[bot] 23b07094b7
Update dependency @react-native/babel-preset to v0.79.3 (#13195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 13:48:57 +01:00
Joplin Bot 7eefc016de Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-13 12:45:33 +00:00
Laurent Cozic c002be76cd Doc: Update sponsors 2025-09-13 12:00:32 +01:00
renovate[bot] 2cd29aaaea
Update dependency react-native-image-picker to v8.2.1 (#13002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-09-12 22:48:44 +01:00
Laurent Cozic 4cb6b01c71 Server: Clean-up SAML login section 2025-09-12 15:14:22 +01:00
Joplin Bot 91c79b9488 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-10 12:42:24 +00:00
Henry Heino fc516d05b3
Docs: Update the Rich Text Editor documentation (#13171) 2025-09-10 10:44:29 +01:00
Henry Heino 2769c9586c
Mobile: Fixes #13138: Rich Text Editor: Fix image size lost on change (#13172) 2025-09-10 10:06:40 +01:00
Henry Heino fd15d5a6d3
Mobile: Rich Text Editor: Accessibility: Fix font size setting not respected (#13174) 2025-09-10 10:05:51 +01:00
krevad 7237d7faa7
All: Translation: Update sv.po (#13170)
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
2025-09-09 19:37:19 -04:00
Henry Heino 3025d62568
Chore: Fix CI (#13168) 2025-09-09 22:31:55 +01:00
renovate[bot] 5b5dcf34a1
Update dependency @axe-core/playwright to v4.10.2 (#13162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 09:54:36 +01:00
Laurent Cozic 9e8500c148 Server v3.4.3 2025-09-09 09:47:29 +01:00
Laurent Cozic 4f1999f921 Merge branch 'release-3.4' into dev 2025-09-09 09:46:39 +01:00
Laurent Cozic 6ee9571069 iOS 13.4.3 2025-09-09 09:25:40 +01:00
krevad 10663b1494
All: Translation: Update sv.po (#13163) 2025-09-09 04:15:15 -04:00
Laurent Cozic f25db9bbd7 Android 3.4.7 2025-09-09 09:14:11 +01:00
Laurent Cozic 44ac261304 Desktop release v3.4.11 2025-09-09 09:05:11 +01:00
renovate[bot] eac995a209
Update dependency esbuild to v0.25.5 (#13040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
2025-09-09 00:32:19 +01:00
Henry Heino 15c973e885
Chore: Mobile: Add additional plugin panel integration tests (#13152) 2025-09-09 00:31:12 +01:00
Henry Heino 1762f9485f
Web: Fixes #13153: Fix installing certain plugins (#13154) 2025-09-09 00:30:48 +01:00
Henry Heino 7777f8428f
Desktop: Upgrade to Electron 37.4.0 (#13156) 2025-09-09 00:30:06 +01:00
Henry Heino 948aa9db4f
Mobile: Upgrade react-native-quick-crypto to v0.7.17 (#13155) 2025-09-09 00:07:29 +01:00
Henry Heino fdde04ee85
Desktop,Mobile,Cli: Support accepting shares with a new key format (#12829) 2025-09-08 23:56:40 +01:00
Laurent Cozic f77a20f5d5 Merge branch 'release-3.4' into dev 2025-09-08 23:55:24 +01:00
Henry Heino d43aa2a3e6
Web: Update the beta notice (#13150) 2025-09-08 23:47:19 +01:00
Henry Heino 04d5ce13c2
Chore: Android: Compile Whisper with support for 16 KB pages (#13118) 2025-09-08 16:50:48 +01:00
Laurent Cozic 3b764ba06a
Server: Remove the need to install pm2-logrotate on startup so that image can work in a closed environment (#13149) 2025-09-08 16:37:35 +01:00
Henry Heino 5492ce55fa
Server: Fixes #12984: Improve handling of concurrent deletion requests for the same item (#13092) 2025-09-08 12:03:20 +01:00
Henry Heino f6b3f9860c
Cli: Fix last change sometimes lost when not in TUI mode (#13090) 2025-09-08 12:03:13 +01:00
Henry Heino 88f687ba6a
Chore: Sync fuzzer: Add actions for publishing and unpublishing notes (#13062) 2025-09-08 12:02:53 +01:00
Henry Heino 1f0a98999f
Desktop, Mobile: Fixes #12987: Fix images rendered in the Markdown editor don't reload when downloaded (#13045) 2025-09-08 12:01:54 +01:00
mrjo118 69135c3bea
Mobile: Fixes #12956: Resize the notes menu to the viewport when the keyboard is open (#13035) 2025-09-08 12:01:24 +01:00
pedr c27d542a4b
Desktop: Fixes #12049: Fix files without extension not being imported properly (#12974)
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
2025-09-08 11:46:36 +01:00
mrjo118 bd1c2534c5
Mobile: Fixes #13095: Fix long note title doesn’t wrap properly for To Do type note (#13099) 2025-09-08 11:05:19 +01:00
mrjo118 72513b520c
Android: Fixes #13079: Fix dropdown menus are offset on Android 15+ (#13106) 2025-09-08 11:04:46 +01:00
Henry Heino ec0f9ef9bc
Server: Fix unique constraint error when multiple `createSharedFolderUserItems` are run concurrently (#13112) 2025-09-08 11:03:28 +01:00
Henry Heino 818bc3218a
Mobile: Improve tag dialog performance with long tags and many tags (#13117) 2025-09-08 11:03:01 +01:00
Henry Heino 82760a5b6a
Web: Show a "Give feedback" banner and link to a survey (#13125) 2025-09-08 10:59:40 +01:00
mrjo118 5ba9a16cfd
Mobile: Fixes #13116: Fix tag association screen no longer searches case insensitively or searches tag endings (#13128) 2025-09-08 10:59:01 +01:00
Henry Heino 68fc91fdc7
Desktop: Resolves #13096: Prefer user-specified CSS page sizing when printing to PDF (#13130) 2025-09-08 10:58:16 +01:00
Henry Heino bdc4687327
Chore: Refactor WebViewController (#13133) 2025-09-08 10:56:51 +01:00
Henry Heino 3a9f57e13f
Cli: Fixes #13086: Fix "use" command when not in TUI mode (#13091) 2025-09-08 10:56:08 +01:00
Henry Heino b72c48c693
Mobile, Desktop: Fixes #13103: Fix error when saving in-editor rendering-related settings (#13105) 2025-09-08 10:56:01 +01:00
Henry Heino f1e42f3bac
iOS: Fixes #13111: Fix "scan notebook" tool on iOS (#13114) 2025-09-08 10:55:48 +01:00
Henry Heino 93c908286d
Mobile: Plugins: Fix renderer plugins that use the `settingValue` API (#13131) 2025-09-08 10:55:42 +01:00
Henry Heino 4eb8777ed0
Mobile: Fix light bar shown above header in dark mode (#13132) 2025-09-08 10:55:15 +01:00
summoner 5e1909cee0
All: Translation: Update hu_HU.po (#13142) 2025-09-08 00:34:54 -04:00
pplulee 2e7b312415
All: Translation: Update zh_CN.po (#13137) 2025-09-06 17:02:31 -04:00
Joplin Bot 7735a59fc1 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-04 18:26:55 +00:00
Laurent Cozic 41d6e912a7 Doc: Updated sponsors 2025-09-04 17:43:49 +02:00
Joplin Bot 4c2fae8423 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-03 01:00:02 +00:00
Laurent Cozic b72c134890 Doc: Update sponsors 2025-09-02 23:01:06 +02:00
Joplin Bot 58a9c229bb Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-01 18:25:20 +00:00
Laurent Cozic d8c203bb8a Merge branch 'release-3.4' into dev 2025-09-01 14:48:55 +02:00
Laurent Cozic 9020c07825 lock files 2025-09-01 14:48:51 +02:00
Laurent Cozic e884da8312 Android 3.4.6 2025-09-01 14:48:38 +02:00
Joplin Bot d134ea8bfe Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-01 12:33:37 +00:00
Henry Heino faa44468f3
Mobile: Plugins: Improve handling of invalid toolbar button enabled conditions (#13076) 2025-09-01 13:50:59 +02:00
Laurent Cozic 85585d16d2 Desktop release v3.4.10 2025-09-01 13:50:43 +02:00
Joplin Bot b9c5b8f187 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-09-01 01:12:51 +00:00
Henry Heino da8e638359
Chore: Mobile: Add test to verify that content scripts load in the note viewer (#13093) 2025-08-31 00:32:10 +02:00
Henry Heino 6482ab5a4e
Mobile: Plugin API: Fix compatibility with certain plugins targetting the desktop app (#13077) 2025-08-29 23:28:45 +02:00
Henry Heino ec74abe754
Mobile: Plugin API: Fix certain renderer plugins fail to load (#13078) 2025-08-29 23:28:39 +02:00
Henry Heino 859bc8d88e
Mobile: Plugins: Fix plugin panel buttons are offscreen on recent versions of Android (#13080) 2025-08-29 23:28:22 +02:00
Henry Heino 56ed471a2f
Chore: Rich Text Editor: Refactor editor dialog to simplify toggling the dialog from external commands (#13082) 2025-08-29 23:28:11 +02:00
Henry Heino 650594ecea
Chore: Sync fuzzer: Add action for deleting notes (#13083) 2025-08-29 23:28:00 +02:00
Henry Heino 3e9bb914e5
Android: Fixes #13015: Fix "edit profile" button is partially offscreen (#13084) 2025-08-29 23:27:51 +02:00
Henry Heino f75e911a4e
Docs: Update the privacy policy (#13087) 2025-08-29 23:27:44 +02:00
Eric Duarte 78fb07d4c7
All: Translation: Update ca.po (#13065) 2025-08-28 17:50:34 -04:00
Henry Heino 6390ef43ed
Desktop: Clarify handwritten text transcription setting (#13073)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-28 09:20:55 +03:00
Henry Heino 78c5c4d7c3
Android: Accessibility: Fix tag search input loses focus when submitted by pressing "enter" (#13070) 2025-08-28 09:20:10 +03:00
Henry Heino 0d1d50768b
Android: Fix shadow shown above the screen header (#13074) 2025-08-28 09:04:10 +03:00
Henry Heino 57093b35ea
Android: Fixes #12960: Rich Text Editor: Fix pressing enter does nothing in some cases (#13075) 2025-08-28 09:03:37 +03:00
Laurent Cozic cba5cf660b Desktop release v3.4.9 2025-08-27 22:09:47 +03:00
Laurent Cozic 0024722c79 Desktop: Clarified that handwritten transcription may not always work 2025-08-27 22:09:26 +03:00
Henry Heino bc2832e78f
Chore: Desktop: Allow access to more Joplin APIs from the desktop development tools in dev mode (#13052) 2025-08-27 22:05:52 +03:00
Henry Heino 424cc96d36
Chore: Sync fuzzer: Fix incorrect expected state after removing the last user from a share (#13061) 2025-08-27 22:03:17 +03:00
Henry Heino 56fd5d828f
Android: Fixes #12952: External keyboard: Fix adding tags by pressing enter on certain Android devices (#13069) 2025-08-27 22:02:48 +03:00
Henry Heino 03843b087a
Desktop: Fixes #12816: Accessibility: Fix dismissing the alarm dialog by pressing escape (#13068) 2025-08-27 22:02:34 +03:00
Henry Heino b179509dd3
Desktop: Fixes #12855: Legacy editor: Fix plugin support (#13066) 2025-08-27 22:02:09 +03:00
Joplin Bot f6851314d2 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-27 18:26:06 +00:00
Laurent Cozic eaec45cb3f Doc: Update sponsors 2025-08-27 18:38:56 +03:00
Laurent Cozic 9be954496c Doc: Update sponsors 2025-08-27 17:45:40 +03:00
Laurent Cozic ac289c5198 Desktop: Clarified that handwritten transcription may not always work 2025-08-27 17:22:06 +03:00
Joplin Bot 98ef5e619b Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-27 12:33:13 +00:00
Laurent Cozic 62faa48aac lock files 2025-08-27 10:15:15 +03:00
Laurent Cozic 5daa7a1f4c Chore: By default, create new releases as pre-releases when publishing desktop app 2025-08-27 09:54:06 +03:00
Laurent Cozic 32be071601 CLI v3.4.1 2025-08-27 09:50:10 +03:00
Laurent Cozic 0dc63dd306 Lock file 2025-08-27 09:47:17 +03:00
Laurent Cozic 78ed58187a Releasing sub-packages 2025-08-27 09:46:45 +03:00
Laurent Cozic b8b8dd8011 iOS 13.4.2 2025-08-27 09:33:18 +03:00
Laurent Cozic 0bc72b45be Android 3.4.5 2025-08-27 09:28:51 +03:00
Laurent Cozic c52523134d Desktop release v3.4.8 2025-08-27 09:23:04 +03:00
Henry Heino aff871eee6
Desktop, Mobile: Markdown editor: Fix image rendering is disabled unless markup rendering is also enabled (#13056) 2025-08-27 09:21:26 +03:00
Henry Heino a5a68a2238
Cli: Add commands for publishing and unpublishing notes with Joplin Server (#13060) 2025-08-27 09:21:10 +03:00
Henry Heino e066b8f9bc
Desktop: Fixes #13043: OCR: Fix processing resources with an invalid `ocr_driver_id` (#13051) 2025-08-27 09:20:00 +03:00
Henry Heino e7827a3a64
Mobile: Remove the "beta" warning from the plugin settings screen (#13063) 2025-08-27 09:19:21 +03:00
Henry Heino 4ceca647dc
Desktop, Mobile: Resolves #13048: Auto-disable plugin settings when conflicting built-in settings are enabled (#13055) 2025-08-27 09:19:07 +03:00
Henry Heino 4185afebdb
Chore: Fix build (#13050) 2025-08-26 18:40:26 +03:00
Henry Heino c530b07f45
Desktop, Mobile: Disable in-editor Markdown rendering by default (can be re-enabled in settings > note) (#13022) 2025-08-26 10:56:53 +03:00
Henry Heino 0ed7daaed8
Linux, Windows: Fixes #12991, #12733: Fix notifications (#13007) 2025-08-26 10:56:42 +03:00
Henry Heino 2eb107c716
Desktop, Mobile: Add a "highlight active line" setting (#12967) 2025-08-26 10:49:59 +03:00
Henry Heino c99780db1b
Mobile: Rich Text Editor: Avoid rendering links with unknown protocols (#12943)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-26 10:49:26 +03:00
Henry Heino ac05b7d389
Mobile: Rich Text Editor: Fix additional blank lines added around list items on save (#12935) 2025-08-26 10:46:00 +03:00
Henry Heino 9719d82c47
Desktop: Fixes #13024: OCR: Fix infinite loop (#13025) 2025-08-26 10:45:27 +03:00
Henry Heino 48694a585f
Mobile: Fixes #12953: Allow the tag dialog to scroll when little screen space is available (#13028) 2025-08-26 10:44:32 +03:00
Henry Heino b577a27887
Mobile: Fixes #13027: Fix additional space added around app content in landscape mode (#13030) 2025-08-26 10:44:06 +03:00
Helmut K. C. Tessarek 9f649c9fc2
All: Update translations 2025-08-25 19:40:33 -04:00
Eric Duarte 8c9c5d13bd
All: Translation: Update es_ES.po (#13041) 2025-08-25 19:32:14 -04:00
renovate[bot] 96692de93c
Update dependency @types/react to v18.3.23 (#13042)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 13:44:38 +00:00
Joplin Bot 3d8e1dd146 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-24 18:26:08 +00:00
Joplin Bot 227e41b69a Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-24 12:31:05 +00:00
renovate[bot] a616e26a0f
Update dependency react-native-safe-area-context to v5.4.1 (#13000)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-24 13:02:03 +03:00
Joplin Bot ba0e7e2226 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-23 12:30:39 +00:00
Laurent Cozic b5a4ba554d Doc: Add sponsor 2025-08-23 13:14:58 +03:00
Arda Kılıçdağı 9037da8f2d
All: Translation: Update tr_TR.po (#13019) 2025-08-22 16:07:31 -04:00
renovate[bot] 6998606ec9
Update dependency pg to v8.15.6 (#13021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 17:00:30 +00:00
Laurent Cozic 66d52c90a3 Desktop release v3.4.7 2025-08-22 13:19:27 +03:00
renovate[bot] f6fb1f7fbf
Update dependency pg to v8.15.5 (#13001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-22 13:14:06 +03:00
Henry Heino 3aac6043da
Chore: Sync fuzzer: Support testing Joplin Cloud readonly shares (#13003) 2025-08-22 11:33:54 +03:00
Henry Heino ae170e0aa0
Desktop: Fixes #12998: Fix error logged when rendering a non-existent resource (#13004) 2025-08-22 11:33:16 +03:00
Henry Heino 371f027a24
MacOS: Fix startup failure when unable to access the keychain (#13006) 2025-08-22 11:32:59 +03:00
Henry Heino 37422f316e
Desktop: Downgrade to Electron 35.7.5 (#13013) 2025-08-22 11:30:39 +03:00
Henry Heino a9f284ae45
Desktop: Fixes #13009: Fix custom root CA support (#13018) 2025-08-22 11:29:54 +03:00
Milo Ivir fd2f69cc73
All: Translation: Update hr_HR.po (#13011) 2025-08-21 18:45:39 -04:00
Joplin Bot c4eab3c79c Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-21 01:03:33 +00:00
renovate[bot] a0b9c6376e
Update dependency react-native-image-picker to v8 (#12997)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 23:37:39 +03:00
Henry Heino e2fc056369
Desktop,Mobile,Cli: Fixes #12648: Fix unshare action requires two syncs to be reflected locally (#12999) 2025-08-20 23:36:47 +03:00
renovate[bot] 453b4705b1
Update dependency @types/node to v18.19.103 (#12985)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 19:25:54 +00:00
Laurent Cozic 4128061e40 Desktop release v3.4.6 2025-08-20 22:22:42 +03:00
Joplin Bot 432b0ca870 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-20 12:32:59 +00:00
renovate[bot] c484cd2e48
Update dependency sass to v1.87.0 (#12995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 15:06:57 +03:00
Laurent Cozic 58f0725c6b Doc: Add sponsor 2025-08-20 15:05:28 +03:00
Henry Heino bf8fbec0cd
Chore: Sync fuzzer: Add support for adding and removing share participants (#12988) 2025-08-20 09:46:23 +03:00
pedr f1d452f130
Server: Fixes #12983: Not handling correctly non JSON error responses from Transcribe (#12986) 2025-08-20 09:46:15 +03:00
Henry Heino 26012cd7d5
Cli,Mobile,Desktop: Shared folders: Fix moving shared subfolder to toplevel briefly marks it as a toplevel share (#12964) 2025-08-20 09:39:39 +03:00
mrjo118 a414241541
Mobile: Improve tag screen usability to allow add or remove tag with a single press, when the keyboard is open (#12954) 2025-08-20 09:33:31 +03:00
Henry Heino 0f13bf9d51
Mobile: Rich Text Editor: Support rendering subscript, superscript, and highlighted formatting (#12944) 2025-08-20 09:33:13 +03:00
Henry Heino c142c5c5c0
Desktop,Mobile: Markdown editor: Toggle checkboxes on ctrl-click (#12927) 2025-08-20 09:32:16 +03:00
Henry Heino af5c0135dc
Mobile: Rich Text Editor: Enable syntax highlighting and auto-indent in the code block editor (#12909) 2025-08-20 09:29:30 +03:00
pedr 8a811b9e78
Doc: Resolves #12861: Add end point documentation for Transcribe (#12870)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-20 09:29:12 +03:00
Henry Heino 602484f143
Desktop: Upgrade to Electron v37.3.0 (#12951) 2025-08-20 08:53:50 +03:00
renovate[bot] dc84db1657
Update dependency sharp to v0.34.2 (#12982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-20 08:33:29 +03:00
Henry Heino f5882ecfcc
Chore: Improve type safety (#12992) 2025-08-20 08:33:10 +03:00
Laurent Cozic 30000c34ec Cli: If no notebook is provided when importing a file, use the default one 2025-08-19 23:33:52 +03:00
renovate[bot] 6e3df1bd90
Update dependency @types/react to v18.3.22 (#12990)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 18:59:17 +03:00
Joplin Bot 67196ac0b2 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-19 12:33:44 +00:00
Laurent Cozic 69646b5522 Doc: Update sponsors 2025-08-19 12:31:13 +03:00
Laurent Cozic 9147afce9a Server v3.4.2 2025-08-18 19:54:35 +03:00
Henry Heino c92701c52f
iOS: Rich Text Editor: Fix the "edit" button for code blocks (#12924) 2025-08-18 18:46:02 +03:00
pedr ab3e9d1a3e
Transcribe: Fixes: Use latest version of joplin/htr-cli available (#12875) 2025-08-18 18:45:52 +03:00
Henry Heino f9cab8843b
Chore: Fix tsc (#12981) 2025-08-18 18:40:58 +03:00
yuudi c36289c024
Server: Fixes #12947 Skip CORS check for SAML callback (#12948)
Co-authored-by: yuudi <yuudi@users.noreply.github.com>
2025-08-18 16:10:20 +03:00
Henry Heino 60b6db8cd4
Mobile: Rich Text Editor: Add basic support for collapsible <details> blocks (#12946) 2025-08-18 16:10:00 +03:00
Henry Heino bbd8f6f40e
Mobile: Rich Text Editor: Fix adding headings moves the cursor to the next line (#12934) 2025-08-18 16:07:55 +03:00
Henry Heino 34b7f4e1f8
Chore: Sync fuzzer: Fix "DecryptionWorker: Cannot start because..." warning (#12925) 2025-08-18 16:04:26 +03:00
Henry Heino 06b681d897
Chore: Sync fuzzer: Add new possible actions: Adding and syncing a new temporary client on an existing account (#12741) 2025-08-18 15:57:44 +03:00
pedr f02a94bef5
Transcribe: Fixes #12766: Remove processed files and clean up after a retention period (#12827) 2025-08-18 15:57:34 +03:00
Miguel Matos ae6b57c5a5
CLI: Add collapsible notebooks functionality (#12718) 2025-08-18 15:55:55 +03:00
Henry Heino 88ab916008
Mobile: Rich Text Editor: Support rendering table of contents blocks (#12949) 2025-08-18 11:35:48 +03:00
pedr 97b0ffc263
Transcribe: #12883: Disable JobProcessor tests by default (#12955) 2025-08-18 11:34:26 +03:00
pedr ff8848d138
Desktop: Fixes #12315: Clicking Edit URL button in Note properties does not focus in url field (#12970) 2025-08-18 11:20:54 +03:00
renovate[bot] 2b686e6318
Update dependency @playwright/test to v1.52.0 (#12972)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 11:17:35 +03:00
renovate[bot] b913d18882
Update dependency @adobe/css-tools to v4.4.3 (#12979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-17 22:02:58 +00:00
renovate[bot] a2c9a01722
Update dependency @types/node to v18.19.101 (#12978)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-17 05:42:21 +00:00
Milo Ivir 000d23c20f
All: Translation: Update hr_HR.po (#12961) 2025-08-16 21:46:55 -04:00
Liffindra Angga Zaaldian 9e9f2f2930
All: Translation: Update id_ID.po (#12977) 2025-08-16 20:42:49 -04:00
VortexP c5a1a759c7
All: Translation: Update fi_FI.po (#12971) 2025-08-15 18:15:58 -04:00
cedecode 0b6a1c75ba
All: Translation: Update de_DE.po (#12966) 2025-08-14 22:09:33 -04:00
renovate[bot] 53a0f8ddbc
Update dependency python to v3.13.3 (#12965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 01:08:28 +02:00
Laurent Cozic 67eabb5038 Doc: Update recommended Postgres for JSB 2025-08-14 22:32:00 +02:00
Laurent Cozic 983fced410 Doc: Fixed Transcribe graph 2025-08-14 22:32:00 +02:00
Jozef Gaal 4f5bbc1132
All: Translation: Update sk_SK.po (#12950) 2025-08-14 00:22:49 -04:00
ERYpTION 2f10235ecb
All: Translation: Update da_DK.po (#12945) 2025-08-13 17:22:50 -04:00
Joplin Bot cfa7d6cb31 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-13 18:27:34 +00:00
renovate[bot] f5d62a50fe
Update dependency @types/serviceworker to v0.0.135 (#12937)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 10:12:25 +00:00
Laurent Cozic b52f5435aa Doc: Add Transcribe System Architecture documentation 2025-08-12 19:24:27 +01:00
renovate[bot] bfd5bfc004
Update dependency git to v2.48.1 (#12921)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 15:32:53 +01:00
renovate[bot] 82965fe991
Update dependency jsdom to v26.1.0 (#12922)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 15:32:45 +01:00
summoner b2c162c25b
All: Translation: Update hu_HU.po (#12918) 2025-08-10 16:05:34 -04:00
Mihai Vasiliu 022e76fe8d
All: Translation: Update ro_RO.po and ro_MD.po (#12917) 2025-08-10 16:04:28 -04:00
Joplin Bot 4b2d1895fd Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-10 18:26:11 +00:00
Joplin Bot 534507a31f Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-10 12:32:07 +00:00
Laurent Cozic 5b4a300c81 iOS 13.4.1 2025-08-10 10:41:49 +01:00
Laurent Cozic 1de0a59313 Chore: Fix iOS IPHONEOS_DEPLOYMENT_TARGET 2025-08-10 10:41:25 +01:00
Laurent Cozic f4dff92d2e Android 3.4.4 2025-08-10 10:34:02 +01:00
Laurent Cozic a5d37a0dca Desktop release v3.4.5 2025-08-10 10:26:53 +01:00
Laurent Cozic 75ef418b39 Update translations 2025-08-10 10:26:37 +01:00
Henry Heino 6bd702ae24
Mobile: Resolves #12843: Rich Text Editor: Improve support for HTML notes (#12912) 2025-08-10 09:32:42 +01:00
Laurent Cozic 9ea1808766 Update translations 2025-08-10 09:31:13 +01:00
Suchith 59f8dd36a6
Desktop: Fixes #12358: Selected emoji for new notebooks display too large until Joplin is restarted (#12888) 2025-08-10 09:30:42 +01:00
Henry Heino ea1d2e4878
Desktop, Mobile: Move several features from Extra Markdown Editor Settings into the main app (#12747) 2025-08-10 09:17:12 +01:00
Henry Heino 46ab00bfe4
Chore: Sync fuzzer: Re-use the same CLI process for commands run on the same client (#12913) 2025-08-10 09:14:25 +01:00
renovate[bot] 07465dd349
Update dependency dotenv to v16.5.0 (#12914)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-09 10:46:41 +01:00
renovate[bot] a288ffe338
Update dependency @types/node to v18.19.100 (#12904)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-08 21:20:11 +00:00
renovate[bot] dba62386b6
Update dependency @types/serviceworker to v0.0.134 (#12907)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-07 12:12:36 +01:00
Henry Heino 6704ab0d13
Mobile: Resolves #12841: Allow editing code blocks from the Rich Text Editor (#12906) 2025-08-07 10:18:09 +01:00
Arda Kılıçdağı 0312f2213d
All: Translation: Update tr_TR.po (#12905) 2025-08-06 20:23:39 -04:00
Chaitanya Gupta 2ac0b66ef6
Doc: Fix link to E2EE spec (#12902) 2025-08-06 13:05:42 +01:00
Henry Heino 639b261ee4
Mobile: Fixes #12844: Rich Text Editor: Make initial search behavior match the Markdown editor (#12878) 2025-08-06 11:10:14 +01:00
mrjo118 82bc819a21
Mobile: Fixes #12822: Fix switching between note and todo on mobile (#12849) 2025-08-06 11:09:05 +01:00
w568w 72f8ebe4ff
Desktop: Fixes #11871: Put crash dump files at the platform-compliant locations (#12839) 2025-08-06 11:08:29 +01:00
pedr 8c8a38e704
Desktop: Resolves #2059: Add option to transform HTML notes into Markdown (#12730)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-06 11:02:13 +01:00
mrjo118 358134038c
Desktop, Mobile: Fixes #12104: Ensure merges to revisions during cleaning are synced to the target (#12444) 2025-08-06 10:52:28 +01:00
Henry Heino 1f4b32a241
Desktop: Fixes #12235: Fix switching to the Markdown editor after pasting links (#12241) 2025-08-06 10:50:17 +01:00
Henry Heino 2a216f1e61
Server: Fix notebooks remain shared after being permanently deleted by the share owner (#12583) 2025-08-06 10:37:38 +01:00
pedr 3f75d770f7
Desktop: Resolves #12224: Add an option to enable or disable search in OCR text (#12578) 2025-08-06 10:37:20 +01:00
Henry Heino b6d32831c6
Mobile: Fixes #12880: Fix plugin support (#12890) 2025-08-06 10:23:40 +01:00
Henry Heino 788033cb5f
Mobile: Fixes #12891: Fix error logged when opening the Markdown editor (#12892) 2025-08-06 10:23:07 +01:00
klaas0 4e685ec687
Mobile: Resolves #12858: Fixed missing filename when a file is shared with the app (#12895) 2025-08-06 10:22:47 +01:00
renovate[bot] c60b703b9c
Update dependency ldapts to v7.4.0 (#12900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 10:19:03 +01:00
renovate[bot] f23e10a975
Update dependency @types/node to v18.19.99 (#12899)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 06:25:58 +00:00
renovate[bot] b9a71c0c3d
Update dependency sharp to v0.34.1 (#12898)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 04:43:57 +00:00
renovate[bot] f525c4179f
Update dependency react-native-share to v12.0.11 (#12897)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 02:34:38 +00:00
renovate[bot] 1dd0ec619f
Update dependency lint-staged to v15.5.2 (#12896)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 02:32:29 +00:00
renovate[bot] d2ee5411d0
Update dependency @types/react to v18.3.21 (#12884)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 22:56:45 +00:00
krevad a2472cb3b7
All: Translation: Update sv.po (#12893) 2025-08-05 18:55:12 -04:00
renovate[bot] ca8415f74a
Update dependency esbuild to v0.25.4 (#12889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 20:34:24 +00:00
renovate[bot] 853b792367
Update dependency @types/node to v18.19.98 (#12881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 20:31:35 +00:00
renovate[bot] 56d477f1c1
Update dependency esbuild to v0.25.4 (#12887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 16:37:20 +00:00
Laurent Cozic 020ba10c56
Update renovate.json5 2025-08-05 13:35:34 +01:00
pedr be09873c58
Desktop: Resolves #12087: Add shortcut to toggle between editors (#12869) 2025-08-05 12:43:58 +01:00
renovate[bot] 4d8a16bda7
Update dependency @types/mustache to v4.2.6 (#12867)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 12:28:03 +01:00
pedr f725d3895f
Transcribe: Resolves #12862: Add log statement signaling that the startup has finished (#12876) 2025-08-05 12:26:10 +01:00
pedr 0e19dce0d1
Transcribe: Fixes #12863: Improve error handling (#12873) 2025-08-05 10:53:42 +01:00
Joplin Bot 31c5058d5e Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-04 18:29:30 +00:00
Laurent Cozic 4d760303bc Android 3.4.3 2025-08-04 18:39:19 +01:00
Laurent Cozic 23e63e5fec Desktop release v3.4.4 2025-08-04 18:33:28 +01:00
renovate[bot] 3880352f53
Update dependency @react-native-documents/picker to v10.1.3 (#12865)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-03 20:34:46 +00:00
summoner 42a3c40702
All: Translation: Update hu_HU.po (#12864) 2025-08-03 12:56:34 -04:00
mrjo118 8e585640e7
Android: Fixes #12821: Fix on screen keyboard covers the markdown toolbar and contents on Android 15+ (#12838) 2025-08-03 17:30:15 +01:00
renovate[bot] cd3fb4e7ad
Update dependency sharp to v0.34.0 (#12854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-03 17:27:35 +01:00
Laurent Cozic 702b5b3c63
Server: Trying to fix a request parsing error that can potentially crash the error (#12860) 2025-08-03 15:04:54 +01:00
PanWor a80406dcb7
All: Translation: Update Polish pl_PL.po (#12857) 2025-08-02 17:06:09 -04:00
renovate[bot] ea8b6485d8
Update dependency pg-boss to v10.2.0 (#12850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-02 13:17:06 +01:00
renovate[bot] 1a2ef78726
Update dependency babel-plugin-react-native-web to v0.20.0 (#12848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-02 09:23:47 +01:00
renovate[bot] 63d5ffc796
Update dependency @types/serviceworker to v0.0.133 (#12846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-02 09:23:28 +01:00
renovate[bot] 15918a57aa
Update dependency react-refresh to v0.17.0 (#12847)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-02 09:23:13 +01:00
renovate[bot] 032e8b5596
Update dependency @types/react-dom to v18.3.7 (#12845)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 20:14:16 +01:00
Laurent Cozic ee091ede52
Update renovate.json5 2025-08-01 17:51:06 +01:00
Henry Heino 763e3f7479
Chore: Resolves #12814: Add additional logging to `DecryptionWorker` and `EncryptionService` (#12824) 2025-08-01 17:39:37 +01:00
Laurent Cozic 0089c62493 Transcribe v3.4.9 2025-08-01 17:32:56 +01:00
Laurent Cozic 20d6d56c02 Doc: Hide "Edit this page" link when printing 2025-08-01 17:32:33 +01:00
pedr 8b999f8dc6
Transcribe: Resolves #12831: Add support for transcribe server to the server docker compose configuration (#12832)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-01 17:31:59 +01:00
Laurent Cozic 0067ac126d Doc: Added technical info to JSB page 2025-08-01 16:28:01 +01:00
renovate[bot] c6f47a9084
Update eslint (#12833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-08-01 14:46:54 +01:00
Laurent Cozic 22817317f1 Transcribe v3.4.8 2025-08-01 13:30:54 +01:00
Laurent Cozic 9ba1c0db4e Chore: Also build ARM64 image for Transcribe 2025-08-01 13:30:34 +01:00
renovate[bot] 70d6c1225c
Update types (#12834)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 12:50:46 +01:00
Laurent Cozic b1f013a8c2 Transcribe v3.4.7 2025-08-01 12:14:32 +01:00
Laurent Cozic 8c66349907 Chore: Fixed credential for Transcribe release 2025-08-01 12:14:09 +01:00
Laurent Cozic 86b4f713ee Chore: Trying to disable Renovate React monorepo rule 2025-08-01 12:08:20 +01:00
Henry Heino f50dc6a536
Chore: Work around test failure in newer NodeJS versions (#12830) 2025-08-01 11:45:09 +01:00
Henry Heino 825ce51a3c
Chore: Resolves #12813: Move performance logger file labels to the corresponding log statements (#12820) 2025-08-01 11:43:05 +01:00
mrjo118 c5b6f0bca1
Mobile: Fixes #12783: Improve usability of inline search in notes (#12791) 2025-08-01 11:39:07 +01:00
Laurent Cozic 86934d502e Transcribe v3.4.6 2025-08-01 11:14:18 +01:00
Laurent Cozic c63ad17f98 Chore: Fixed Transcribe Docker image 2025-08-01 11:13:57 +01:00
Joplin Bot c746b5fdc2 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-08-01 01:19:23 +00:00
Laurent Cozic 949fb85755 Transcribe v3.4.5 2025-07-31 21:36:35 +01:00
Laurent Cozic 0f94cb8c17
Chore: Updated script to allow deploying Transcribe server (#12828) 2025-07-31 21:35:03 +01:00
Joplin Bot 7ba61bb585 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-07-31 18:28:37 +00:00
Henry Heino 00e4657a39
Desktop: Resolves #12714: Make more settings per-profile (application layout, note list style, and note list order) (#12825) 2025-07-31 17:12:31 +01:00
pedr cbdc98553a
Desktop, Server: Add transcribe functionality to Desktop though Joplin Server (#12670) 2025-07-31 16:42:03 +01:00
Laurent Cozic e3c2589a12 Doc: Allow setting the initial hosting type on the Plans page 2025-07-31 14:48:25 +01:00
Henry Heino 56b3cc3dc2
Android: Fixes #12782: Fix save button is invisible in release builds (#12826) 2025-07-31 13:59:25 +01:00
Suchith d59a09fd29
Desktop: Fixes #12233: Add tooltips to sidebar buttons (#12798) 2025-07-30 15:42:08 +01:00
Laurent Cozic 5a64222276 Desktop: Fixes #12816: Date/Time dialog button not visible in dark mode 2025-07-30 15:22:34 +01:00
Henry Heino 012297d52a
Android: Fixes #12781: Fix editor becomes blank after dismissing search (#12818) 2025-07-30 10:53:12 +01:00
Henry Heino 5e70bce2c3
Mobile: Performance: Improve Rich Text Editor startup performance (#12819) 2025-07-30 10:52:57 +01:00
Henry Heino 4c3eca1f18
Mobile: Add a Rich Text Editor (#12748) 2025-07-29 20:25:43 +01:00
Henry Heino c899f63a41
Chore: Resolves #12088: Desktop: Add performance logging statements to the startup code (#12812) 2025-07-29 19:57:12 +01:00
Liffindra Angga Zaaldian c838b86413
All: Translation: Update id_ID.po (#12815) 2025-07-29 06:57:18 -04:00
pedr 90d6d1747a
Transcribe: Fixes #12765: Removes file from temporary folder after storing it (#12795) 2025-07-28 18:32:10 +01:00
renovate[bot] 6e8ba8a536
Update dependency jsdom to v26 (#12809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 12:24:57 +01:00
bwat47 ffeb5f887a
Doc: Update s3.md for provider Cloudflare R2 (#12805) 2025-07-27 15:54:10 +01:00
Laurent Cozic 65bde86263 Doc: Added documentation for the CLA consent records tool and archives 2025-07-27 10:00:33 +01:00
Laurent Cozic 1c236ca73c Doc: Update CLA consent records 2025-07-27 09:36:28 +01:00
Laurent Cozic 2881280100 Merge branch 'cla_signatures' into dev 2025-07-27 09:33:50 +01:00
Laurent Cozic 954b48b779 Merge branch 'dev' into cla_signatures 2025-07-27 09:33:24 +01:00
Laurent Cozic 53e7b672b0 Chore: Recorded CLA consent documents 2025-07-27 09:33:22 +01:00
krevad ceaaab77e8
All: Translation: Update sv.po (#12800) 2025-07-26 18:00:43 -04:00
Eric Duarte c29bbe96f7
All: Translation: Update es_ES.po and ca.po (#12760) 2025-07-26 17:03:20 -04:00
Mihai Vasiliu db323ac585
All: Translation: Update ro_RO.po and ro_MD.po (#12799) 2025-07-26 16:54:55 -04:00
krevad dc8e3242f3
All: Translation: Update sv.po (#12797) 2025-07-26 11:00:38 -04:00
github-actions[bot] 9705941538
@krevad has signed the CLA in laurent22/joplin#12797 2025-07-26 09:58:22 +00:00
Joplin Bot 0cf9981ac7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-07-26 01:09:51 +00:00
Laurent Cozic b93ee3469b Desktop release v3.4.3 2025-07-25 19:22:30 +01:00
Laurent Cozic 73e5bc74a5 Android 3.4.2 2025-07-25 19:21:50 +01:00
Henry Heino 6c761b3fb4
Desktop,Mobile: Fixes #12573: Markdown editor: Make list indentation size equivalent to four spaces (#12794) 2025-07-25 19:18:14 +01:00
Henry Heino e13985a952
Desktop: Fixes #12790: Plugins: Fix importing sqlite3 (#12792) 2025-07-25 19:17:40 +01:00
Laurent Cozic 8b912b22cf Chore: Fixed bad merge 2025-07-25 09:25:17 +01:00
Henry Heino 4c90cd62fe
Chore: Mobile: Log startup performance information (#12776)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-07-25 09:20:38 +01:00
Henry Heino 999ec8c11f
Android: Fix title bar is partially hidden by the screen header (#12785) 2025-07-25 09:19:23 +01:00
Henry Heino d8e73f3141
Chore: Mobile: Fix warning (#12786) 2025-07-25 09:19:11 +01:00
renovate[bot] 3b1a4e8209
Update dependency react-native-paper to v5.13.5 (#12784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 23:33:23 +00:00
renovate[bot] 1ff0f0f1c8
Update dependency @types/node to v18.19.87 (#12779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 13:48:29 +01:00
Joplin Bot 68863db4bd Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-07-24 12:36:28 +00:00
github-actions[bot] 83f1fcc228
@jordanhandy has signed the CLA in laurent22/joplin#12701 2025-07-11 13:05:02 +00:00
github-actions[bot] b03e370d2b
@khemarato has signed the CLA in laurent22/joplin#12696 2025-07-11 06:21:31 +00:00
github-actions[bot] 4ddd5c4558
@laurent22 has signed the CLA in laurent22/joplin#12571 2025-07-04 16:36:54 +00:00
github-actions[bot] 7746694dca
@SAYAN02-DEV has signed the CLA in laurent22/joplin#12666 2025-07-03 07:56:53 +00:00
github-actions[bot] f1ac95a1c7
@bekemax has signed the CLA in laurent22/joplin#12586 2025-06-27 17:11:39 +00:00
github-actions[bot] 78e9ced96c
@jhult has signed the CLA in laurent22/joplin#12567 2025-06-20 14:38:18 +00:00
github-actions[bot] bba6ede569
@Robin-Sch has signed the CLA in laurent22/joplin#12563 2025-06-18 21:52:29 +00:00
github-actions[bot] 7a26d4f336
@Gustavo-V-F has signed the CLA in laurent22/joplin#12537 2025-06-15 20:44:49 +00:00
github-actions[bot] 04a976e459
@ShawnZhang31 has signed the CLA in laurent22/joplin#12345 2025-05-27 01:28:32 +00:00
github-actions[bot] ecfef1a9da
@NBA2K1 has signed the CLA in laurent22/joplin#12316 2025-05-18 07:40:04 +00:00
github-actions[bot] e422a88bb0
@eyaaba has signed the CLA in laurent22/joplin#12260 2025-05-10 11:48:28 +00:00
github-actions[bot] 74ef89d25b
@SilviaAC has signed the CLA in laurent22/joplin#12256 2025-05-09 09:43:31 +00:00
github-actions[bot] 28b7251e16
@yatishgoel has signed the CLA in laurent22/joplin#12185 2025-04-29 09:12:38 +00:00
github-actions[bot] 9218c7df1f
@BellezaEmporium has signed the CLA in laurent22/joplin#12161 2025-04-28 09:42:54 +00:00
627 changed files with 71709 additions and 35887 deletions

View File

@ -15,6 +15,23 @@
# POSTGRES_PORT=5432
# POSTGRES_HOST=localhost
# =============================================================================
# TRANSCRIBE CONFIG EXAMPLE
# -----------------------------------------------------------------------------
# This service is not required, and it will be ignored by using --profile server
# when running docker-compose. If you want to use it, you need to set the
# following environment variables.
# =============================================================================
# TRANSCRIBE_API_KEY=secret_string_shared_between_server_and_transcribe
# TRANSCRIBE_ENABLED=true
# QUEUE_DATABASE_NAME=transcribe
# QUEUE_DATABASE_USER=transcribe
# QUEUE_DATABASE_PASSWORD=transcribe
# QUEUE_DATABASE_PORT=5431
# HTR_CLI_IMAGES_FOLDER=/home/user/images_storage
# =============================================================================
# DEV CONFIG EXAMPLE
# -----------------------------------------------------------------------------

View File

@ -10,13 +10,16 @@ QUEUE_TTL=900000
QUEUE_RETRY_COUNT=2
QUEUE_MAINTENANCE_INTERVAL=30000
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:0.0.2
# Fullpath to images folder
HTR_CLI_IMAGES_FOLDER=/home/user/joplin/packages/transcribe/images
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:latest
# Fullpath to images folder e.g.:
#HTR_CLI_IMAGES_FOLDER=/home/user/joplin/packages/transcribe/images
HTR_CLI_IMAGES_FOLDER=
QUEUE_DRIVER=pg
# QUEUE_DRIVER=sqlite
FILE_STORAGE_MAINTENANCE_INTERVAL=3600000
FILE_STORAGE_TTL=604800000 # one week
# =============================================================================
# Queue driver
@ -27,4 +30,5 @@ QUEUE_DRIVER=pg
QUEUE_DATABASE_NAME=transcribe
QUEUE_DATABASE_USER=transcribe
QUEUE_DATABASE_PASSWORD=transcribe
QUEUE_DATABASE_PORT=5432
QUEUE_DATABASE_PORT=5432
QUEUE_DATABASE_HOST=localhost

View File

@ -55,6 +55,7 @@ packages/app-desktop/vendor/lib/
packages/app-mobile/packageInfo.js
packages/app-mobile/android
packages/app-mobile/**/*.bundle.js
packages/app-mobile/**/*.bundle.css
packages/app-mobile/web/public/pluginAssets/**/*
packages/app-mobile/ios
packages/app-mobile/lib/rnInjectedJs/
@ -74,6 +75,7 @@ packages/lib/services/database/types.ts
packages/lib/vendor/
packages/lib/vendor/fountain.min.js
packages/lib/welcomeAssets.js
packages/editor/*/vendor/
packages/plugins/**/api
packages/plugins/**/dist
packages/server/dist/
@ -94,8 +96,10 @@ packages/onenote-converter/pkg/onenote_converter.js
packages/app-cli/app/LinkSelector.js
packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/cli-integration-tests.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
packages/app-cli/app/command-cat.js
packages/app-cli/app/command-config.js
packages/app-cli/app/command-cp.js
@ -114,6 +118,8 @@ packages/app-cli/app/command-ls.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mv.js
packages/app-cli/app/command-publish.test.js
packages/app-cli/app/command-publish.js
packages/app-cli/app/command-ren.js
packages/app-cli/app/command-restore.js
packages/app-cli/app/command-rmbook.test.js
@ -126,6 +132,8 @@ packages/app-cli/app/command-share.test.js
packages/app-cli/app/command-share.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-unpublish.test.js
packages/app-cli/app/command-unpublish.js
packages/app-cli/app/command-use.js
packages/app-cli/app/command-version.js
packages/app-cli/app/gui/FolderListWidget.js
@ -133,6 +141,7 @@ packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/iterateStdin.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
@ -155,6 +164,8 @@ packages/app-desktop/app.reducer.js
packages/app-desktop/app.js
packages/app-desktop/bridge.js
packages/app-desktop/checkForUpdates.js
packages/app-desktop/commands/convertNoteToMarkdown.test.js
packages/app-desktop/commands/convertNoteToMarkdown.js
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyToClipboard.js
packages/app-desktop/commands/editProfileConfig.js
@ -195,6 +206,7 @@ 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
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
packages/app-desktop/gui/ConversionNotification/ConversionNotification.js
packages/app-desktop/gui/Dialog.js
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
@ -296,6 +308,7 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js
packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/getResourceBaseUrl.js
packages/app-desktop/gui/NoteEditor/utils/getWindowCommandPriority.js
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/markupRenderOptions.js
@ -663,6 +676,9 @@ 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/ExtendedWebView/utils/useCss.js
packages/app-mobile/components/FeedbackBanner.test.js
packages/app-mobile/components/FeedbackBanner.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
@ -671,48 +687,34 @@ packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.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
packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/types.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/addPluginAssets.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/makeResourceModel.js
packages/app-mobile/components/NoteBodyViewer/hooks/useContentScripts.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.js
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
packages/app-mobile/components/NoteBodyViewer/types.js
packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js
packages/app-mobile/components/NoteEditor/EditLinkDialog.js
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/RichTextEditor.test.js
packages/app-mobile/components/NoteEditor/RichTextEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
packages/app-mobile/components/NoteEditor/WarningBanner.js
packages/app-mobile/components/NoteEditor/commandDeclarations.js
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteItem.js
packages/app-mobile/components/NoteList.js
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/SafeAreaView.js
packages/app-mobile/components/ScreenHeader/Menu.js
packages/app-mobile/components/ScreenHeader/WarningBanner.test.js
packages/app-mobile/components/ScreenHeader/WarningBanner.js
@ -749,6 +751,7 @@ packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginNotification.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.test.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
@ -813,7 +816,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButto
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/mockRepositoryApiConstructor.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/newRepoApi.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/pluginServiceSetup.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/openWebsiteForPlugin.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginCallbacks.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.js
@ -860,6 +862,38 @@ packages/app-mobile/components/voiceTyping/AudioRecordingBanner.js
packages/app-mobile/components/voiceTyping/RecordingControls.js
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.js
packages/app-mobile/components/voiceTyping/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/applyTemplateToEditor.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.test.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/startAutosaveLoop.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/watchEditorForTemplateChanges.js
packages/app-mobile/contentScripts/imageEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
packages/app-mobile/contentScripts/markdownEditorBundle/types.js
packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/markdownEditorBundle/utils/useCodeMirrorPlugins.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/types.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/addPluginAssets.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/afterFullPageRender.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/makeResourceModel.js
packages/app-mobile/contentScripts/rendererBundle/types.js
packages/app-mobile/contentScripts/rendererBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/rendererBundle/utils/useContentScripts.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.test.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/convertHtmlToMarkdown.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/richTextEditorBundle/types.js
packages/app-mobile/contentScripts/richTextEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/types.js
packages/app-mobile/contentScripts/utils/polyfills.js
packages/app-mobile/contentScripts/utils/readFileToBase64.js
packages/app-mobile/contentScripts/utils/setUpLogger.js
packages/app-mobile/gulpfile.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
@ -869,20 +903,19 @@ packages/app-mobile/services/AlarmServiceDriver.web.js
packages/app-mobile/services/BackButtonService.js
packages/app-mobile/services/commands/stateToWhenClauseContext.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/e2ee/RSA.react-native.web.js
packages/app-mobile/services/e2ee/crypto.js
packages/app-mobile/services/plugins/PlatformImplementation.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/VoiceTyping.js
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
packages/app-mobile/services/voiceTyping/utils/unzip.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/services/voiceTyping/whisper.test.js
packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
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/copyAssets.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
@ -890,7 +923,9 @@ packages/app-mobile/utils/ShareUtils.test.js
packages/app-mobile/utils/ShareUtils.js
packages/app-mobile/utils/TlsUtils.js
packages/app-mobile/utils/appDefaultState.js
packages/app-mobile/utils/appReducer.js
packages/app-mobile/utils/autodetectTheme.js
packages/app-mobile/utils/buildStartupTasks.js
packages/app-mobile/utils/checkPermissions.js
packages/app-mobile/utils/createRootStyle.js
packages/app-mobile/utils/database-driver-react-native.js
@ -910,6 +945,7 @@ packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
@ -918,7 +954,6 @@ 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
@ -928,6 +963,7 @@ packages/app-mobile/utils/pickDocument.js
packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/polyfills/index.web.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareFile.js
packages/app-mobile/utils/shareHandler.js
@ -938,6 +974,7 @@ 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/testing/getWebViewWindowById.js
packages/app-mobile/utils/testing/mockPluginServiceSetup.js
packages/app-mobile/utils/testing/setupGlobalStore.js
packages/app-mobile/utils/testing/testingLibrary.js
packages/app-mobile/utils/types.js
@ -976,24 +1013,48 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
packages/editor/CodeMirror/extensions/highlightActiveLineExtension.js
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.js
packages/editor/CodeMirror/extensions/links/referenceLinksStateField.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.test.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.js
packages/editor/CodeMirror/extensions/links/utils/getUrlAtPosition.js
packages/editor/CodeMirror/extensions/links/utils/openLink.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.test.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.test.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.js
packages/editor/CodeMirror/extensions/markdownMathExtension.test.js
packages/editor/CodeMirror/extensions/markdownMathExtension.js
packages/editor/CodeMirror/extensions/modifierKeyCssExtension.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.test.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.js
packages/editor/CodeMirror/extensions/rendering/addFormattingClasses.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.test.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.js
packages/editor/CodeMirror/extensions/rendering/renderingExtension.js
packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
packages/editor/CodeMirror/extensions/rendering/replaceFormatCharacters.js
packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/index.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testing/createEditorControl.js
packages/editor/CodeMirror/testing/createEditorSettings.js
packages/editor/CodeMirror/testing/createTestEditor.js
packages/editor/CodeMirror/testing/findNodesWithName.js
packages/editor/CodeMirror/testing/forceFullParse.js
@ -1025,13 +1086,62 @@ packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/ProseMirror/commands.test.js
packages/editor/ProseMirror/commands.js
packages/editor/ProseMirror/createEditor.js
packages/editor/ProseMirror/index.js
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
packages/editor/ProseMirror/plugins/detailsPlugin.js
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/postProcessRenderedHtml.js
packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
packages/editor/ProseMirror/plugins/keymapPlugin.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
packages/editor/ProseMirror/plugins/listPlugin.js
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
packages/editor/ProseMirror/plugins/searchPlugin.js
packages/editor/ProseMirror/schema.js
packages/editor/ProseMirror/styles.js
packages/editor/ProseMirror/testing/createTestEditor.js
packages/editor/ProseMirror/types.js
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
packages/editor/ProseMirror/utils/dom/createButton.js
packages/editor/ProseMirror/utils/dom/createTextArea.js
packages/editor/ProseMirror/utils/dom/createTextNode.js
packages/editor/ProseMirror/utils/dom/createUniqueId.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
packages/editor/ProseMirror/utils/forEachHeading.js
packages/editor/ProseMirror/utils/jumpToHash.js
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.js
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
packages/editor/ProseMirror/utils/preprocessEditorInput.js
packages/editor/ProseMirror/utils/sanitizeHtml.js
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
packages/editor/ProseMirror/vendor/changedDescendants.js
packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/polyfills.js
packages/editor/testing/createEditorSettings.js
packages/editor/testing/setUpLogger.js
packages/editor/types.js
packages/editor/utils/getFileFromPasteEvent.js
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/FeedHandler.spec.js
packages/fork-htmlparser2/src/FeedHandler.js
@ -1076,6 +1186,8 @@ packages/lib/JoplinDatabase.js
packages/lib/JoplinError.js
packages/lib/JoplinServerApi.js
packages/lib/ObjectUtils.js
packages/lib/PerformanceLogger.test.js
packages/lib/PerformanceLogger.js
packages/lib/PoorManIntervals.js
packages/lib/RotatingLogs.test.js
packages/lib/RotatingLogs.js
@ -1093,6 +1205,8 @@ packages/lib/array.js
packages/lib/callbackUrlUtils.test.js
packages/lib/callbackUrlUtils.js
packages/lib/clipperUtils.js
packages/lib/commands/convertHtmlToMarkdown.test.js
packages/lib/commands/convertHtmlToMarkdown.js
packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js
@ -1109,6 +1223,8 @@ packages/lib/commands/toggleAllFolders.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
@ -1285,20 +1401,26 @@ packages/lib/services/database/migrations/44.js
packages/lib/services/database/migrations/45.js
packages/lib/services/database/migrations/46.js
packages/lib/services/database/migrations/47.js
packages/lib/services/database/migrations/48.js
packages/lib/services/database/migrations/index.js
packages/lib/services/database/sqlStringToLines.js
packages/lib/services/database/types.js
packages/lib/services/debug/populateDatabase.js
packages/lib/services/e2ee/EncryptionService.test.js
packages/lib/services/e2ee/EncryptionService.js
packages/lib/services/e2ee/RSA.node.js
packages/lib/services/e2ee/crypto.test.js
packages/lib/services/e2ee/crypto.js
packages/lib/services/e2ee/cryptoShared.js
packages/lib/services/e2ee/cryptoTestUtils.js
packages/lib/services/e2ee/ppk.test.js
packages/lib/services/e2ee/ppk.js
packages/lib/services/e2ee/ppkTestUtils.js
packages/lib/services/e2ee/ppk/RSA.node.js
packages/lib/services/e2ee/ppk/ppk.test.js
packages/lib/services/e2ee/ppk/ppk.js
packages/lib/services/e2ee/ppk/ppkTestUtils.js
packages/lib/services/e2ee/ppk/webCrypto/LongDataWrapper.js
packages/lib/services/e2ee/ppk/webCrypto/StringToBufferWrapper.js
packages/lib/services/e2ee/ppk/webCrypto/WebCryptoRsa.js
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.test.js
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.js
packages/lib/services/e2ee/types.js
packages/lib/services/e2ee/utils.test.js
packages/lib/services/e2ee/utils.js
@ -1353,6 +1475,8 @@ packages/lib/services/ocr/OcrDriverBase.js
packages/lib/services/ocr/OcrService.test.js
packages/lib/services/ocr/OcrService.js
packages/lib/services/ocr/drivers/OcrDriverTesseract.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.test.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.js
packages/lib/services/ocr/utils/filterOcrText.test.js
packages/lib/services/ocr/utils/filterOcrText.js
packages/lib/services/ocr/utils/types.js
@ -1522,6 +1646,7 @@ packages/lib/shim-init-node.js
packages/lib/shim.js
packages/lib/string-utils.test.js
packages/lib/string-utils.js
packages/lib/testing/plugins/createTestPlugin.js
packages/lib/testing/share/makeMockShareInvitation.js
packages/lib/testing/share/mockShareService.js
packages/lib/testing/syncTargetUtils.js
@ -1666,12 +1791,14 @@ packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/model/FolderRecord.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
@ -1703,6 +1830,7 @@ packages/tools/release-electron.js
packages/tools/release-ios.js
packages/tools/release-plugin-repo-cli.js
packages/tools/release-server.js
packages/tools/release-transcribe.js
packages/tools/saveClaConsentRecords.js
packages/tools/setupNewRelease.js
packages/tools/spellcheck.js

View File

@ -23,6 +23,7 @@ module.exports = {
'FileSystemCreateWritableOptions': 'readonly',
'FileSystemHandle': 'readonly',
'IDBTransactionMode': 'readonly',
'FlatArray': 'readonly',
'BigInt': 'readonly',
'globalThis': 'readonly',

View File

@ -7,9 +7,13 @@
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
TRANSCRIBE_TAG_PREFIX=transcribe
TRANSCRIBE_REPOSITORY=joplin/transcribe
IS_PULL_REQUEST=0
IS_DESKTOP_RELEASE=0
IS_SERVER_RELEASE=0
IS_TRANSCRIBE_RELEASE=0
IS_LINUX=0
IS_MACOS=0
@ -23,6 +27,10 @@ if [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
IS_SERVER_RELEASE=1
fi
if [[ $GIT_TAG_NAME = $TRANSCRIBE_TAG_PREFIX-* ]]; then
IS_TRANSCRIBE_RELEASE=1
fi
if [[ $GIT_TAG_NAME = v* ]]; then
IS_DESKTOP_RELEASE=1
fi
@ -41,15 +49,17 @@ DOCKER_IMAGE_PLATFORM="linux/amd64"
# a release
RUN_TESTS=0
if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ]; then
if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ] && [ "$IS_TRANSCRIBE_RELEASE" = 0 ]; then
RUN_TESTS=1
fi
if [ "$RUNNER_ARCH" == "ARM64" ] && [ "$IS_SERVER_RELEASE" == "0" ]; then
# We exit now because nothing works properly with the ARM64 architecture.
# We only proceed if building the server image.
echo "Running on ARM64 and not trying to build server image - early exit"
exit 0
if [ "$RUNNER_ARCH" == "ARM64" ]; then
if [ "$IS_SERVER_RELEASE" == "0" ] && [ "$IS_TRANSCRIBE_RELEASE" == "0" ]; then
# We exit now because nothing works properly with the ARM64 architecture.
# We only proceed if building the server image.
echo "Running on ARM64 and not trying to build server image - early exit"
exit 0
fi
fi
if [ "$RUNNER_ARCH" == "ARM64" ]; then
@ -80,12 +90,14 @@ echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "BUILD_SEQUENCIAL=$BUILD_SEQUENCIAL"
echo "SERVER_REPOSITORY=$SERVER_REPOSITORY"
echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
echo "TRANSCRIBE_TAG_PREFIX=$TRANSCRIBE_TAG_PREFIX"
echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
echo "IS_DESKTOP_RELEASE=$IS_DESKTOP_RELEASE"
echo "IS_SERVER_RELEASE=$IS_SERVER_RELEASE"
echo "IS_TRANSCRIBE_RELEASE=$IS_TRANSCRIBE_RELEASE"
echo "RUN_TESTS=$RUN_TESTS"
echo "IS_LINUX=$IS_LINUX"
echo "IS_MACOS=$IS_MACOS"
@ -301,9 +313,13 @@ if [ "$IS_DESKTOP_RELEASE" == "1" ]; then
USE_HARD_LINKS=false yarn dist
fi
elif [[ $IS_LINUX = 1 ]] && [ "$IS_SERVER_RELEASE" == "1" ]; then
echo "Step: Building Docker Image..."
echo "Step: Building Joplin Server Docker Image..."
cd "$ROOT_DIR"
yarn buildServerDocker --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
yarn buildServerDocker --docker-file Dockerfile.server --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
elif [[ $IS_LINUX = 1 ]] && [ "$IS_TRANSCRIBE_RELEASE" == "1" ]; then
echo "Step: Building Joplin Transcribe Docker Image..."
cd "$ROOT_DIR"
yarn buildServerDocker --docker-file Dockerfile.transcribe --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $TRANSCRIBE_REPOSITORY
else
echo "Step: Building but *not* publishing desktop application..."

View File

@ -40,4 +40,29 @@ jobs:
cd packages/app-mobile/android
sed -i -- 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/' app/build.gradle
./gradlew assembleRelease
- name: Verify alignment
run: |
cd packages/app-mobile/android/app
APK_FILE="./build/outputs/apk/release/app-release.apk"
if test ! -f "$APK_FILE" ; then
echo "APK file not found."
exit 1
else
echo "APK file found at: $APK_FILE"
fi
BUILD_TOOLS_PATH="$ANDROID_HOME/build-tools/"
if test ! -d "$BUILD_TOOLS_PATH" ; then
echo "Build tools not found at $BUILD_TOOLS_PATH ($ANDROID_HOME, $BUILD_TOOLS_VERSION)"
exit 1
fi
# The build-tools/ directory contains different subdirectories
# for each build tools version. As a result, there may be multiple
# zipalign tools. Select one of them:
ZIPALIGN_PATH="$(find $BUILD_TOOLS_PATH -name "zipalign" -print | head -n1)"
if test ! -x "$ZIPALIGN_PATH" ; then
echo "zipalign not found (searching in $BUILD_TOOLS_PATH, candidate: $ZIPALIGN_PATH)"
exit 1
fi
"$ZIPALIGN_PATH" -c -P 16 -v 4 "$APK_FILE"

View File

@ -17,7 +17,6 @@ jobs:
uses: ./.github/workflows/shared/setup-build-environment
- name: Install Docker Engine
# if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
if: runner.os == 'Linux'
run: |
sudo apt-get install -y apt-transport-https
@ -36,7 +35,7 @@ jobs:
# a pull request it will fail because the PR doesn't have access to
# secrets
- uses: docker/login-action@v3
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
if: runner.os == 'Linux' && (startsWith(github.ref, 'refs/tags/server-v') || startsWith(github.ref, 'refs/tags/transcribe-v'))
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@ -141,7 +140,7 @@ jobs:
echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM"
yarn install
yarn buildServerDocker --platform $DOCKER_IMAGE_PLATFORM --tag-name server-v0.0.0 --repository joplin/server
yarn buildServerDocker --docker-file Dockerfile.server --platform $DOCKER_IMAGE_PLATFORM --tag-name server-v0.0.0 --repository joplin/server
# Basic test to ensure that the created build is valid. It should exit with
# code 0 if it works.

186
.gitignore vendored
View File

@ -69,8 +69,10 @@ docs/**/*.mustache
packages/app-cli/app/LinkSelector.js
packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/cli-integration-tests.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
packages/app-cli/app/command-cat.js
packages/app-cli/app/command-config.js
packages/app-cli/app/command-cp.js
@ -89,6 +91,8 @@ packages/app-cli/app/command-ls.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mv.js
packages/app-cli/app/command-publish.test.js
packages/app-cli/app/command-publish.js
packages/app-cli/app/command-ren.js
packages/app-cli/app/command-restore.js
packages/app-cli/app/command-rmbook.test.js
@ -101,6 +105,8 @@ packages/app-cli/app/command-share.test.js
packages/app-cli/app/command-share.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-unpublish.test.js
packages/app-cli/app/command-unpublish.js
packages/app-cli/app/command-use.js
packages/app-cli/app/command-version.js
packages/app-cli/app/gui/FolderListWidget.js
@ -108,6 +114,7 @@ packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/iterateStdin.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
@ -130,6 +137,8 @@ packages/app-desktop/app.reducer.js
packages/app-desktop/app.js
packages/app-desktop/bridge.js
packages/app-desktop/checkForUpdates.js
packages/app-desktop/commands/convertNoteToMarkdown.test.js
packages/app-desktop/commands/convertNoteToMarkdown.js
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyToClipboard.js
packages/app-desktop/commands/editProfileConfig.js
@ -170,6 +179,7 @@ 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
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
packages/app-desktop/gui/ConversionNotification/ConversionNotification.js
packages/app-desktop/gui/Dialog.js
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
@ -271,6 +281,7 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js
packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/getResourceBaseUrl.js
packages/app-desktop/gui/NoteEditor/utils/getWindowCommandPriority.js
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/markupRenderOptions.js
@ -638,6 +649,9 @@ 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/ExtendedWebView/utils/useCss.js
packages/app-mobile/components/FeedbackBanner.test.js
packages/app-mobile/components/FeedbackBanner.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
@ -646,48 +660,34 @@ packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.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
packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/types.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/addPluginAssets.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/makeResourceModel.js
packages/app-mobile/components/NoteBodyViewer/hooks/useContentScripts.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.js
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
packages/app-mobile/components/NoteBodyViewer/types.js
packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js
packages/app-mobile/components/NoteEditor/EditLinkDialog.js
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/RichTextEditor.test.js
packages/app-mobile/components/NoteEditor/RichTextEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
packages/app-mobile/components/NoteEditor/WarningBanner.js
packages/app-mobile/components/NoteEditor/commandDeclarations.js
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteItem.js
packages/app-mobile/components/NoteList.js
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/SafeAreaView.js
packages/app-mobile/components/ScreenHeader/Menu.js
packages/app-mobile/components/ScreenHeader/WarningBanner.test.js
packages/app-mobile/components/ScreenHeader/WarningBanner.js
@ -724,6 +724,7 @@ packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginNotification.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.test.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
@ -788,7 +789,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButto
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/mockRepositoryApiConstructor.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/newRepoApi.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/pluginServiceSetup.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/openWebsiteForPlugin.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginCallbacks.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.js
@ -835,6 +835,38 @@ packages/app-mobile/components/voiceTyping/AudioRecordingBanner.js
packages/app-mobile/components/voiceTyping/RecordingControls.js
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.js
packages/app-mobile/components/voiceTyping/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/applyTemplateToEditor.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.test.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/startAutosaveLoop.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/watchEditorForTemplateChanges.js
packages/app-mobile/contentScripts/imageEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
packages/app-mobile/contentScripts/markdownEditorBundle/types.js
packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/markdownEditorBundle/utils/useCodeMirrorPlugins.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/types.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/addPluginAssets.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/afterFullPageRender.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/makeResourceModel.js
packages/app-mobile/contentScripts/rendererBundle/types.js
packages/app-mobile/contentScripts/rendererBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/rendererBundle/utils/useContentScripts.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.test.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/convertHtmlToMarkdown.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/richTextEditorBundle/types.js
packages/app-mobile/contentScripts/richTextEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/types.js
packages/app-mobile/contentScripts/utils/polyfills.js
packages/app-mobile/contentScripts/utils/readFileToBase64.js
packages/app-mobile/contentScripts/utils/setUpLogger.js
packages/app-mobile/gulpfile.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
@ -844,20 +876,19 @@ packages/app-mobile/services/AlarmServiceDriver.web.js
packages/app-mobile/services/BackButtonService.js
packages/app-mobile/services/commands/stateToWhenClauseContext.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/e2ee/RSA.react-native.web.js
packages/app-mobile/services/e2ee/crypto.js
packages/app-mobile/services/plugins/PlatformImplementation.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/VoiceTyping.js
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
packages/app-mobile/services/voiceTyping/utils/unzip.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/services/voiceTyping/whisper.test.js
packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
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/copyAssets.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
@ -865,7 +896,9 @@ packages/app-mobile/utils/ShareUtils.test.js
packages/app-mobile/utils/ShareUtils.js
packages/app-mobile/utils/TlsUtils.js
packages/app-mobile/utils/appDefaultState.js
packages/app-mobile/utils/appReducer.js
packages/app-mobile/utils/autodetectTheme.js
packages/app-mobile/utils/buildStartupTasks.js
packages/app-mobile/utils/checkPermissions.js
packages/app-mobile/utils/createRootStyle.js
packages/app-mobile/utils/database-driver-react-native.js
@ -885,6 +918,7 @@ packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
@ -893,7 +927,6 @@ 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
@ -903,6 +936,7 @@ packages/app-mobile/utils/pickDocument.js
packages/app-mobile/utils/polyfills/bufferPolyfill.js
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
packages/app-mobile/utils/polyfills/index.js
packages/app-mobile/utils/polyfills/index.web.js
packages/app-mobile/utils/setupNotifications.js
packages/app-mobile/utils/shareFile.js
packages/app-mobile/utils/shareHandler.js
@ -913,6 +947,7 @@ 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/testing/getWebViewWindowById.js
packages/app-mobile/utils/testing/mockPluginServiceSetup.js
packages/app-mobile/utils/testing/setupGlobalStore.js
packages/app-mobile/utils/testing/testingLibrary.js
packages/app-mobile/utils/types.js
@ -951,24 +986,48 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
packages/editor/CodeMirror/extensions/highlightActiveLineExtension.js
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.js
packages/editor/CodeMirror/extensions/links/referenceLinksStateField.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.test.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.js
packages/editor/CodeMirror/extensions/links/utils/getUrlAtPosition.js
packages/editor/CodeMirror/extensions/links/utils/openLink.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.test.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.test.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.js
packages/editor/CodeMirror/extensions/markdownMathExtension.test.js
packages/editor/CodeMirror/extensions/markdownMathExtension.js
packages/editor/CodeMirror/extensions/modifierKeyCssExtension.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.test.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.js
packages/editor/CodeMirror/extensions/rendering/addFormattingClasses.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.test.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.js
packages/editor/CodeMirror/extensions/rendering/renderingExtension.js
packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
packages/editor/CodeMirror/extensions/rendering/replaceFormatCharacters.js
packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/index.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testing/createEditorControl.js
packages/editor/CodeMirror/testing/createEditorSettings.js
packages/editor/CodeMirror/testing/createTestEditor.js
packages/editor/CodeMirror/testing/findNodesWithName.js
packages/editor/CodeMirror/testing/forceFullParse.js
@ -1000,13 +1059,62 @@ packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/ProseMirror/commands.test.js
packages/editor/ProseMirror/commands.js
packages/editor/ProseMirror/createEditor.js
packages/editor/ProseMirror/index.js
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
packages/editor/ProseMirror/plugins/detailsPlugin.js
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/postProcessRenderedHtml.js
packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
packages/editor/ProseMirror/plugins/keymapPlugin.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
packages/editor/ProseMirror/plugins/listPlugin.js
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
packages/editor/ProseMirror/plugins/searchPlugin.js
packages/editor/ProseMirror/schema.js
packages/editor/ProseMirror/styles.js
packages/editor/ProseMirror/testing/createTestEditor.js
packages/editor/ProseMirror/types.js
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
packages/editor/ProseMirror/utils/dom/createButton.js
packages/editor/ProseMirror/utils/dom/createTextArea.js
packages/editor/ProseMirror/utils/dom/createTextNode.js
packages/editor/ProseMirror/utils/dom/createUniqueId.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
packages/editor/ProseMirror/utils/forEachHeading.js
packages/editor/ProseMirror/utils/jumpToHash.js
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.js
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
packages/editor/ProseMirror/utils/preprocessEditorInput.js
packages/editor/ProseMirror/utils/sanitizeHtml.js
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
packages/editor/ProseMirror/vendor/changedDescendants.js
packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/polyfills.js
packages/editor/testing/createEditorSettings.js
packages/editor/testing/setUpLogger.js
packages/editor/types.js
packages/editor/utils/getFileFromPasteEvent.js
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/FeedHandler.spec.js
packages/fork-htmlparser2/src/FeedHandler.js
@ -1051,6 +1159,8 @@ packages/lib/JoplinDatabase.js
packages/lib/JoplinError.js
packages/lib/JoplinServerApi.js
packages/lib/ObjectUtils.js
packages/lib/PerformanceLogger.test.js
packages/lib/PerformanceLogger.js
packages/lib/PoorManIntervals.js
packages/lib/RotatingLogs.test.js
packages/lib/RotatingLogs.js
@ -1068,6 +1178,8 @@ packages/lib/array.js
packages/lib/callbackUrlUtils.test.js
packages/lib/callbackUrlUtils.js
packages/lib/clipperUtils.js
packages/lib/commands/convertHtmlToMarkdown.test.js
packages/lib/commands/convertHtmlToMarkdown.js
packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js
@ -1084,6 +1196,8 @@ packages/lib/commands/toggleAllFolders.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
@ -1260,20 +1374,26 @@ packages/lib/services/database/migrations/44.js
packages/lib/services/database/migrations/45.js
packages/lib/services/database/migrations/46.js
packages/lib/services/database/migrations/47.js
packages/lib/services/database/migrations/48.js
packages/lib/services/database/migrations/index.js
packages/lib/services/database/sqlStringToLines.js
packages/lib/services/database/types.js
packages/lib/services/debug/populateDatabase.js
packages/lib/services/e2ee/EncryptionService.test.js
packages/lib/services/e2ee/EncryptionService.js
packages/lib/services/e2ee/RSA.node.js
packages/lib/services/e2ee/crypto.test.js
packages/lib/services/e2ee/crypto.js
packages/lib/services/e2ee/cryptoShared.js
packages/lib/services/e2ee/cryptoTestUtils.js
packages/lib/services/e2ee/ppk.test.js
packages/lib/services/e2ee/ppk.js
packages/lib/services/e2ee/ppkTestUtils.js
packages/lib/services/e2ee/ppk/RSA.node.js
packages/lib/services/e2ee/ppk/ppk.test.js
packages/lib/services/e2ee/ppk/ppk.js
packages/lib/services/e2ee/ppk/ppkTestUtils.js
packages/lib/services/e2ee/ppk/webCrypto/LongDataWrapper.js
packages/lib/services/e2ee/ppk/webCrypto/StringToBufferWrapper.js
packages/lib/services/e2ee/ppk/webCrypto/WebCryptoRsa.js
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.test.js
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.js
packages/lib/services/e2ee/types.js
packages/lib/services/e2ee/utils.test.js
packages/lib/services/e2ee/utils.js
@ -1328,6 +1448,8 @@ packages/lib/services/ocr/OcrDriverBase.js
packages/lib/services/ocr/OcrService.test.js
packages/lib/services/ocr/OcrService.js
packages/lib/services/ocr/drivers/OcrDriverTesseract.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.test.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.js
packages/lib/services/ocr/utils/filterOcrText.test.js
packages/lib/services/ocr/utils/filterOcrText.js
packages/lib/services/ocr/utils/types.js
@ -1497,6 +1619,7 @@ packages/lib/shim-init-node.js
packages/lib/shim.js
packages/lib/string-utils.test.js
packages/lib/string-utils.js
packages/lib/testing/plugins/createTestPlugin.js
packages/lib/testing/share/makeMockShareInvitation.js
packages/lib/testing/share/mockShareService.js
packages/lib/testing/syncTargetUtils.js
@ -1641,12 +1764,14 @@ packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/model/FolderRecord.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
@ -1678,6 +1803,7 @@ packages/tools/release-electron.js
packages/tools/release-ios.js
packages/tools/release-plugin-repo-cli.js
packages/tools/release-server.js
packages/tools/release-transcribe.js
packages/tools/saveClaConsentRecords.js
packages/tools/setupNewRelease.js
packages/tools/spellcheck.js

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,215 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Milo Ivir <mail@mivirtype.de>\n"
"Language-Team: \n"
"Language: hr_HR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.6\n"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:13
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:9
msgid "/month"
msgstr "/mjesec"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:19
msgid "/year"
msgstr "/godina"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:8
msgid ""
"<a href=\"https://joplincloud.com\">Joplin Cloud</a> allows you to "
"synchronise your notes across devices. It also lets you publish notes, and "
"collaborate on notebooks with your friends, family or colleagues."
msgstr ""
"<a href=\"https://joplincloud.com\">Joplin Cloud</a> omogućuje "
"sinkronizaciju bilješki na različitim uređajima. Omogućuje i objavljivanje "
"bilješki i suradnju na bilježnicama s prijateljima, obitelji ili kolegama."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:205
msgid "<span class=\"frame-bg frame-bg-yellow-lg\">Customise</span> it"
msgstr "<span class=\"frame-bg frame-bg-yellow-lg\">Prilagodi</span> uslugu"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:104
msgid "<span class=\"frame-bg frame-bg-yellow\">Multimedia</span> notes"
msgstr "<span class=\"frame-bg frame-bg-yellow\">Multimedijske</span> bilješke"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:256
msgid "100% <span class=\"frame-bg frame-bg-yellow-lg\">your data</span>"
msgstr "100 % <span class=\"frame-bg frame-bg-yellow-lg\">tvoji podaci</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:298
msgid "A <span class=\"frame-bg frame-bg-yellow-lg\">French</span> Alternative"
msgstr ""
"<span class=\"frame-bg frame-bg-yellow-lg\">Francuska</span> alternativa"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:236
msgid ""
"Access your notes from your computer, phone or tablet by synchronising with "
"various services, including Joplin Cloud, Dropbox and OneDrive. The app is "
"available on Windows, macOS, Linux, Android and iOS. A terminal app is also "
"available!"
msgstr ""
"Pristupi svojim bilješkama s računala, mobitela ili tableta sinkronizacijom "
"s raznim uslugama, uključujući Joplin Cloud, Dropbox i OneDrive. Program je "
"dostupan za Windows, macOS, Linux, Android i iOS sustave. Dostupan je i "
"program za terminal!"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:49
msgid ""
"Already have a Joplin Cloud account? <a href=\"https://"
"joplincloud.com\">Login now</a>"
msgstr ""
"Već imaš Joplin Cloud račun? <a href=\"https://joplincloud.com\">Prijavi se "
"sada</a>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:208
msgid ""
"Customise the app with plugins, custom themes and multiple text editors "
"(Rich Text or Markdown). Or create your own scripts and plugins using the "
"Extension API."
msgstr ""
"Prilagodi program pomoću dodataka, prilagođenih tema i uređivača teksta "
"(formatirani tekst ili Markdown). Ili izradi vlastita skripta i dodatke "
"pomoću API-ja za proširenja."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:242
msgid "Download it now"
msgstr "Preuzmi sada"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:112
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:63
msgid "Download the app"
msgstr "Preuzmi program"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:213
msgid "Find out more"
msgstr "Saznaj više"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:54
msgid "Free your <span class=\"frame-bg frame-bg-blue\">notes</span>"
msgstr "Oslobodi svoje <span class=\"frame-bg frame-bg-blue\">bilješke</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:175
msgid "Get the clipper"
msgstr "Nabavi Clipper"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:107
msgid ""
"Images, videos, PDFs and audio files are supported. Create math expressions "
"and diagrams directly from the app. Take photos with the mobile app and save "
"them to a note."
msgstr ""
"Podržane su slike, videozapisi, PDF-ovi i audio datoteke. Stvori matematičke "
"izraze i dijagrame izravno iz programa. Snimaj fotografije s programom za "
"mobitel i spremi ih u bilješku."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:327
msgid "In the <span class=\"frame-bg frame-bg-yellow\">Press</span>"
msgstr "<span class=\"frame-bg frame-bg-yellow\">Recenzije</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:5
msgid "Joplin Cloud <span class=\"frame-bg frame-bg-yellow\">plans</span>"
msgstr "Joplin Cloud <span class=\"frame-bg frame-bg-yellow\">tarife</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:301
msgid ""
"Joplin Cloud is based in France. This means your data is protected by strict "
"European Union privacy laws. In addition, Joplin Cloud implements strong end-"
"to-end encryption so that not even us can have access to your data."
msgstr ""
"Joplin Cloud ima sjedište u Francuskoj. To znači da su tvoji podaci "
"zaštićeni strogim zakonima o privatnosti Europske unije. Osim toga, Joplin "
"Cloud implementira snažno sveobuhvatno šifriranje (end-to-end encryption) "
"tako da čak ni mi ne možemo pristupiti tvojim podacima."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:57
msgid ""
"Joplin is an open source note-taking app. Capture your thoughts and securely "
"access them from any device."
msgstr ""
"Joplin je program za bilješke otvorenog koda. Zabilježi svoje misli i "
"sigurno im pristupi s bilo kojeg uređaja."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:262
msgid "More about E2EE"
msgstr "Više o E2EE"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:391
msgid "Our <span class=\"frame-bg frame-bg-blue-lg\">sponsors</span>"
msgstr "Naši <span class=\"frame-bg frame-bg-blue-lg\">sponzori</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:23
msgid "Pay Monthly"
msgstr "Plaćaj mjesečno"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:30
msgid "Pay Yearly"
msgstr "Plaćaj godišnje"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:167
msgid ""
"Save <span class=\"frame-bg frame-bg-blue\">web pages</span> <br>as notes"
msgstr ""
"Spremaj <span class=\"frame-bg frame-bg-blue\">web stranice</span> <br>kao "
"bilješke"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:65
msgid "Sign up with Joplin Cloud"
msgstr "Registriraj se na Joplin Cloud"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:394
msgid "Thank you for your support!"
msgstr "Hvala ti na podršci!"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:257
msgid ""
"The app is open source and your notes are saved to an open format, so you'll "
"always have access to them. Uses End-To-End Encryption (E2EE) to secure your "
"notes and ensure no-one but yourself can access them."
msgstr ""
"Program je otvorenog koda i tvoje se bilješke spremaju u otvorenom formatu, "
"tako da ćeš im uvijek moći pristupiti. Program koristi sveobuhvatno "
"šifriranje engl. End-To-End Encryption (E2EE) kako bi zaštitila tvoje "
"bilješke i osigurala da im nitko osim tebe ne može pristupiti."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:144
msgid "Try it now"
msgstr "Isprobaj sada"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:170
msgid ""
"Use the web clipper extension, available on Chrome and Firefox, to save web "
"pages or take screenshots as notes."
msgstr ""
"Koristi proširenje Web Clipper, dostupno za Chrome i Firefox, za spremanje "
"web stranica ili snimanje ekrana kao bilješku."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:138
msgid ""
"With Joplin Cloud, share your notes with your friends, family or colleagues "
"and collaborate on them."
msgstr ""
"Joplin Cloud ti omogućuje da dijeliš bilješke s prijateljima, obitelji ili "
"kolegama te da na njima surađujete."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:137
msgid "Work <span class=\"frame-bg frame-bg-yellow\">together</span>"
msgstr "<span class=\"frame-bg frame-bg-yellow\">Surađuj</span> s drugima"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:141
msgid ""
"You can also publish a note to the internet and share the URL with others."
msgstr "Bilješke možeš objaviti i na internetu te dijeliti URL s drugima."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:233
msgid ""
"Your notes, <span class=\"frame-bg frame-bg-blue-lg\">everywhere</span> you "
"are"
msgstr ""
"Tvoje bilješke, <span class=\"frame-bg frame-bg-blue-lg\">gdje god</span> se "
"nalaziš"

View File

@ -219,10 +219,7 @@
$('.feature-description-' + featureId).toggle(200);
});
});
</script>
<script>
const setHostingType = (type) => {
const other = type === 'managed' ? 'self' : 'managed';
$('.toggle-button-' + type).addClass('active');
@ -244,6 +241,7 @@
setHostingType('self');
});
setHostingType('managed');
const initialHostingType = urlQuery.get('hosting') ? urlQuery.get('hosting') : 'managed';
setHostingType(initialHostingType);
</script>
</div>

View File

@ -63,11 +63,22 @@ FROM node:18-slim
ARG user=joplin
RUN useradd --create-home --shell /bin/bash $user
# Install PM2 and set home directory. Setting the PM2 data dir so modules/config persist regardless
# of user home.
RUN npm i -g pm2@5.4.3 && mkdir -p /opt/pm2 && chown -R $user:$user /opt/pm2
ENV PM2_HOME=/opt/pm2
USER $user
COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages
COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini
# Install pm2-logrotate and default settings as the runtime user
RUN pm2 install pm2-logrotate \
&& pm2 set pm2-logrotate:max_size 100MB \
&& pm2 set pm2-logrotate:retain 5 \
&& pm2 set pm2-logrotate:compress true
ENV NODE_ENV=production
ENV RUNNING_IN_DOCKER=1
EXPOSE ${APP_PORT}

View File

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

View File

@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://realgambling.ca/"><img title="RealGambling.ca" width="256" src="https://joplinapp.org/images/sponsors/RealGambling.png" alt="RealGambling.ca"/></a> <a href="https://essaypro.com/"><img title="write an essay online with EssayPro" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="write an essay online with EssayPro"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essaywriter.pro"><img title="write my essay services by EssayWriter" width="256" src="https://joplinapp.org/images/sponsors/EssayWriterPro.png" alt="write my essay services by EssayWriter"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://homeworkguy.org/someone-to-take-my-online-class"><img title="someone to take my online class" width="256" src="https://joplinapp.org/images/sponsors/HomeworkGuy.png" alt="someone to take my online class"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://damangameplay.in"><img title="Daman Game" width="256" src="https://joplinapp.org/images/sponsors/DamanGame.png" alt="Daman Game"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a> <a href="https://uk.notgamstop.com/bonuses/free-spins-no-deposit-no-gamstop/"><img title="free spins no deposit at NotGamstop" width="256" src="https://joplinapp.org/images/sponsors/NotGamStop.jpg" alt="free spins no deposit at NotGamstop"/></a>
<!-- SPONSORS-ORG -->
* * *
@ -40,9 +40,8 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
| | | | |
| :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars2.githubusercontent.com/u/97193607?s=96&v=4"/></br>[Akhil-CM](https://github.com/Akhil-CM) | <img width="50" src="https://avatars2.githubusercontent.com/u/552452?s=96&v=4"/></br>[andypiper](https://github.com/andypiper) | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1177810?s=96&v=4"/></br>[felixstorm](https://github.com/felixstorm) | <img width="50" src="https://avatars2.githubusercontent.com/u/8030470?s=96&v=4"/></br>[Galliver7](https://github.com/Galliver7) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/668977?s=96&v=4"/></br>[ugoertz](https://github.com/ugoertz) | | | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1177810?s=96&v=4"/></br>[felixstorm](https://github.com/felixstorm) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/668977?s=96&v=4"/></br>[ugoertz](https://github.com/ugoertz) |
| | | | |
<!-- SPONSORS-GITHUB -->
# Community

View File

@ -5,24 +5,24 @@
"version": "latest",
"platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"yarn": "latest",
"yarn": "1.22.19",
"vips.dev": {
"platforms": ["aarch64-darwin"],
},
"nodejs": "23.10.0",
"nodejs": "23.11.0",
"pkg-config": "latest",
"darwin.apple_sdk.frameworks.Foundation": { // satisfies missing CoreText/CoreText.h
// https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/darwin/apple-sdk/default.nix
"version": "",
"platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"python": "3.13.2",
"python": "3.13.3",
"bat": "latest",
"electron": {
"version": "latest",
"excluded_platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"git": "2.47.2",
"git": "2.48.1",
},
"shell": {
"init_hook": [

View File

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

View File

@ -17,11 +17,21 @@
version: '3'
networks:
app-network:
transcribe-network:
shared-network:
services:
db:
image: postgres:16
profiles:
- full
- server
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- app-network
ports:
- "5432:5432"
restart: unless-stopped
@ -31,10 +41,17 @@ services:
- POSTGRES_DB=${POSTGRES_DATABASE}
app:
image: joplin/server:latest
profiles:
- full
- server
depends_on:
- db
- transcribe
ports:
- "22300:22300"
networks:
- app-network
- shared-network
restart: unless-stopped
environment:
- APP_PORT=22300
@ -45,3 +62,48 @@ services:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PORT=${POSTGRES_PORT}
- POSTGRES_HOST=db
- TRANSCRIBE_API_KEY=${TRANSCRIBE_API_KEY}
- TRANSCRIBE_BASE_URL=http://transcribe:4567
- TRANSCRIBE_ENABLED=${TRANSCRIBE_ENABLED}
transcribe-db:
image: postgres:16
profiles:
- full
volumes:
- ./data/transcribe-postgres:/var/lib/postgresql/data
networks:
- transcribe-network
ports:
- "${QUEUE_DATABASE_PORT}:5432"
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=${QUEUE_DATABASE_PASSWORD}
- POSTGRES_USER=${QUEUE_DATABASE_USER}
- POSTGRES_DB=${QUEUE_DATABASE_NAME}
command: -p ${QUEUE_DATABASE_PORT}
transcribe:
image: joplin/transcribe:latest
profiles:
- full
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${HTR_CLI_IMAGES_FOLDER}:/app/packages/transcribe/images
depends_on:
- transcribe-db
ports:
- "4567:4567"
networks:
- transcribe-network
- shared-network
restart: unless-stopped
environment:
- APP_PORT=4567
- DB_CLIENT=pg
- QUEUE_DATABASE_NAME=${QUEUE_DATABASE_NAME}
- QUEUE_DATABASE_USER=${QUEUE_DATABASE_USER}
- QUEUE_DATABASE_PASSWORD=${QUEUE_DATABASE_PASSWORD}
- QUEUE_DATABASE_PORT=${QUEUE_DATABASE_PORT}
- QUEUE_DATABASE_HOST=transcribe-db
- API_KEY=${TRANSCRIBE_API_KEY}
- HTR_CLI_IMAGES_FOLDER=${HTR_CLI_IMAGES_FOLDER}

View File

@ -0,0 +1,13 @@
<strong>Joplin</strong> je besplatan program otvorenog koda za bilješke i popis zadataka koji može obraditi veliki broj bilješki organizirane u bilježnice. Bilješke se mogu pretraživati, kopirati, označavati i mijenjati izravno iz programa ili iz vlastitog uređivača teksta.
Bilješke su u <a href="https://joplinapp.org/help/apps/markdown">Markdown formatu</a>.
Iz Evernotea izvezene bilješke <a href="https://joplinapp.org/help/apps/import_export">mogu se uvesti</a> u Joplin, uključujući formatirani sadržaj (koji se pretvara u Markdown), resurse (slike, privitke itd.) i potpune metapodatke (geografski podaci mjesta, vrijeme aktualiziranja, vrijeme stvaranja itd.). Mogu se uvesti i obične Markdown datoteke.
Joplin radi ponajprije s lokalnim podacima (offline first), što znači da uvijek imaš sve svoje podatke na mobitelu ili računalu. To osigurava da su tvoje bilješke uvijek dostupne, bez obzira je li imaš internetsku vezu ili ne.</p>
Bilješke se mogu sigurno <a href="https://joplinapp.org/help/apps/sync">sinkronizirati</a> pomoću <a href="https://joplinapp.org/help/apps/sync/e2ee">sveobuhvatnog šifriranja</a> s raznim uslugama u oblaku, uključujući Nextcloud, Dropbox, OneDrive i <a href="https://joplinapp.org/plans/">Joplin Cloud</a>.
Pretraživanje cijelog teksta dostupno je na svim platformama za brzo pronalaženje potrebnih informacija. Program se može prilagoditi pomoću dodataka i tema, a možeš stvoriti i vlastite.
Program je dostupan za Windows, Linux, macOS, Android i iOS sustave. <a href="https://joplinapp.org/help/apps/clipper">Web Clipper</a>, za spremanje web stranica i snimaka ekrana iz tvog preglednika, je također dostupan za <a href="https://addons.mozilla.org/firefox/addon/joplin-web-clipper/">Firefox</a> i <a href="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek">Chrome</a>.

View File

@ -0,0 +1 @@
Program za bilješke i popis zadataka sa sinkronizacijom između Linuxa, macOS-a, Windowsa i mobitela

View File

@ -51,6 +51,8 @@
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
"releasePluginRepoCli": "node packages/tools/release-plugin-repo-cli.js",
"releaseServer": "node packages/tools/release-server.js",
"releaseTranscribe": "node packages/tools/release-transcribe.js",
"saveClaConsentRecords": "node packages/tools/saveClaConsentRecords.js",
"setupNewRelease": "node ./packages/tools/setupNewRelease",
"spellcheck": "node packages/tools/spellcheck.js",
"tagServerLatest": "node packages/tools/tagServerLatest.js",
@ -72,22 +74,22 @@
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"cspell": "5.21.2",
"eslint": "8.57.0",
"eslint": "8.57.1",
"eslint-interactive": "10.8.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "27.9.0",
"eslint-plugin-promise": "6.2.0",
"eslint-plugin-react": "7.34.3",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-react": "7.37.4",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "11.0.2",
"glob": "11.0.3",
"gulp": "4.0.2",
"husky": "9.1.7",
"lerna": "3.22.1",
"lint-staged": "15.5.1",
"lint-staged": "15.5.2",
"madge": "8.0.0",
"npm-package-json-lint": "8.0.0",
"typescript": "5.4.5"
"typescript": "5.8.2"
},
"dependencies": {
"@types/fs-extra": "11.0.4",
@ -99,8 +101,7 @@
"packageManager": "yarn@4.9.2",
"resolutions": {
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
"eslint": "patch:eslint@8.57.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"eslint": "patch:eslint@8.57.1#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",

View File

@ -380,6 +380,13 @@ class AppGui {
this.widget('noteList').toggleShowIds();
}
toggleFolderCollapse() {
const folderList = this.widget('folderList');
if (folderList && folderList.toggleFolderCollapse) {
folderList.toggleFolderCollapse();
}
}
widget(name) {
if (name === 'root') return this.rootWidget_;
return this.rootWidget_.childByName(name);
@ -506,6 +513,8 @@ class AppGui {
this.toggleNoteMetadata();
} else if (cmd === 'toggle_ids') {
this.toggleFolderIds();
} else if (cmd === 'toggle_folder_collapse') {
this.toggleFolderCollapse();
} else if (cmd === 'enter_command_line_mode') {
const cmd = await this.widget('statusBar').prompt();
if (!cmd) return;

View File

@ -9,7 +9,6 @@ import Tag from '@joplin/lib/models/Tag';
import Setting, { Env } from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry.js';
import { dirname, fileExtension } from '@joplin/lib/path-utils';
import { splitCommandString } from '@joplin/utils';
import { _ } from '@joplin/lib/locale';
import { pathExists, readFile, readdirSync } from 'fs-extra';
import RevisionService from '@joplin/lib/services/RevisionService';
@ -19,7 +18,6 @@ import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
import initializeCommandService from './utils/initializeCommandService';
const { cliUtils } = require('./cli-utils.js');
const Cache = require('@joplin/lib/Cache');
const { splitCommandBatch } = require('@joplin/lib/string-utils');
class Application extends BaseApplication {
@ -222,6 +220,7 @@ class Application extends BaseApplication {
return { ...this.commandMetadata_ };
}
public hasGui() {
return this.gui() && !this.gui().isDummy();
}
@ -332,6 +331,7 @@ class Application extends BaseApplication {
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
{ keys: ['z'], type: 'function', command: 'toggle_folder_collapse' },
];
// Filter the keymap item by command so that items in keymap.json can override
@ -381,22 +381,6 @@ class Application extends BaseApplication {
return output;
}
public async commandList(argv: string[]) {
if (argv.length && argv[0] === 'batch') {
const commands = [];
const commandLines = splitCommandBatch(await readFile(argv[1], 'utf-8'));
for (const commandLine of commandLines) {
if (!commandLine.trim()) continue;
const splitted = splitCommandString(commandLine.trim());
commands.push(splitted);
}
return commands;
} else {
return [argv];
}
}
// We need this special case here because by the time the `version` command
// runs, the keychain has already been setup.
public checkIfKeychainEnabled(argv: string[]) {
@ -433,15 +417,12 @@ class Application extends BaseApplication {
if (argv.length) {
this.gui_ = this.dummyGui();
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
const initialFolder = await Folder.load(Setting.value('activeFolderId'));
await this.switchCurrentFolder(initialFolder);
await this.applySettingsSideEffects();
try {
const commands = await this.commandList(argv);
for (const command of commands) {
await this.execCommand(command);
}
await this.execCommand(argv);
} catch (error) {
if (this.showStackTraces_) {
console.error(error);
@ -453,6 +434,7 @@ class Application extends BaseApplication {
}
await Setting.saveAll();
await this.database_.close();
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
// https://stackoverflow.com/questions/18050095

View File

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

View File

@ -1,19 +0,0 @@
const BaseCommand = require('./base-command').default;
const { _ } = require('@joplin/lib/locale');
class Command extends BaseCommand {
usage() {
return 'batch <file-path>';
}
description() {
return _('Runs the commands contained in the text file. There should be one command per line.');
}
async action() {
// Implementation is in app.js::commandList()
throw new Error('No implemented');
}
}
module.exports = Command;

View File

@ -0,0 +1,79 @@
import { splitCommandBatch } from '@joplin/lib/string-utils';
import BaseCommand from './base-command';
import { _ } from '@joplin/lib/locale';
import { splitCommandString } from '@joplin/utils';
import iterateStdin from './utils/iterateStdin';
import { readFile } from 'fs-extra';
import app from './app';
interface Options {
'file-path': string;
options: {
'continue-on-failure': boolean;
};
}
class Command extends BaseCommand {
public usage() {
return 'batch <file-path>';
}
public options() {
return [
// These are present mostly for testing purposes
['--continue-on-failure', 'Continue running commands when one command in the batch fails.'],
];
}
public description() {
return _('Runs the commands contained in the text file. There should be one command per line.');
}
private streamCommands_ = async function*(filePath: string) {
const processLines = function*(lines: string) {
const commandLines = splitCommandBatch(lines);
for (const command of commandLines) {
if (!command.trim()) continue;
yield splitCommandString(command.trim());
}
};
if (filePath === '-') { // stdin
// Iterating over standard input conflicts with the CLI app's GUI.
if (app().hasGui()) {
throw new Error(_('Reading commands from standard input is only available in CLI mode.'));
}
for await (const lines of iterateStdin('command> ')) {
yield* processLines(lines);
}
} else {
const data = await readFile(filePath, 'utf-8');
yield* processLines(data);
}
};
public async action(options: Options) {
let lastError;
for await (const command of this.streamCommands_(options['file-path'])) {
try {
await app().refreshCurrentFolder();
await app().execCommand(command);
} catch (error) {
if (options.options['continue-on-failure']) {
app().stdout(error.message);
lastError = error;
} else {
throw error;
}
}
}
if (lastError) {
throw lastError;
}
}
}
module.exports = Command;

View File

@ -6,6 +6,7 @@ import app from './app';
import { _ } from '@joplin/lib/locale';
import { ImportOptions } from '@joplin/lib/services/interop/types';
import { unique } from '@joplin/lib/array';
import Folder from '@joplin/lib/models/Folder';
class Command extends BaseCommand {
public override usage() {
@ -32,14 +33,16 @@ class Command extends BaseCommand {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public override async action(args: any) {
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
let destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
if (args.notebook && !destinationFolder) throw new Error(_('Cannot find "%s".', args.notebook));
if (!destinationFolder) destinationFolder = await Folder.defaultFolder();
const importOptions: ImportOptions = {};
importOptions.path = args.path;
importOptions.format = args.options.format ? args.options.format : 'auto';
importOptions.destinationFolderId = folder ? folder.id : null;
importOptions.destinationFolderId = destinationFolder ? destinationFolder.id : null;
let lastProgress = '';

View File

@ -0,0 +1,104 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import mockShareService from '@joplin/lib/testing/share/mockShareService';
import { createFolderTree, setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import Setting from '@joplin/lib/models/Setting';
const Command = require('./command-publish');
const setUpCommand = () => {
const onStdout = jest.fn();
const command = setupCommandForTesting(Command, onStdout);
return { command, onStdout };
};
describe('command-publish', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
mockShareService({
getShares: async () => {
return { items: [] };
},
postShares: async () => ({ id: 'test-id' }),
getShareInvitations: async () => null,
}, ShareService.instance());
});
test('should publish a note', async () => {
const { command, onStdout } = setUpCommand();
const testFolder = await Folder.save({ title: 'Test' });
const testNote = await Note.save({ title: 'test', parent_id: testFolder.id });
await command.action({
note: testNote.id,
options: {
force: true,
},
});
// Should be shared
await waitFor(async () => {
expect(await Note.load(testNote.id)).toMatchObject({
is_shared: 1,
});
});
// Should have logged the publication URL
expect(onStdout).toHaveBeenCalled();
expect(onStdout.mock.lastCall[0]).toMatch(/Published at URL:/);
});
test('should be enabled for Joplin Server and Cloud sync targets', () => {
const { command } = setUpCommand();
Setting.setValue('sync.target', 1);
expect(command.enabled()).toBe(false);
const supportedSyncTargets = [9, 10, 11];
for (const id of supportedSyncTargets) {
Setting.setValue('sync.target', id);
expect(command.enabled()).toBe(true);
}
});
test('should not ask for confirmation if a note is already published', async () => {
const { command } = setUpCommand();
const promptMock = jest.fn(() => true);
command.setPrompt(promptMock);
await createFolderTree('', [
{
title: 'folder 1',
children: [
{
title: 'note 1',
body: 'test',
},
],
},
]);
const noteId = (await Note.loadByTitle('note 1')).id;
// Should ask for confirmation when first sharing
await command.action({
note: noteId,
options: { },
});
expect(promptMock).toHaveBeenCalledTimes(1);
expect(await Note.load(noteId)).toMatchObject({ is_shared: 1 });
// Should not ask for confirmation if called again for the same note
await command.action({
note: noteId,
options: { },
});
expect(promptMock).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,64 @@
import { _ } from '@joplin/lib/locale';
import BaseCommand from './base-command';
import app from './app';
import Logger from '@joplin/utils/Logger';
import ShareService from '@joplin/lib/services/share/ShareService';
import { ModelType } from '@joplin/lib/BaseModel';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import Setting from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('command-publish');
type Args = {
note: string;
options: {
force?: boolean;
};
};
class Command extends BaseCommand {
public usage() {
return 'publish [note]';
}
public description() {
return _('Publishes a note to Joplin Server or Joplin Cloud');
}
public options() {
return [
['-f, --force', _('Do not ask for user confirmation.')],
];
}
public enabled() {
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
}
public async action(args: Args) {
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
const parent = await app().loadItem(ModelType.Folder, targetNote.parent_id);
const force = args.options.force;
const alreadyShared = !!targetNote.is_shared;
const ok = force || alreadyShared ? true : await this.prompt(
_('Publish note "%s" (in notebook "%s")?', targetNote.title, parent.title ?? '<root>'),
{ booleanAnswerDefault: 'n' },
);
if (!ok) return;
logger.info('Share note: ', targetNote.id);
const share = await ShareService.instance().shareNote(targetNote.id, false);
this.stdout(_('Synchronising...'));
await reg.waitForSyncFinishedThenSync();
const userId = ShareService.instance().userId;
const shareUrl = ShareService.instance().shareUrl(userId, share);
this.stdout(_('Published at URL: %s', shareUrl));
}
}
module.exports = Command;

View File

@ -14,17 +14,25 @@ class Command extends BaseCommand {
return `${_('Start, stop or check the API server. To specify on which port it should run, set the api.port config variable. Commands are (%s).', ['start', 'stop', 'status'].join('|'))} This is an experimental feature - use at your own risks! It is recommended that the server runs off its own separate profile so that no two CLI instances access that profile at the same time. Use --profile to specify the profile path.`;
}
options() {
return [
['--exit-early', 'Allow the command to exit while the server is still running. The server will still stop when the app exits. Valid only for the `start` subcommand.'],
['--quiet', 'Log less information to the console. More verbose logs will still be available through log-clipper.txt.'],
];
}
async action(args) {
const command = args.command;
const ClipperServer = require('@joplin/lib/ClipperServer').default;
ClipperServer.instance().initialize();
const stdoutFn = (...s) => this.stdout(s.join(' '));
const ignoreOutputFn = ()=>{};
const clipperLogger = new Logger();
clipperLogger.addTarget('file', { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget('console', { console: {
info: stdoutFn,
warn: stdoutFn,
info: args.options.quiet ? ignoreOutputFn : stdoutFn,
warn: args.options.quiet ? ignoreOutputFn : stdoutFn,
error: stdoutFn,
} });
ClipperServer.instance().setDispatch(() => {});
@ -38,7 +46,11 @@ class Command extends BaseCommand {
this.stdout(_('Server is already running on port %d', runningOnPort));
} else {
await shim.fsDriver().writeFile(pidPath, process.pid.toString(), 'utf-8');
await ClipperServer.instance().start(); // Never exit
const promise = ClipperServer.instance().start();
if (!args.options['exit-early']) {
await promise; // Never exit
}
}
} else if (command === 'status') {
this.stdout(runningOnPort ? _('Server is running on port %d', runningOnPort) : _('Server is not running.'));

View File

@ -149,6 +149,7 @@ class Command extends BaseCommand {
waiting: invitation.status === ShareUserStatus.Waiting,
rejected: invitation.status === ShareUserStatus.Rejected,
folderId: invitation.share.folder_id,
canWrite: !!invitation.can_write,
fromUser: {
email: invitation.share.user?.email,
},

View File

@ -0,0 +1,43 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import mockShareService from '@joplin/lib/testing/share/mockShareService';
import { setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
const Command = require('./command-unpublish');
describe('command-unpublish', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
mockShareService({
getShares: async () => {
return { items: [{ id: 'test-id' }] };
},
postShares: async () => {
throw new Error('Unexpected call to postShares');
},
getShareInvitations: async () => null,
}, ShareService.instance());
});
test('should unpublish a note', async () => {
const command = setupCommandForTesting(Command, ()=>{});
const testFolder = await Folder.save({ title: 'Test' });
const testNote = await Note.save({ title: 'test', parent_id: testFolder.id, is_shared: 1 });
await command.action({
note: testNote.id,
});
await waitFor(async () => {
expect(await Note.load(testNote.id)).toMatchObject({
is_shared: 0,
});
});
});
});

View File

@ -0,0 +1,57 @@
import { _ } from '@joplin/lib/locale';
import BaseCommand from './base-command';
import app from './app';
import Logger from '@joplin/utils/Logger';
import ShareService from '@joplin/lib/services/share/ShareService';
import { ModelType } from '@joplin/lib/BaseModel';
import Note from '@joplin/lib/models/Note';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import Setting from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('command-unpublish');
type Args = {
note: string;
};
class Command extends BaseCommand {
public usage() {
return 'publish [note]';
}
public description() {
return _('Publishes a note to Joplin Server or Joplin Cloud');
}
public options() {
return [
['-f, --force', _('Do not ask for user confirmation.')],
];
}
public enabled() {
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
}
public async action(args: Args) {
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
if (!targetNote.is_shared) {
throw new Error(_('Note not published: %s', targetNote.title));
}
logger.info('Unshare note: ', targetNote.id);
await ShareService.instance().unshareNote(targetNote.id);
const note = await Note.load(targetNote.id);
if (note.is_shared) {
throw new Error('Assertion failure: The note is still shared.');
}
this.stdout(_('Synchronising...'));
await reg.waitForSyncFinishedThenSync();
}
}
module.exports = Command;

View File

@ -2,6 +2,7 @@ import BaseCommand from './base-command';
import app from './app';
import { _ } from '@joplin/lib/locale';
import BaseModel from '@joplin/lib/BaseModel';
import Folder from '@joplin/lib/models/Folder';
class Command extends BaseCommand {
public override usage() {
@ -20,6 +21,18 @@ class Command extends BaseCommand {
public override async action(args: any) {
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
// Auto-expand parent folders in GUI if present
if (app().gui() && app().gui().widget && app().gui().widget('folderList')) {
const folderListWidget = app().gui().widget('folderList');
if (folderListWidget.expandToFolder) {
// Get all folders to pass to expandToFolder
const folders = await Folder.all();
folderListWidget.folders = folders; // Ensure widget has current folders
folderListWidget.expandToFolder(folder.id);
}
}
app().switchCurrentFolder(folder);
}
}

View File

@ -4,11 +4,14 @@ import BaseModel from '@joplin/lib/BaseModel';
import Setting from '@joplin/lib/models/Setting';
import { _ } from '@joplin/lib/locale';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { getDisplayParentId, getTrashFolderId } from '@joplin/lib/services/trash';
import {
getDisplayParentId,
getTrashFolderId,
} from '@joplin/lib/services/trash';
const ListWidget = require('tkwidgets/ListWidget.js');
export default class FolderListWidget extends ListWidget {
export default class FolderListWidget extends ListWidget {
private folders_: FolderEntity[] = [];
public constructor() {
@ -31,7 +34,18 @@ export default class FolderListWidget extends ListWidget {
if (item === '-') {
output.push('-'.repeat(this.innerWidth));
} else if (item.type_ === Folder.modelType()) {
output.push(' '.repeat(this.folderDepth(this.folders, item.id)));
const depth = this.folderDepth(this.folders, item.id);
output.push(' '.repeat(depth));
// Add collapse/expand indicator
const hasChildren = this.folderHasChildren_(this.folders, item.id);
if (hasChildren) {
const collapsedFolders = Setting.value('collapsedFolderIds');
const isCollapsed = collapsedFolders.includes(item.id);
output.push(isCollapsed ? '[+] ' : '[-] ');
} else {
output.push(' '); // Space for alignment
}
if (this.showIds) {
output.push(Folder.shortId(item.id));
@ -65,7 +79,10 @@ export default class FolderListWidget extends ListWidget {
let output = 0;
while (true) {
const folder = BaseModel.byId(folders, folderId);
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
const folderParentId = getDisplayParentId(
folder,
folders.find((f) => f.id === folder.parent_id),
);
if (!folder || !folderParentId) return output;
output++;
folderId = folderParentId;
@ -153,7 +170,10 @@ export default class FolderListWidget extends ListWidget {
public folderHasChildren_(folders: FolderEntity[], folderId: string) {
for (let i = 0; i < folders.length; i++) {
const folder = folders[i];
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
const folderParentId = getDisplayParentId(
folder,
folders.find((f) => f.id === folder.parent_id),
);
if (folderParentId === folderId) return true;
}
return false;
@ -161,7 +181,12 @@ export default class FolderListWidget extends ListWidget {
public render() {
if (this.updateItems_) {
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
this.logger().debug(
'Rebuilding items...',
this.notesParentType,
this.selectedJoplinItemId,
this.selectedSearchId,
);
const wasSelectedItemId = this.selectedJoplinItemId;
const previousParentType = this.notesParentType;
@ -170,12 +195,20 @@ export default class FolderListWidget extends ListWidget {
const orderFolders = (parentId: string) => {
for (let i = 0; i < this.folders.length; i++) {
const f = this.folders[i];
const originalParent = this.folders_.find(f => f.id === f.parent_id);
const originalParent = this.folders_.find(
(f) => f.id === f.parent_id,
);
const folderParentId = getDisplayParentId(f, originalParent); // f.parent_id ? f.parent_id : '';
if (folderParentId === parentId) {
newItems.push(f);
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
// Only recurse into children if the folder is not collapsed
if (this.folderHasChildren_(this.folders, f.id)) {
const collapsedFolders = Setting.value('collapsedFolderIds');
if (!collapsedFolders.includes(f.id)) {
orderFolders(f.id);
}
}
}
}
};
@ -221,4 +254,53 @@ export default class FolderListWidget extends ListWidget {
const index = this.itemIndexByKey('id', itemId);
this.currentIndex = index >= 0 ? index : 0;
}
public toggleFolderCollapse() {
const item = this.currentItem;
if (item && item.type_ === Folder.modelType() && this.folderHasChildren_(this.folders, item.id)) {
const collapsedFolders = Setting.value('collapsedFolderIds');
const isCollapsed = collapsedFolders.includes(item.id);
if (isCollapsed) {
const newCollapsed = collapsedFolders.filter((id: string) => id !== item.id);
Setting.setValue('collapsedFolderIds', newCollapsed);
} else {
Setting.setValue('collapsedFolderIds', [...collapsedFolders, item.id]);
}
this.updateItems_ = true;
this.invalidate();
return true;
}
return false;
}
public expandToFolder(folderId: string) {
// Find all parent folders and expand them
const parentsToExpand: string[] = [];
let currentId = folderId;
while (currentId) {
const folder = BaseModel.byId(this.folders, currentId);
if (!folder) break;
const parentId = getDisplayParentId(
folder,
this.folders.find((f) => f.id === folder.parent_id),
);
if (parentId) {
parentsToExpand.unshift(parentId);
currentId = parentId;
} else {
break;
}
}
// Expand all parent folders
const collapsedFolders = Setting.value('collapsedFolderIds');
const newCollapsed = collapsedFolders.filter((id: string) => !parentsToExpand.includes(id));
Setting.setValue('collapsedFolderIds', newCollapsed);
this.updateItems_ = true;
this.invalidate();
}
}

View File

@ -0,0 +1,54 @@
import { createInterface } from 'readline/promises';
const iterateStdin = async function*(prompt: string) {
let nextLineListeners: (()=> void)[] = [];
const dispatchAllListeners = () => {
const listeners = nextLineListeners;
nextLineListeners = [];
for (const listener of listeners) {
listener();
}
};
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
rl.setPrompt(prompt);
let buffer: string[] = [];
rl.on('line', (line) => {
buffer.push(line);
dispatchAllListeners();
});
let done = false;
rl.on('close', () => {
done = true;
dispatchAllListeners();
});
const readNextLines = () => {
return new Promise<string|null>(resolve => {
if (done) {
resolve(null);
} else if (buffer.length > 0) {
resolve(buffer.join('\n'));
buffer = [];
} else {
nextLineListeners.push(() => {
resolve(buffer.join('\n'));
buffer = [];
});
}
});
};
while (!done) {
rl.prompt();
const lines = await readNextLines();
yield lines;
}
};
export default iterateStdin;

View File

@ -9,7 +9,7 @@ const shimInitCli = (options: ShimInitOptions) => {
shim.showMessageBox = async (message: string, options: ShowMessageBoxOptions) => {
const gui = app()?.gui();
let answers = options.buttons ?? [_('Ok'), _('Cancel')];
let answers = options.buttons ?? [_('OK'), _('Cancel')];
if (options.type === 'error' || options.type === 'info') {
answers = [];

View File

@ -35,7 +35,7 @@
],
"owner": "Laurent Cozic"
},
"version": "3.4.0",
"version": "3.4.1",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
@ -57,7 +57,7 @@
"proper-lockfile": "4.1.2",
"redux": "4.2.1",
"server-destroy": "1.0.1",
"sharp": "0.33.5",
"sharp": "0.34.2",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
@ -72,12 +72,12 @@
"devDependencies": {
"@joplin/tools": "~3.4",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.12",
"@types/node": "18.19.86",
"@types/jest": "29.5.14",
"@types/node": "18.19.112",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",
"temp": "0.9.4",
"typescript": "5.4.5"
"typescript": "5.8.2"
}
}

View File

@ -1,6 +1,6 @@
import MarkupToHtml, { MarkupLanguage } from '@joplin/renderer/MarkupToHtml';
import { RenderResult } from '@joplin/renderer/types';
import MarkupToHtml from '@joplin/renderer/MarkupToHtml';
import { RenderResult, MarkupLanguage } from '@joplin/renderer/types';
describe('MarkupToHtml', () => {

View File

@ -0,0 +1,13 @@
<p>A task list created by the TipTap editor:</p>
<ul data-type="taskList">
<li><label contenteditable="false"><input type="checkbox"><span></span></label>
<div>
<p>Testing...</p>
</div>
</li>
<li><label contenteditable="false"><input type="checkbox"><span></span></label>
<div>
<p>testing</p>
</div>
</li>
</ul>

View File

@ -0,0 +1,5 @@
A task list created by the TipTap editor:
- [ ] Testing...
- [ ] testing

View File

@ -0,0 +1,26 @@
<p>List 1:</p>
<ul>
<li><label><input type="checkbox"/>This</label></li>
<li><label><input type="checkbox" checked/>is a test.</label></li>
</ul>
<p>List 2:</p>
<ul>
<li>
<input type="checkbox" id="checkbox-1"/><label for="checkbox-1">This</label>
</li>
<li>
<input type="checkbox" checked id="checkbox-2"/><label for="checkbox-2">is another test.</label>
</li>
</ul>
<p>List 3:</p>
<ul>
<li>
<input type="checkbox" id="checkbox-a1"/><label for="checkbox-a1">This</label>
</li>
<li>
<input type="checkbox" checked id="checkbox-a2"/><label for="checkbox-a2">is another test.</label>
</li>
<li>
<input type="checkbox" checked id="checkbox-a3"/><label for="checkbox-a3"></label>
</li>
</ul>

View File

@ -0,0 +1,15 @@
List 1:
- [ ] This
- [x] is a test.
List 2:
- [ ] This
- [x] is another test.
List 3:
- [ ] This
- [x] is another test.
- [x] &nbsp;

View File

@ -1,7 +1,7 @@
<ul class="joplin-checklist">
<ul class="joplin-checklist" data-is-checklist="1">
<li>Not checked</li>
<li class="checked">Checked!!
<ul class="joplin-checklist">
<ul class="joplin-checklist" data-is-checklist="1">
<li class="checked">Indented, with <strong>bold</strong></li>
<li>Indented, not checked</li>
</ul>

View File

@ -1,15 +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,
<span 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;"/></span>
<span 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;"/></span>
<span 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>
&Tab;"/></span>

View File

@ -0,0 +1,3 @@
1. File without extension and leading `./`: [file1](./file1). Gets imported, but filename is converted to extension, like `<internal_id>.file1`
2. File without extension: [file2](file2). Not imported at all.
3. File with extension: [file3](file3.text). Gets imported properly.

View File

@ -8,7 +8,7 @@ import { FileLocker } from '@joplin/utils/fs';
import { IpcMessageHandler, IpcServer, Message, newHttpError, sendMessage, SendMessageOptions, startServer, stopServer } from '@joplin/utils/ipc';
import { BrowserWindow, Tray, WebContents, screen, App, nativeTheme } from 'electron';
import bridge from './bridge';
const url = require('url');
import * as url from 'url';
const path = require('path');
const { dirname } = require('@joplin/lib/path-utils');
const fs = require('fs-extra');

View File

@ -86,8 +86,14 @@ export default class InteropServiceHelper {
// pdfs.
// https://github.com/laurent22/joplin/issues/6254.
await win.webContents.executeJavaScript('document.querySelectorAll(\'details\').forEach(el=>el.setAttribute(\'open\',\'\'))');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const data = await win.webContents.printToPDF(options as any);
const data = await win.webContents.printToPDF({
...options,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partially refactored old code before rule was applied
pageSize: options.pageSize as any,
// Allows users to override the CSS page size.
// See https://github.com/laurent22/joplin/issues/13096
preferCSSPageSize: true,
});
resolve(data);
} catch (error) {
reject(error);

View File

@ -55,11 +55,18 @@ import userFetcher, { initializeUserFetcher } from '@joplin/lib/utils/userFetche
import { parseNotesParent } from '@joplin/lib/reducer';
import OcrService from '@joplin/lib/services/ocr/OcrService';
import OcrDriverTesseract from '@joplin/lib/services/ocr/drivers/OcrDriverTesseract';
import OcrDriverTranscribe from '@joplin/lib/services/ocr/drivers/OcrDriverTranscribe';
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';
import initializeCommandService from './utils/initializeCommandService';
import OcrDriverBase from '@joplin/lib/services/ocr/OcrDriverBase';
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
import Note from '@joplin/lib/models/Note';
import Resource from '@joplin/lib/models/Resource';
const perfLogger = PerformanceLogger.create();
const pluginClasses = [
require('./plugins/GotoAnything').default,
@ -67,6 +74,8 @@ const pluginClasses = [
const appDefaultState = createAppDefaultState(resourceEditWatcherDefaultState);
type StartupTask = { label: string; task: ()=> void|Promise<void> };
class Application extends BaseApplication {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@ -348,16 +357,19 @@ class Application extends BaseApplication {
// 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(
const drivers: OcrDriverBase[] = [];
drivers.push(new OcrDriverTesseract(
{ createWorker: Tesseract.createWorker },
{
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);
drivers.push(new OcrDriverTranscribe());
this.ocrService_ = new OcrService(drivers);
}
void this.ocrService_.runInBackground();
@ -411,56 +423,53 @@ class Application extends BaseApplication {
});
}
// 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
// insert an extra argument so that they can be processed in a consistent way everywhere.
if (!bridge().electronIsDev()) argv.splice(1, 0, '.');
private buildStartupTasks_() {
const tasks: StartupTask[] = [];
const addTask = (label: string, task: StartupTask['task']) => {
tasks.push({ label, task });
};
argv = await super.start(argv, startOptions);
addTask('app/set up extra debug logging', () => {
reg.logger().info('app.start: doing regular boot');
const dir: string = Setting.value('profileDir');
await this.setupIntegrationTestUtils();
syncDebugLog.enabled = false;
bridge().setLogFilePath(Logger.globalLogger.logFilePath());
if (dir.endsWith('dev-desktop-2')) {
syncDebugLog.addTarget(TargetType.File, {
path: `${homedir()}/synclog.txt`,
});
syncDebugLog.enabled = true;
syncDebugLog.info(`Profile dir: ${dir}`);
}
});
await this.applySettingsSideEffects();
addTask('app/set up registry', () => {
reg.setDispatch(this.dispatch.bind(this));
reg.setShowErrorMessageBoxHandler((message: string) => { bridge().showErrorMessageBox(message); });
});
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
reg.logger().info('app.start: doing upgradeSyncTarget action');
bridge().mainWindow().show();
return { action: 'upgradeSyncTarget' };
}
addTask('app/set up auto updater', () => {
this.setupAutoUpdaterService();
});
reg.logger().info('app.start: doing regular boot');
const dir: string = Setting.value('profileDir');
syncDebugLog.enabled = false;
if (dir.endsWith('dev-desktop-2')) {
syncDebugLog.addTarget(TargetType.File, {
path: `${homedir()}/synclog.txt`,
});
syncDebugLog.enabled = true;
syncDebugLog.info(`Profile dir: ${dir}`);
}
this.setupAutoUpdaterService();
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); });
addTask('app/set up AlarmService', () => {
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
});
if (Setting.value('flagOpenDevTools')) {
bridge().openDevTools();
addTask('app/openDevTools', () => {
bridge().openDevTools();
});
}
this.protocolHandler_ = bridge().electronApp().getCustomProtocolHandler();
this.protocolHandler_.allowReadAccessToDirectory(__dirname); // App bundle directory
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('cacheDir'));
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('resourceDir'));
addTask('app/set up custom protocol handler', async () => {
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'));
@ -468,44 +477,52 @@ class Application extends BaseApplication {
// 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);
addTask('app/initialize PluginManager, redux, CommandService, and KeymapService', async () => {
PluginManager.instance().dispatch_ = this.dispatch.bind(this);
PluginManager.instance().setLogger(reg.logger());
PluginManager.instance().register(pluginClasses);
this.initRedux();
this.initRedux();
PerFolderSortOrderService.initialize();
initializeCommandService(this.store(), Setting.value('env') === 'dev');
initializeCommandService(this.store(), Setting.value('env') === 'dev');
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService.initialize(menuCommandNames());
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService.initialize(menuCommandNames());
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (error) {
reg.logger().error(error);
}
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
await refreshFolders((action: any) => this.dispatch(action), '');
const tags = await Tag.allWithNotes();
this.dispatch({
type: 'TAG_UPDATE_ALL',
items: tags,
try {
await keymapService.loadCustomKeymap(`${Setting.value('profileDir')}/keymap-desktop.json`);
} catch (error) {
reg.logger().error(error);
}
});
await this.setupCustomCss();
addTask('app/initialize PerFolderSortOrderService', () => {
PerFolderSortOrderService.initialize();
});
addTask('app/dispatch initial settings', () => {
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
});
addTask('app/update folders and tags', async () => {
await refreshFolders((action) => this.dispatch(action), '');
const tags = await Tag.allWithNotes();
this.dispatch({
type: 'TAG_UPDATE_ALL',
items: tags,
});
});
addTask('app/set up custom CSS', async () => {
await this.setupCustomCss();
});
// const masterKeys = await MasterKey.all();
@ -514,188 +531,242 @@ class Application extends BaseApplication {
// items: masterKeys,
// });
const getNotesParent = async () => {
let notesParent = parseNotesParent(Setting.value('notesParent'), Setting.value('activeFolderId'));
if (notesParent.type === 'Tag' && !(await Tag.load(notesParent.selectedItemId))) {
notesParent = {
type: 'Folder',
selectedItemId: Setting.value('activeFolderId'),
};
addTask('app/send initial selection to redux', async () => {
const getNotesParent = async () => {
let notesParent = parseNotesParent(Setting.value('notesParent'), Setting.value('activeFolderId'));
if (notesParent.type === 'Tag' && !(await Tag.load(notesParent.selectedItemId))) {
notesParent = {
type: 'Folder',
selectedItemId: Setting.value('activeFolderId'),
};
}
return notesParent;
};
const notesParent = await getNotesParent();
if (notesParent.type === 'SmartFilter') {
this.store().dispatch({
type: 'SMART_FILTER_SELECT',
id: notesParent.selectedItemId,
});
} else if (notesParent.type === 'Tag') {
this.store().dispatch({
type: 'TAG_SELECT',
id: notesParent.selectedItemId,
});
} else {
this.store().dispatch({
type: 'FOLDER_SELECT',
id: notesParent.selectedItemId,
});
}
return notesParent;
};
const notesParent = await getNotesParent();
this.store().dispatch({
type: 'FOLDER_SET_COLLAPSED_ALL',
ids: Setting.value('collapsedFolderIds'),
});
if (notesParent.type === 'SmartFilter') {
this.store().dispatch({
type: 'SMART_FILTER_SELECT',
id: notesParent.selectedItemId,
type: 'NOTE_DEVTOOLS_SET',
value: Setting.value('flagOpenDevTools'),
});
} else if (notesParent.type === 'Tag') {
this.store().dispatch({
type: 'TAG_SELECT',
id: notesParent.selectedItemId,
});
} else {
this.store().dispatch({
type: 'FOLDER_SELECT',
id: notesParent.selectedItemId,
});
}
this.store().dispatch({
type: 'FOLDER_SET_COLLAPSED_ALL',
ids: Setting.value('collapsedFolderIds'),
});
this.store().dispatch({
type: 'NOTE_DEVTOOLS_SET',
value: Setting.value('flagOpenDevTools'),
addTask('app/initializeUserFetcher', async () => {
initializeUserFetcher();
shim.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60);
});
// Always disable on Mac for now - and disable too for the few apps that may have the flag enabled.
// At present, it only seems to work on Windows.
if (shim.isMac()) {
Setting.setValue('featureFlag.autoUpdaterServiceEnabled', false);
}
addTask('app/updateTray', () => this.updateTray());
// 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 (!Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
void checkForUpdates(true, bridge().mainWindow(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
// Initial check on startup
shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
addTask('app/set main window state', () => {
if (Setting.value('startMinimized') && Setting.value('showTrayIcon')) {
bridge().mainWindow().hide();
} else {
bridge().mainWindow().show();
}
}
});
initializeUserFetcher();
shim.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60);
addTask('app/start maintenance tasks', () => {
// Always disable on Mac for now - and disable too for the few apps that may have the flag enabled.
// At present, it only seems to work on Windows.
if (shim.isMac()) {
Setting.setValue('featureFlag.autoUpdaterServiceEnabled', false);
}
this.updateTray();
// 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 (!Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
void checkForUpdates(true, bridge().mainWindow(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
shim.setTimeout(() => {
void AlarmService.garbageCollect();
}, 1000 * 60 * 60);
// Initial check on startup
shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
}
}
if (Setting.value('startMinimized') && Setting.value('showTrayIcon')) {
bridge().mainWindow().hide();
} else {
bridge().mainWindow().show();
}
shim.setTimeout(() => {
void AlarmService.garbageCollect();
}, 1000 * 60 * 60);
void ShareService.instance().maintenance();
void ShareService.instance().maintenance();
ResourceService.runInBackground();
ResourceService.runInBackground();
if (Setting.value('env') === 'dev') {
void AlarmService.updateAllNotifications();
} else {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
void reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
if (Setting.value('env') === 'dev') {
void AlarmService.updateAllNotifications();
} else {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
void reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
void AlarmService.updateAllNotifications();
void DecryptionWorker.instance().scheduleStart();
});
}
void DecryptionWorker.instance().scheduleStart();
});
}
const clipperLogger = new Logger();
clipperLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget(TargetType.Console);
ClipperServer.instance().initialize(actionApi);
ClipperServer.instance().setEnabled(!Setting.value('altInstanceId'));
ClipperServer.instance().setLogger(clipperLogger);
ClipperServer.instance().setDispatch(this.store().dispatch);
if (ClipperServer.instance().enabled() && Setting.value('clipperServer.autoStart')) {
void ClipperServer.instance().start();
}
ExternalEditWatcher.instance().setLogger(reg.logger());
ExternalEditWatcher.instance().initialize(bridge, this.store().dispatch);
ResourceEditWatcher.instance().initialize(
reg.logger(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(action: any) => { this.store().dispatch(action); },
(path: string) => bridge().openItem(path),
() => this.store().getState().windowId,
);
// Forwards the local event to the global event manager, so that it can
// be picked up by the plugin manager.
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
ResourceEditWatcher.instance().on('resourceChange', (event: any) => {
eventManager.emit(EventName.ResourceChange, event);
RevisionService.instance().runInBackground();
this.startRotatingLogMaintenance(Setting.value('profileDir'));
});
RevisionService.instance().runInBackground();
addTask('app/set up ClipperServer', () => {
const clipperLogger = new Logger();
clipperLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget(TargetType.Console);
ClipperServer.instance().initialize(actionApi);
ClipperServer.instance().setEnabled(!Setting.value('altInstanceId'));
ClipperServer.instance().setLogger(clipperLogger);
ClipperServer.instance().setDispatch(this.store().dispatch);
if (ClipperServer.instance().enabled() && Setting.value('clipperServer.autoStart')) {
void ClipperServer.instance().start();
}
});
addTask('app/set up external edit watchers', () => {
ExternalEditWatcher.instance().setLogger(reg.logger());
ExternalEditWatcher.instance().initialize(bridge, this.store().dispatch);
ResourceEditWatcher.instance().initialize(
reg.logger(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(action: any) => { this.store().dispatch(action); },
(path: string) => bridge().openItem(path),
() => this.store().getState().windowId,
);
// Forwards the local event to the global event manager, so that it can
// be picked up by the plugin manager.
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
ResourceEditWatcher.instance().on('resourceChange', (event: any) => {
eventManager.emit(EventName.ResourceChange, event);
});
});
// Make it available to the console window - useful to call revisionService.collectRevisions()
if (Setting.value('env') === 'dev') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(window as any).joplin = {
revisionService: RevisionService.instance(),
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
resourceService: ResourceService.instance(),
searchEngine: SearchEngine.instance(),
ocrService: () => this.ocrService_,
};
addTask('app/add debug variables', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(window as any).joplin = {
revisionService: RevisionService.instance(),
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
resourceService: ResourceService.instance(),
searchEngine: SearchEngine.instance(),
shim,
Note,
Folder,
Resource,
Setting,
ocrService: () => this.ocrService_,
};
});
}
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
bridge().setOnAllowedExtensionsChangeListener((newExtensions) => {
Setting.setValue('linking.extraAllowedExtensions', newExtensions);
addTask('app/listen for main process events', () => {
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
bridge().setOnAllowedExtensionsChangeListener((newExtensions) => {
Setting.setValue('linking.extraAllowedExtensions', newExtensions);
});
ipcRenderer.on('window-focused', (_event, newWindowId) => {
const currentWindowId = this.store().getState().windowId;
if (newWindowId !== currentWindowId) {
this.dispatch({
type: 'WINDOW_FOCUS',
windowId: newWindowId,
lastWindowId: currentWindowId,
});
}
});
});
ipcRenderer.on('window-focused', (_event, newWindowId) => {
const currentWindowId = this.store().getState().windowId;
if (newWindowId !== currentWindowId) {
this.dispatch({
type: 'WINDOW_FOCUS',
windowId: newWindowId,
lastWindowId: currentWindowId,
});
}
addTask('app/initPluginService', () => this.initPluginService());
addTask('app/setupContextMenu', () => {
this.setupContextMenu();
});
await this.initPluginService();
this.setupContextMenu();
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
this.startRotatingLogMaintenance(Setting.value('profileDir'));
await this.setupOcrService();
eventManager.on(EventName.OcrServiceResourcesProcessed, async () => {
await ResourceService.instance().indexNoteResources();
addTask('app/set up SpellCheckerService', async () => {
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
});
eventManager.on(EventName.NoteResourceIndexed, async () => {
SearchEngine.instance().scheduleSyncTables();
addTask('app/listen for resource events', () => {
eventManager.on(EventName.OcrServiceResourcesProcessed, async () => {
await ResourceService.instance().indexNoteResources();
});
eventManager.on(EventName.NoteResourceIndexed, async () => {
SearchEngine.instance().scheduleSyncTables();
});
});
// Used by tests
ipcRenderer.send('startup-finished');
addTask('app/setupOcrService', () => this.setupOcrService());
return tasks;
}
// 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> {
const startupTask = perfLogger.taskStart('app/start');
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
// insert an extra argument so that they can be processed in a consistent way everywhere.
if (!bridge().electronIsDev()) argv.splice(1, 0, '.');
argv = await super.start(argv, startOptions);
await this.setupIntegrationTestUtils();
bridge().setLogFilePath(Logger.globalLogger.logFilePath());
await this.applySettingsSideEffects();
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
reg.logger().info('app.start: doing upgradeSyncTarget action');
bridge().mainWindow().show();
startupTask.onEnd();
return { action: 'upgradeSyncTarget' };
}
const startupTasks = this.buildStartupTasks_();
for (const task of startupTasks) {
await perfLogger.track(task.label, async () => task.task());
}
// setTimeout(() => {
// void populateDatabase(reg.db(), {
@ -749,6 +820,10 @@ class Application extends BaseApplication {
// await runIntegrationTests();
// Used by tests
ipcRenderer.send('startup-finished');
startupTask.onEnd();
return null;
}

View File

@ -8,8 +8,8 @@ import { urlDecode } from '@joplin/lib/string-utils';
import * as Sentry from '@sentry/electron/main';
import { homedir } from 'os';
import { msleep } from '@joplin/utils/time';
import { pathExists, pathExistsSync, writeFileSync } from 'fs-extra';
import { extname, normalize } from 'path';
import { pathExists, pathExistsSync, writeFileSync, ensureDirSync } from 'fs-extra';
import { extname, normalize, join } from 'path';
import isSafeToOpen from './utils/isSafeToOpen';
import { closeSync, openSync, readSync, statSync } from 'fs';
import { KB } from '@joplin/utils/bytes';
@ -67,6 +67,30 @@ export class Bridge {
this.logFilePath_ = v;
}
private getCrashDumpDirectory(): string {
try {
const platformName = shim.platformName();
switch (platformName) {
case 'win32':
// Windows: Use %LOCALAPPDATA%\CrashDumps
return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'CrashDumps');
case 'darwin':
// macOS: Use ~/Library/Logs/DiagnosticReports
return join(homedir(), 'Library', 'Logs', 'DiagnosticReports');
case 'linux':
// Linux: Use XDG_STATE_HOME (for logs) or fallback to ~/.local/state
return join(process.env.XDG_STATE_HOME || join(homedir(), '.local', 'state'), 'joplin');
default:
// For unknown platforms, default to the home directory
return homedir();
}
} catch (error) {
// If we can't get the platform name, fallback to the home directory
return homedir();
}
}
private sentryInit() {
const getLogLines = () => {
try {
@ -109,7 +133,10 @@ export class Bridge {
log: logAttachment ? logAttachment.data.trim().split('\n') : [],
};
writeFileSync(`${homedir()}/joplin_crash_dump_${date}.json`, JSON.stringify(errorEventWithLog, null, '\t'), 'utf-8');
const crashDumpDir = this.getCrashDumpDirectory();
ensureDirSync(crashDumpDir);
const crashDumpPath = join(crashDumpDir, `joplin_crash_dump_${date}.json`);
writeFileSync(crashDumpPath, JSON.stringify(errorEventWithLog, null, '\t'), 'utf-8');
} catch (error) {
// Ignore the error since we can't handle it here
}

View File

@ -0,0 +1,96 @@
import * as convertHtmlToMarkdown from './convertNoteToMarkdown';
import { AppState, createAppDefaultState } from '../app.reducer';
import Note from '@joplin/lib/models/Note';
import { MarkupLanguage } from '@joplin/renderer';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import Folder from '@joplin/lib/models/Folder';
import { NoteEntity } from '@joplin/lib/services/database/types';
describe('convertNoteToMarkdown', () => {
let state: AppState = undefined;
beforeEach(async () => {
state = createAppDefaultState({});
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
});
it('should set the original note to be trashed', async () => {
const folder = await Folder.save({ title: 'test_folder' });
const htmlNote = await Note.save({ title: 'test', body: '<p>Hello</p>', parent_id: folder.id, markup_language: MarkupLanguage.Html });
state.selectedNoteIds = [htmlNote.id];
await convertHtmlToMarkdown.runtime().execute({ state, dispatch: () => {} });
const refreshedNote = await Note.load(htmlNote.id);
expect(htmlNote.deleted_time).toBe(0);
expect(refreshedNote.deleted_time).not.toBe(0);
});
it('should recreate a new note that is a clone of the original', async () => {
let noteConvertedToMarkdownId = '';
const dispatchFn = jest.fn()
.mockImplementationOnce(() => {})
.mockImplementationOnce(action => {
noteConvertedToMarkdownId = action.id;
});
const folder = await Folder.save({ title: 'test_folder' });
const htmlNoteProperties = {
title: 'test',
body: '<p>Hello</p>',
parent_id: folder.id,
markup_language: MarkupLanguage.Html,
author: 'test-author',
is_todo: 1,
todo_completed: 1,
};
const htmlNote = await Note.save(htmlNoteProperties);
state.selectedNoteIds = [htmlNote.id];
await convertHtmlToMarkdown.runtime().execute({ state, dispatch: dispatchFn });
expect(dispatchFn).toHaveBeenCalledTimes(2);
expect(noteConvertedToMarkdownId).not.toBe('');
const markdownNote = await Note.load(noteConvertedToMarkdownId);
const fields: (keyof NoteEntity)[] = ['parent_id', 'title', 'author', 'is_todo', 'todo_completed'];
for (const field of fields) {
expect(htmlNote[field]).toEqual(markdownNote[field]);
}
});
it('should generate action to trigger notification', async () => {
let originalHtmlNoteId = '';
let actionType = '';
const dispatchFn = jest.fn()
.mockImplementationOnce(action => {
originalHtmlNoteId = action.value;
actionType = action.type;
})
.mockImplementationOnce(() => {});
const folder = await Folder.save({ title: 'test_folder' });
const htmlNoteProperties = {
title: 'test',
body: '<p>Hello</p>',
parent_id: folder.id,
markup_language: MarkupLanguage.Html,
author: 'test-author',
is_todo: 1,
todo_completed: 1,
};
const htmlNote = await Note.save(htmlNoteProperties);
state.selectedNoteIds = [htmlNote.id];
await convertHtmlToMarkdown.runtime().execute({ state, dispatch: dispatchFn });
expect(dispatchFn).toHaveBeenCalledTimes(2);
expect(originalHtmlNoteId).toBe(htmlNote.id);
expect(actionType).toBe('NOTE_HTML_TO_MARKDOWN_DONE');
});
});

View File

@ -0,0 +1,52 @@
import { _ } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
import { stateUtils } from '@joplin/lib/reducer';
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { MarkupLanguage } from '@joplin/renderer';
import { runtime as convertHtmlToMarkdown } from '@joplin/lib/commands/convertHtmlToMarkdown';
import bridge from '../services/bridge';
export const declaration: CommandDeclaration = {
name: 'convertNoteToMarkdown',
label: () => _('Convert note to Markdown'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, noteId: string = null) => {
noteId = noteId || stateUtils.selectedNoteId(context.state);
const note = await Note.load(noteId);
if (!note) return;
try {
const markdownBody = await convertHtmlToMarkdown().execute(context, note.body);
const newNote = await Note.duplicate(note.id);
newNote.body = markdownBody;
newNote.markup_language = MarkupLanguage.Markdown;
await Note.save(newNote);
await Note.delete(note.id, { toTrash: true });
context.dispatch({
type: 'NOTE_HTML_TO_MARKDOWN_DONE',
value: note.id,
});
context.dispatch({
type: 'NOTE_SELECT',
id: newNote.id,
});
} catch (error) {
bridge().showErrorMessageBox(_('Could not convert note to Markdown: %s', error.message));
}
},
enabledCondition: 'oneNoteSelected && noteIsHtml && !noteIsReadOnly',
};
};

View File

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

View File

@ -0,0 +1,28 @@
import * as React from 'react';
import { useContext, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import { Dispatch } from 'redux';
import { PopupNotificationContext } from '../PopupNotification/PopupNotificationProvider';
import { NotificationType } from '../PopupNotification/types';
interface Props {
noteId: string;
dispatch: Dispatch;
}
export default (props: Props) => {
const popupManager = useContext(PopupNotificationContext);
useEffect(() => {
if (!props.noteId || props.noteId === '') return;
props.dispatch({ type: 'NOTE_HTML_TO_MARKDOWN_DONE', value: '' });
const notification = popupManager.createPopup(() => (
<div>{_('The note has been converted to Markdown and the original note has been moved to the trash')}</div>
), { type: NotificationType.Success });
notification.scheduleDismiss();
}, [props.dispatch, popupManager, props.noteId]);
return <div style={{ display: 'none' }}/>;
};

View File

@ -1,4 +1,4 @@
import { useMemo, useRef, useState } from 'react';
import { useMemo, useRef, useState, useCallback } from 'react';
interface Props {
width: number;
@ -9,40 +9,62 @@ interface Props {
const fontSizeCache_: Record<string, number> = {};
export default (props: Props) => {
const containerRef = useRef(null);
const containerRef = useRef<HTMLDivElement>(null);
const [containerReady, setContainerReady] = useState(false);
const refCallback = useCallback((el: HTMLDivElement | null) => {
if (el && !containerRef.current) {
containerRef.current = el;
requestAnimationFrame(() => {
setContainerReady(true);
});
}
}, []);
const fontSize = useMemo(() => {
if (!containerReady) return props.height;
if (!containerReady || !containerRef.current) {
return Math.min(props.height * 0.7, 14);
}
const cacheKey = [props.width, props.height, props.emoji].join('-');
if (fontSizeCache_[cacheKey]) {
return fontSizeCache_[cacheKey];
}
// Set the emoji font size so that it fits within the specified width
// and height. In fact, currently it only looks at the height.
let spanFontSize = props.height;
const span = document.createElement('span');
span.innerText = props.emoji;
span.style.fontSize = `${spanFontSize}px`;
span.style.visibility = 'hidden';
span.style.position = 'absolute';
span.style.whiteSpace = 'nowrap';
containerRef.current.appendChild(span);
let rect = span.getBoundingClientRect();
while (rect.height > props.height) {
spanFontSize -= .5;
while ((rect.height > props.height || rect.width > props.width) && spanFontSize > 1) {
spanFontSize -= 0.5;
span.style.fontSize = `${spanFontSize}px`;
rect = span.getBoundingClientRect();
}
span.remove();
fontSizeCache_[cacheKey] = spanFontSize;
return spanFontSize;
}, [props.width, props.height, props.emoji, containerReady, containerRef]);
}, [props.width, props.height, props.emoji, containerReady]);
return <div className="emoji-box" ref={el => { containerRef.current = el; setContainerReady(true); }} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: props.width, height: props.height, fontSize }}>{props.emoji}</div>;
return <div
ref={refCallback}
style={{
width: props.width,
height: props.height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
fontSize,
}}
>
{props.emoji}
</div>;
};

View File

@ -15,7 +15,7 @@ import { connect } from 'react-redux';
import { AppState } from '../../app.reducer';
import Setting from '@joplin/lib/models/Setting';
import CommandService from '@joplin/lib/services/CommandService';
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk';
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk/ppk';
import ToggleAdvancedSettingsButton from '../ConfigScreen/controls/ToggleAdvancedSettingsButton';
import MacOSMissingPasswordHelpLink from '../ConfigScreen/controls/MissingPasswordHelpLink';

View File

@ -38,12 +38,14 @@ import restart from '../services/restart';
import { connect } from 'react-redux';
import { NoteListColumns } from '@joplin/lib/services/plugins/api/noteListType';
import validateColumns from './NoteListHeader/utils/validateColumns';
import ConversionNotification from './ConversionNotification/ConversionNotification';
import TrashNotification from './TrashNotification/TrashNotification';
import UpdateNotification from './UpdateNotification/UpdateNotification';
import NoteEditor from './NoteEditor/NoteEditor';
import PluginNotification from './PluginNotification/PluginNotification';
import { Toast } from '@joplin/lib/services/plugins/api/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import { Dispatch } from 'redux';
const ipcRenderer = require('electron').ipcRenderer;
@ -84,6 +86,7 @@ interface Props {
showInvalidJoplinCloudCredential: boolean;
toast: Toast;
shouldSwitchToAppleSiliconVersion: boolean;
noteHtmlToMarkdownDone: string;
}
interface ShareFolderDialogOptions {
@ -797,6 +800,10 @@ class MainScreenComponent extends React.Component<Props, State> {
return (
<div style={style}>
<ConversionNotification
noteId={this.props.noteHtmlToMarkdownDone}
dispatch={this.props.dispatch as Dispatch}
/>
<TrashNotification
lastDeletion={this.props.lastDeletion}
lastDeletionNotificationTime={this.props.lastDeletionNotificationTime}
@ -853,6 +860,7 @@ const mapStateToProps = (state: AppState) => {
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && state.mustAuthenticate,
toast: state.toast,
shouldSwitchToAppleSiliconVersion: shim.isAppleSilicon() && process.arch !== 'arm64',
noteHtmlToMarkdownDone: state.noteHtmlToMarkdownDone,
};
};

View File

@ -803,6 +803,7 @@ function useMenu(props: Props) {
menuItemDic.toggleNoteList,
menuItemDic.toggleVisiblePanes,
menuItemDic.toggleEditorPlugin,
menuItemDic.toggleEditors,
{
label: _('Layout button sequence'),
submenu: layoutButtonSequenceMenuItems,
@ -906,6 +907,7 @@ function useMenu(props: Props) {
separator(),
menuItemDic.setTags,
menuItemDic.showShareNoteDialog,
menuItemDic.convertNoteToMarkdown,
separator(),
menuItemDic.showNoteProperties,
menuItemDic.showNoteContentProperties,

View File

@ -67,6 +67,11 @@ import 'codemirror/mode/diff/diff';
import 'codemirror/mode/erlang/erlang';
import 'codemirror/mode/sql/sql';
interface ExtendedWindow {
CodeMirror?: unknown;
}
declare const window: ExtendedWindow;
export interface EditorProps {
value: string;
@ -100,6 +105,14 @@ function Editor(props: EditorProps, ref: any) {
const editorParent = useRef(null);
const lastEditTime = useRef(NaN);
useEffect(() => {
window.CodeMirror = CodeMirror;
return () => {
window.CodeMirror = undefined;
};
}, []);
// Codemirror plugins add new commands to codemirror (or change it's behavior)
// This command adds the smartListIndent function which will be bound to tab
useListIdent(CodeMirror);

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import bridge from '../../../../../../services/bridge';
import { contentScriptsToCodeMirrorPlugin } from '@joplin/lib/services/plugins/utils/loadContentScripts';
import { extname } from 'path';
import shim from '@joplin/lib/shim';
@ -7,6 +8,18 @@ import uuid from '@joplin/lib/uuid';
import { reg } from '@joplin/lib/registry';
const addPluginDependency = (path: string) => {
const id = `content-script-${encodeURIComponent(path)}`;
if (document.getElementById(id)) {
return;
}
const element = document.createElement('script');
element.setAttribute('id', id);
element.setAttribute('src', path);
document.head.appendChild(element);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
export default function useExternalPlugins(CodeMirror: any, plugins: PluginStates) {
const [options, setOptions] = useState({});
@ -23,7 +36,14 @@ export default function useExternalPlugins(CodeMirror: any, plugins: PluginState
if (mod.codeMirrorResources) {
for (const asset of mod.codeMirrorResources) {
try {
require(`codemirror/${asset}`);
let assetPath = shim.fsDriver().resolveRelativePathWithinDir(`${bridge().vendorDir()}/lib/codemirror/`, asset);
// Compatibility with old versions of Joplin, where the file extension was automatically added by require().
if (extname(assetPath) === '') {
assetPath += '.js';
}
addPluginDependency(assetPath);
} catch (error) {
error.message = `${asset} is not a valid CodeMirror asset, keymap or mode. You can find a list of valid assets here: https://codemirror.net/doc/manual.html#addons`;
throw error;

View File

@ -30,6 +30,7 @@ import useEditorSearchHandler from '../utils/useEditorSearchHandler';
import CommandService from '@joplin/lib/services/CommandService';
import useRefocusOnVisiblePaneChange from './utils/useRefocusOnVisiblePaneChange';
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
import eventManager, { EventName, ResourceChangeEvent } from '@joplin/lib/eventManager';
const logger = Logger.create('CodeMirror6');
const logDebug = (message: string) => logger.debug(message);
@ -272,6 +273,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
props.noteId, props.useCustomPdfViewer,
]);
useEffect(() => {
const listener = (event: ResourceChangeEvent) => {
editorRef.current?.onResourceChanged(event.id);
};
eventManager.on(EventName.ResourceChange, listener);
return () => {
eventManager.off(EventName.ResourceChange, listener);
};
}, [props.resourceInfos]);
useEffect(() => {
if (!webviewReady) return;
@ -340,6 +352,8 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
props.setShowLocalSearch(event.searchState.dialogVisible);
}
lastSearchState.current = event.searchState;
} else if (event.kind === EditorEventType.FollowLink) {
void CommandService.instance().execute('openItem', event.link);
}
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch]);
@ -362,6 +376,9 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
readOnly: props.disabled,
markdownMarkEnabled: Setting.value('markdown.plugin.mark'),
katexEnabled: Setting.value('markdown.plugin.katex'),
inlineRenderingEnabled: Setting.value('editor.inlineRendering'),
imageRenderingEnabled: Setting.value('editor.imageRendering'),
highlightActiveLine: Setting.value('editor.highlightActiveLine'),
themeData: {
...styles.globalTheme,
marginLeft: 0,
@ -410,6 +427,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
onSelectPastBeginning={onSelectPastBeginning}
externalSearch={props.searchMarkers}
useLocalSearch={props.useLocalSearch}
onLocalize={_}
/>
</div>
);

View File

@ -15,6 +15,10 @@ import useEditorSearch from '../utils/useEditorSearchExtension';
import CommandService from '@joplin/lib/services/CommandService';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import localisation from './utils/localisation';
import Resource from '@joplin/lib/models/Resource';
import { parseResourceUrl } from '@joplin/lib/urlUtils';
import { resourceFilename } from '@joplin/lib/models/utils/resourceUtils';
import getResourceBaseUrl from '../../../utils/getResourceBaseUrl';
interface Props extends EditorProps {
style: React.CSSProperties;
@ -104,7 +108,16 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
onLogMessage: message => onLogMessageRef.current(message),
};
const editor = createEditor(editorContainerRef.current, editorProps);
const editor = createEditor(editorContainerRef.current, {
...editorProps,
resolveImageSrc: async (src, reloadCounter) => {
const url = parseResourceUrl(src);
if (!url.itemId) return null;
const item = await Resource.load(url.itemId);
if (!item) return null;
return `${getResourceBaseUrl()}/${resourceFilename(item)}${reloadCounter ? `?r=${reloadCounter}` : ''}`;
},
});
editor.addStyles({
'.cm-scroller': { overflow: 'auto' },
'&.CodeMirror': {

View File

@ -1393,10 +1393,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
async function onCut(event: any) {
event.preventDefault();
const selectedContent = editor.selection.getContent();
copyHtmlToClipboard(selectedContent);
editor.insertContent('');
event.preventDefault();
onChangeHandler();
}
@ -1444,7 +1444,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
// `compositionend` means that a user has finished entering a Chinese
// (or other languages that require IME) character.
editor.on(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
editor.on(TinyMceEditorEvents.Cut, onCut);
editor.on(TinyMceEditorEvents.Cut, onCut, true);
editor.on(TinyMceEditorEvents.JoplinChange, onChangeHandler);
editor.on(TinyMceEditorEvents.Undo, onChangeHandler);
editor.on(TinyMceEditorEvents.Redo, onChangeHandler);

View File

@ -5,6 +5,7 @@ import shim from '@joplin/lib/shim';
const useLinkTooltips = (editor: Editor|null) => {
const resetModifiedTitles = useCallback(() => {
if (!editor) return;
for (const element of editor.getDoc().querySelectorAll('a[data-joplin-original-title]')) {
element.setAttribute('title', element.getAttribute('data-joplin-original-title') ?? '');
element.removeAttribute('data-joplin-original-title');

View File

@ -56,6 +56,7 @@ import useResourceUnwatcher from './utils/useResourceUnwatcher';
import StatusBar from './StatusBar';
import useVisiblePluginEditorViewIds from '@joplin/lib/hooks/plugins/useVisiblePluginEditorViewIds';
import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
import getResourceBaseUrl from './utils/getResourceBaseUrl';
const debounce = require('debounce');
@ -169,7 +170,7 @@ function NoteEditorContent(props: NoteEditorProps) {
const theme = themeStyle(options.themeId ? options.themeId : props.themeId);
const markupToHtml = markupLanguageUtils.newMarkupToHtml(props.plugins, {
resourceBaseUrl: `joplin-content://note-viewer/${Setting.value('resourceDir')}/`,
resourceBaseUrl: getResourceBaseUrl(),
customCss: props.customCss,
});
@ -466,6 +467,7 @@ function NoteEditorContent(props: NoteEditorProps) {
// It is currently used to remember pdf scroll position for each attachments of each note uniquely.
noteId: props.noteId,
watchedNoteFiles: props.watchedNoteFiles,
enableHtmlToMarkdownBanner: props.enableHtmlToMarkdownBanner,
};
let editor = null;
@ -488,6 +490,17 @@ function NoteEditorContent(props: NoteEditorProps) {
setShowRevisions(false);
}, []);
const onBannerConvertItToMarkdown = useCallback(async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
if (!props.selectedNoteIds || props.selectedNoteIds.length === 0) return;
await CommandService.instance().execute('convertNoteToMarkdown', props.selectedNoteIds[0]);
}, [props.selectedNoteIds]);
const onHideBannerConvertItToMarkdown = async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
Setting.setValue('editor.enableHtmlToMarkdownBanner', false);
};
const onBannerResourceClick = useCallback(async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
const resourceId = event.currentTarget.getAttribute('data-resource-id');
@ -632,9 +645,30 @@ function NoteEditorContent(props: NoteEditorProps) {
const theme = themeStyle(props.themeId);
function renderConvertHtmlToMarkdown(): React.ReactNode {
if (!props.enableHtmlToMarkdownBanner) return null;
const note = props.notes.find(n => n.id === props.selectedNoteIds[0]);
if (!note) return null;
if (note.markup_language !== MarkupLanguage.Html) return null;
return (
<div style={styles.resourceWatchBanner}>
<p style={styles.resourceWatchBannerLine}>
{_('This note is in HTML format. Convert it to Markdown to edit it more easily.')}
&nbsp;
<a href="#" style={styles.resourceWatchBannerAction} onClick={onBannerConvertItToMarkdown}>{`${_('Convert it')}`}</a>
{' / '}
<a href="#" style={styles.resourceWatchBannerAction} onClick={onHideBannerConvertItToMarkdown}>{_('Don\'t show this message again')}</a>
</p>
</div>
);
}
return (
<div style={styles.root} onDragOver={onDragOver} onDrop={onDrop} ref={containerRef}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{renderConvertHtmlToMarkdown()}
{renderResourceWatchingNotification()}
{renderResourceInSearchResultsNotification()}
<NoteTitleBar
@ -722,6 +756,7 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
syncUserId: state.settings['sync.userId'],
shareCacheSetting: state.settings['sync.shareCache'],
searchResults: state.searchResults,
enableHtmlToMarkdownBanner: state.settings['editor.enableHtmlToMarkdownBanner'],
};
};

View File

@ -4,7 +4,8 @@ import { AppState } from '../../../app.reducer';
import Setting from '@joplin/lib/models/Setting';
import BannerContent from './BannerContent';
import { _ } from '@joplin/lib/locale';
import bridge from '../../../services/bridge';
import onRichTextReadMoreLinkClick from '@joplin/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick';
import onRichTextDismissLinkClick from '@joplin/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick';
import { useMemo } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import PluginService from '@joplin/lib/services/plugins/PluginService';
@ -16,14 +17,6 @@ interface Props {
plugins: PluginStates;
}
const onRichTextDismissLinkClick = () => {
Setting.setValue('richTextBannerDismissed', true);
};
const onRichTextReadMoreLinkClick = () => {
void bridge().openExternal('https://joplinapp.org/help/apps/rich_text_editor');
};
const onSwitchToLegacyEditor = () => {
Setting.setValue('editor.legacyMarkdown', true);
};

View File

@ -69,6 +69,10 @@ export default function styles(props: NoteEditorProps) {
marginTop: 0,
marginBottom: 10,
},
resourceWatchBannerAction: {
textDecoration: 'underline',
color: theme.colorWarnUrl,
},
};
});
}

View File

@ -8,14 +8,15 @@ const MenuItem = bridge().MenuItem;
import Resource, { resourceOcrStatusToString } from '@joplin/lib/models/Resource';
import BaseItem from '@joplin/lib/models/BaseItem';
import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
import { NoteEntity, ResourceEntity, ResourceOcrStatus } from '@joplin/lib/services/database/types';
import { NoteEntity, ResourceEntity, ResourceOcrDriverId, ResourceOcrStatus } from '@joplin/lib/services/database/types';
import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types';
import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly';
import Setting from '@joplin/lib/models/Setting';
import ItemChange from '@joplin/lib/models/ItemChange';
import shim from '@joplin/lib/shim';
import shim, { MessageBoxType } from '@joplin/lib/shim';
import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils';
import CommandService from '@joplin/lib/services/CommandService';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
@ -137,6 +138,40 @@ export function menuItems(dispatch: Function): ContextMenuItems {
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
},
recognizeHandwrittenImage: {
label: _('Recognize handwritten image'),
onAction: async (options: ContextMenuOptions) => {
const syncTargetId = Setting.value('sync.target');
if (!SyncTargetRegistry.isJoplinServerOrCloud(syncTargetId)) {
await shim.showMessageBox(_('This feature is only available on Joplin Cloud and Joplin Server.'), { type: MessageBoxType.Error });
return;
}
if (!Setting.value('ocr.handwrittenTextDriverEnabled')) {
await shim.showMessageBox(_('This feature is disabled by default, you need to manually enable it by turning on the option to \'Enable handwritten transcription\'.'), { type: MessageBoxType.Error });
return;
}
const { resource } = await resourceInfo(options);
if (!['image/png', 'image/jpg', 'image/jpeg', 'image/bmp'].includes(resource.mime)) {
await shim.showMessageBox(_('This image type is not supported by the recognition system.'), { type: MessageBoxType.Error });
return;
}
await Resource.save({
id: resource.id,
ocr_status: ResourceOcrStatus.Todo,
ocr_driver_id: ResourceOcrDriverId.HandwrittenText,
ocr_details: '',
ocr_error: '',
ocr_text: '',
});
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => {
return itemType === ContextMenuItemType.Resource || (itemType === ContextMenuItemType.Image && options.resourceId);
},
},
revealInFolder: {
label: _('Reveal file in folder'),
onAction: async (options: ContextMenuOptions) => {

View File

@ -0,0 +1,4 @@
import Setting from '@joplin/lib/models/Setting';
const getResourceBaseUrl = () => `joplin-content://note-viewer/${Setting.value('resourceDir')}/`;
export default getResourceBaseUrl;

View File

@ -13,6 +13,7 @@ import { MarkupToHtmlOptions } from '../../hooks/useMarkupToHtml';
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
import { RefObject, SetStateAction } from 'react';
import * as React from 'react';
import { ResourceEntity, ResourceLocalStateEntity } from '@joplin/lib/services/database/types';
export interface AllAssetsOptions {
contentMaxWidthTarget?: string;
@ -67,6 +68,7 @@ export interface NoteEditorProps {
onTitleChange?: (title: string)=> void;
bodyEditor: string;
startupPluginsLoaded: boolean;
enableHtmlToMarkdownBanner: boolean;
}
export interface NoteBodyEditorRef {
@ -138,6 +140,7 @@ export interface NoteBodyEditorProps {
noteId: string;
useCustomPdfViewer: boolean;
watchedNoteFiles: string[];
enableHtmlToMarkdownBanner: boolean;
}
export interface NoteBodyEditorPropsAndRef extends NoteBodyEditorProps {
@ -212,10 +215,8 @@ export function defaultFormNote(): FormNote {
}
export interface ResourceInfo {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
localState: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
item: any;
localState: ResourceLocalStateEntity;
item: ResourceEntity;
}
export interface ResourceInfos {

View File

@ -49,7 +49,7 @@ const useScheduleSaveCallbacks = (props: Props) => {
}, [props.dispatch, props.editorId, props.setFormNote]);
const saveNoteIfWillChange = useCallback(async (formNote: FormNote) => {
if (!formNote.id || !formNote.bodyWillChangeId) return;
if (!formNote.id || !formNote.bodyWillChangeId || !props.editorRef.current) return;
const body = await props.editorRef.current.content();

View File

@ -343,6 +343,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
style={styles.input}
id={uniqueId(key)}
name={uniqueId(key)}
autoFocus
/>;
editCompHandler = () => {
@ -363,6 +364,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
id={uniqueId(key)}
name={uniqueId(key)}
aria-invalid={!this.state.isValid.location}
autoFocus
/>
{
this.state.isValid.location ? null
@ -387,6 +389,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
style={styles.input}
id={uniqueId(key)}
name={uniqueId(key)}
autoFocus
/>
);
}

View File

@ -8,6 +8,7 @@ import { focus } from '@joplin/lib/utils/focusHandler';
import Dialog from './Dialog';
import { ChangeEvent } from 'react';
import { formatDateTimeLocalToMs, isValidDate } from '@joplin/utils/time';
import lightTheme from '@joplin/lib/themes/light';
interface Props {
themeId: number;
@ -117,6 +118,15 @@ export default class PromptDialog extends React.Component<Props, any> {
borderColor: theme.dividerColor,
};
// The button to change the date/time cannot be customized easily so we need to use the
// light theme for that particular component.
this.styles_.dateTimeInput = {
...this.styles_.input,
color: lightTheme.color,
backgroundColor: lightTheme.backgroundColor,
borderColor: lightTheme.dividerColor,
};
this.styles_.select = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
control: (provided: any) => {
@ -241,8 +251,6 @@ export default class PromptDialog extends React.Component<Props, any> {
} else {
onClose(true);
}
} else if (event.key === 'Escape') {
onClose(false);
}
};
@ -256,7 +264,7 @@ export default class PromptDialog extends React.Component<Props, any> {
onChange={onChange}
type="datetime-local"
className='datetime-picker'
style={styles.input}
style={styles.dateTimeInput}
/>;
} else if (this.props.inputType === 'tags') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@ -299,7 +307,7 @@ export default class PromptDialog extends React.Component<Props, any> {
}
return (
<Dialog className='prompt-dialog' contentStyle={styles.dialog}>
<Dialog className='prompt-dialog' contentStyle={styles.dialog} onCancel={() => onClose(false, 'cancel')}>
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
<div style={{ display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor }}>
{inputComp}

View File

@ -27,7 +27,7 @@ const CollapseExpandAllButton = (props: CollapseExpandAllButtonProps) => {
const icon = props.allFoldersCollapsed ? 'far fa-caret-square-right' : 'far fa-caret-square-down';
const label = props.allFoldersCollapsed ? _('Expand all notebooks') : _('Collapse all notebooks');
return <button onClick={() => onToggleAllFolders(props.allFoldersCollapsed)} className='sidebar-header-button -collapseall'>
return <button onClick={() => onToggleAllFolders(props.allFoldersCollapsed)} className='sidebar-header-button -collapseall' title={label}>
<i
aria-label={label}
role='img'
@ -39,9 +39,11 @@ const CollapseExpandAllButton = (props: CollapseExpandAllButtonProps) => {
const NewFolderButton = () => {
// To allow it to be accessed by accessibility tools, the new folder button
// is not included in the portion of the list with role='tree'.
return <button onClick={onAddFolderButtonClick} className='sidebar-header-button -newfolder'>
const label = _('New notebook');
return <button onClick={onAddFolderButtonClick} className='sidebar-header-button -newfolder' title={label}>
<i
aria-label={_('New notebook')}
aria-label={label}
role='img'
className='fas fa-plus'
/>

View File

@ -79,5 +79,7 @@ export default function() {
'switchProfile3',
'pasteAsText',
'showNoteProperties',
'convertNoteToMarkdown',
'toggleEditors',
];
}

View File

@ -72,4 +72,10 @@ export default class MainScreen {
await setFilePickerResponse(electronApp, [path]);
await activateMainMenuItem(electronApp, 'HTML - HTML document (Directory)', 'Import');
}
public async pluginPanelLocator(pluginId: string) {
return this.page.locator(
`iframe[id^=${JSON.stringify(`plugin-view-${pluginId}`)}]`,
);
}
}

View File

@ -45,6 +45,41 @@ test.describe('pluginApi', () => {
}));
});
test('should report the correct visibility state for dialogs', async ({ startAppWithPlugins }) => {
const { app, mainWindow } = await startAppWithPlugins(['resources/test-plugins/dialogs.js']);
const mainScreen = await new MainScreen(mainWindow).setup();
await mainScreen.createNewNote('Dialog test note');
const editor = mainScreen.noteEditor;
const expectVisible = async (visible: boolean) => {
// Check UI visibility
if (visible) {
await expect(mainScreen.dialog).toBeVisible();
} else {
await expect(mainScreen.dialog).not.toBeVisible();
}
// Check visibility reported through the plugin API
await expect.poll(async () => {
await mainScreen.goToAnything.runCommand(app, 'getTestDialogVisibility');
const editorContent = await editor.contentLocator();
return editorContent.textContent();
}).toBe(JSON.stringify({
visible: visible,
active: visible,
}));
};
await expectVisible(false);
await mainScreen.goToAnything.runCommand(app, 'showTestDialog');
await expectVisible(true);
// Submitting the dialog should include form data in the output
await mainScreen.dialog.getByRole('button', { name: 'Okay' }).click();
await expectVisible(false);
});
test('should be possible to create multiple toasts with the same text from a plugin', async ({ startAppWithPlugins }) => {
const { app, mainWindow } = await startAppWithPlugins(['resources/test-plugins/showToast.js']);
const mainScreen = await new MainScreen(mainWindow).setup();
@ -122,5 +157,30 @@ test.describe('pluginApi', () => {
await msleep(Second);
await expect(noteEditor.codeMirrorEditor).toHaveText(expectedUpdatedText);
});
test('should support hiding and showing panels', async ({ startAppWithPlugins }) => {
const { mainWindow, app } = await startAppWithPlugins(['resources/test-plugins/panels.js']);
const mainScreen = await new MainScreen(mainWindow).setup();
await mainScreen.createNewNote('Test note (panels)');
const panelLocator = await mainScreen.pluginPanelLocator('org.joplinapp.plugins.example.panels');
const noteEditor = mainScreen.noteEditor;
await mainScreen.goToAnything.runCommand(app, 'testShowPanel');
await expect(noteEditor.codeMirrorEditor).toHaveText('visible');
// Panel should be visible
await expect(panelLocator).toBeVisible();
// The panel should have the expected content
const panelContent = panelLocator.contentFrame();
await expect(
panelContent.getByRole('heading', { name: 'Panel content' }),
).toBeAttached();
await mainScreen.goToAnything.runCommand(app, 'testHidePanel');
await expect(noteEditor.codeMirrorEditor).toHaveText('hidden');
await expect(panelLocator).not.toBeVisible();
});
});

View File

@ -47,5 +47,22 @@ joplin.plugins.register({
}));
},
});
await joplin.commands.register({
name: 'getTestDialogVisibility',
label: 'Returns the dialog visibility state',
execute: async () => {
// panels.visible should also work for dialogs.
const visible = await joplin.views.panels.visible(dialogHandle);
// For dialogs, isActive should return the visibility.
// (Prefer panels.visible for dialogs).
const active = await joplin.views.panels.isActive(dialogHandle);
await joplin.commands.execute('editor.setText', JSON.stringify({
visible,
active,
}));
},
});
},
});

View File

@ -0,0 +1,71 @@
// Allows referencing the Joplin global:
/* eslint-disable no-undef */
// Allows the `joplin-manifest` block comment:
/* eslint-disable multiline-comment-style */
/* joplin-manifest:
{
"id": "org.joplinapp.plugins.example.panels",
"manifest_version": 1,
"app_min_version": "3.1",
"name": "JS Bundle test",
"description": "JS Bundle Test plugin",
"version": "1.0.0",
"author": "",
"homepage_url": "https://joplinapp.org"
}
*/
const waitFor = async (condition) => {
const wait = () => {
return new Promise(resolve => {
setTimeout(() => resolve(), 100);
});
};
for (let i = 0; i < 100; i++) {
if (await condition()) {
return;
}
// Pause for a brief delay
await wait();
}
throw new Error('Condition was never true');
};
joplin.plugins.register({
onStart: async function() {
const panels = joplin.views.panels;
const view = await panels.create('panelTestView');
await panels.setHtml(view, '<h1>Panel content</h1><p>Test</p>');
await panels.hide(view);
await joplin.commands.register({
name: 'testShowPanel',
label: 'Test panel visibility',
execute: async () => {
await panels.show(view);
await waitFor(async () => {
return await panels.visible(view);
});
await joplin.commands.execute('editor.setText', 'visible');
},
});
await joplin.commands.register({
name: 'testHidePanel',
label: 'Test: Hide the panel',
execute: async () => {
await panels.hide(view);
await waitFor(async () => {
return !await panels.visible(view);
});
await joplin.commands.execute('editor.setText', 'hidden');
},
});
},
});

View File

@ -31,6 +31,7 @@ import FileApiDriverLocal from '@joplin/lib/file-api-driver-local';
import * as React from 'react';
import nodeSqlite = require('sqlite3');
import initLib from '@joplin/lib/initLib';
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
const pdfJs = require('pdfjs-dist');
const { isAppleSilicon } = require('is-apple-silicon');
require('@sentry/electron/renderer');
@ -38,6 +39,8 @@ require('@sentry/electron/renderer');
// Allows components to use React as a global
window.React = React;
const perfLogger = PerformanceLogger.create();
const main = async () => {
// eslint-disable-next-line no-console
@ -106,7 +109,7 @@ const main = async () => {
}
};
main().catch((error) => {
perfLogger.track('main', main).catch((error) => {
const env = bridge().env();
console.error(error);

View File

@ -53,6 +53,10 @@ const { rootProfileDir } = determineBaseAppDirs(profileFromArgs, appName, altIns
// various places early in the initialisation code.
mkdirpSync(rootProfileDir);
// Required for correct display of Windows notifications. Should be done near the beginning of startup. See
// https://www.electron.build/nsis.html#guid-vs-application-name
electronApp.setAppUserModelId(appId);
const settingsPath = `${rootProfileDir}/settings.json`;
let autoUploadCrashDumps = false;

View File

@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.4.2",
"version": "3.4.11",
"description": "Joplin for Desktop",
"main": "main.bundle.js",
"private": true,
@ -131,9 +131,9 @@
"homepage": "https://github.com/laurent22/joplin#readme",
"devDependencies": {
"7zip-bin": "5.2.0",
"@axe-core/playwright": "4.10.1",
"@axe-core/playwright": "4.10.2",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.1",
"@electron/rebuild": "3.7.2",
"@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4",
"@joplin/default-plugins": "~3.4",
@ -142,14 +142,14 @@
"@joplin/renderer": "~3.4",
"@joplin/tools": "~3.4",
"@joplin/utils": "~3.4",
"@playwright/test": "1.51.1",
"@playwright/test": "1.52.0",
"@sentry/electron": "4.24.0",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.12",
"@types/mustache": "4.2.5",
"@types/node": "18.19.86",
"@types/react": "18.3.20",
"@types/react-dom": "18.3.6",
"@types/jest": "29.5.14",
"@types/mustache": "4.2.6",
"@types/node": "18.19.112",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
"@types/react-redux": "7.1.33",
"@types/styled-components": "5.1.32",
"@types/tesseract.js": "2.0.0",
@ -160,13 +160,13 @@
"compare-versions": "6.1.1",
"countable": "3.0.1",
"debounce": "1.2.1",
"electron": "35.5.1",
"electron": "37.4.0",
"electron-builder": "24.13.3",
"electron-updater": "6.6.0",
"electron-updater": "6.6.2",
"electron-window-state": "5.0.3",
"esbuild": "^0.25.3",
"formatcoords": "1.1.3",
"glob": "11.0.2",
"glob": "11.0.3",
"gulp": "4.0.2",
"highlight.js": "11.11.1",
"immer": "9.0.21",
@ -179,7 +179,6 @@
"moment": "2.30.1",
"mustache": "4.2.0",
"nan": "2.22.2",
"node-fetch": "2.6.7",
"node-notifier": "10.0.1",
"node-rsa": "1.1.1",
"pdfjs-dist": "3.11.174",
@ -202,15 +201,16 @@
"taboverride": "4.0.3",
"tesseract.js": "5.1.1",
"tinymce": "6.8.5",
"ts-jest": "29.1.5",
"ts-jest": "29.3.1",
"ts-node": "10.9.2",
"typescript": "5.4.5"
"typescript": "5.8.2"
},
"dependencies": {
"@electron/remote": "2.1.2",
"@joplin/onenote-converter": "~3.4",
"fs-extra": "11.2.0",
"keytar": "7.9.0",
"node-fetch": "2.6.7",
"sqlite3": "5.1.6"
}
}

View File

@ -345,8 +345,8 @@ class DialogComponent extends React.PureComponent<Props, State> {
return {
id: result.commandName,
title: result.title,
parent_id: null,
fields: [],
parent_id: null as string,
fields: [] as string[],
type: BaseModel.TYPE_COMMAND,
};
});

View File

@ -51,10 +51,8 @@
const modulePath = args && args.length ? args[0] : null;
if (!modulePath) throw new Error('No module path specified on `require` call');
// The sqlite3 is actually part of the lib package so we need to do
// something convoluted to get it working.
if (modulePath === 'sqlite3') {
return require('../../node_modules/@joplin/lib/node_modules/sqlite3/lib/sqlite3.js');
return require('sqlite3');
}
if (modulePath === 'fs-extra') {

View File

@ -30,7 +30,7 @@ const makeBuildContext = (entryPoint: string, renderer: boolean, computeFileSize
// in the final bundle.
name: 'joplin--relative-imports-for-externals',
setup: build => {
const externalRegex = /^(.*\.node|sqlite3|electron|@electron\/remote\/.*|electron\/.*|@mapbox\/node-pre-gyp|jsdom)$/;
const externalRegex = /^(.*\.node|sqlite3|node-fetch|electron|@electron\/remote\/.*|electron\/.*|@mapbox\/node-pre-gyp|jsdom)$/;
build.onResolve({ filter: externalRegex }, args => {
// Electron packages don't need relative requires
if (args.path === 'electron' || args.path.startsWith('electron/')) {

View File

@ -82,7 +82,7 @@ async function main() {
const files = [
'@fortawesome/fontawesome-free/css/all.min.css',
'@joeattardi/emoji-button/dist/index.js',
'codemirror/addon/dialog/dialog.css',
'codemirror/addon/',
'codemirror/lib/codemirror.css',
'mark.js/dist/mark.min.js',
'roboto-fontface/css/roboto/roboto-fontface.css',

View File

@ -25,7 +25,7 @@ async function main() {
// wrong one. However it means it will have to be manually upgraded for each
// new Electron release. Some ABI map there:
// https://github.com/electron/node-abi/tree/master/test
const forceAbiArgs = '--force-abi 134';
const forceAbiArgs = '--force-abi 138';
if (isWindows()) {
// Cannot run this in parallel, or the 64-bit version might end up

View File

@ -67,7 +67,8 @@ yarn-error.log
lib/csstojs/
lib/rnInjectedJs/
dist/
components/**/*.bundle.js
/**/*.bundle.js
/**/*.bundle.css
components/**/*.bundle.js.LICENSE.txt
components/**/*.bundle.js.md5
components/**/*.bundle.min.js

View File

@ -89,8 +89,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097774
versionName "3.4.1"
versionCode 2097780
versionName "3.4.7"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
@ -100,6 +100,8 @@ android {
externalNativeBuild {
cmake {
cppFlags '-DCMAKE_BUILD_TYPE=Release'
// For 16 KB pages. This should be removable after upgrading to NDK r28
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
}

View File

@ -38,8 +38,9 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
set(WHISPER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../../../vendor/whisper.cpp)
# Based on the Whisper.cpp Android example:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections")
set(SHARED_FLAGS "-O3 ")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SHARED_FLAGS} ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SHARED_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections")
# Whisper: See https://stackoverflow.com/a/76290722
add_subdirectory(${WHISPER_LIB_DIR} ./whisper)

View File

@ -6,6 +6,12 @@ import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
class MainActivity : ReactActivity() {
/**
@ -20,4 +26,25 @@ class MainActivity : ReactActivity() {
*/
override fun createReactActivityDelegate(): ReactActivityDelegate =
ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled))
/**
* This is a workaround to fix the upstream issue https://github.com/facebook/react-native/issues/49759#issuecomment-2918934967
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= 35) {
val rootView = findViewById<View>(android.R.id.content)
ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets ->
val innerPadding = insets.getInsets(WindowInsetsCompat.Type.ime())
rootView.setPadding(
innerPadding.left,
innerPadding.top,
innerPadding.right,
innerPadding.bottom
)
insets
}
}
}
}

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