Compare commits

...
Author SHA1 Message Date
Laurent Cozic 636fbdf7d0 Android 3.0.8 2024-07-06 13:21:55 +02:00
Laurent Cozic ee97434bb0 Tools: Skip more minor changes for changelog generation 2024-07-06 12:19:17 +02:00
Laurent Cozic 599cf5b86f Desktop release v3.0.13 2024-07-06 12:12:46 +02:00
Henry Heino 2fd6a3a2fa Desktop: Fixes #10685: Fix shift-delete asks to permanently delete the current note, rather than cut text, when the editor is selected. (#10687) 2024-07-06 12:05:35 +02:00
Henry Heino a3e04103de Desktop: Fixes #10679: Fix incorrect text rendering on MacOS by changing the default font to Avenir Next (#10686) 2024-07-05 19:58:09 +02:00
Henry Heino 320d0df60d Desktop, Mobile: Fixes #10674: Fix sidebar performance regression with many nested notebooks (#10676) 2024-07-04 14:56:57 +02:00
cedecode f32fe63205 All: Update de_DE.po (small improvements) (#10673) 2024-07-03 13:53:32 +02:00
Laurent Cozic 3e0fb48e44 Api: Do not return deleted notes in folders/:id/notes call 2024-07-03 10:41:31 +02:00
Laurent Cozic 4a475f1b53 CLI v3.0.1 2024-07-02 20:58:38 +02:00
Laurent Cozic 8679cc5704 Lock file 2024-07-02 20:41:46 +02:00
Laurent Cozic a48c4ba93f Releasing sub-packages 2024-07-02 20:40:42 +02:00
Laurent Cozic 16e82b5462 iOS 13.0.5 2024-07-01 17:48:06 +02:00
Laurent Cozic 1a82255865 Android 3.0.7 2024-07-01 17:47:33 +02:00
Laurent Cozic a64d6e3270 Desktop release v3.0.12 2024-07-01 17:40:41 +02:00
Laurent Cozic ae1620dd50 Update translations 2024-07-01 17:39:00 +02:00
rnbastos d804e95d06 Update pt_BR.po (#10667) 2024-07-01 17:33:36 +02:00
pedr 78d9a7e636 Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore (#10649) 2024-07-01 17:31:58 +02:00
Laurent Cozic 3270122419 Fixed tests 2024-06-29 18:36:43 +02:00
Laurent Cozic e4b8976aa0 All: Set min version for synchronising to 3.0.0 2024-06-29 12:29:41 +02:00
Laurent Cozic 17e1eecb11 iOS 13.0.4 2024-06-29 12:21:51 +02:00
Laurent Cozic dd9a389711 Android 3.0.6 2024-06-29 12:20:36 +02:00
Laurent Cozic 55eaedb8b2 Desktop release v3.0.11 2024-06-29 11:34:25 +02:00
cedecode f8d772de87 All: Update de_DE.po (#10660) 2024-06-27 11:43:26 +02:00
qx100 77c39ac084 Translate Update zh_CN.po (#10659) 2024-06-26 18:39:32 +02:00
Henry Heino 3557138c84 iOS: Fix voice typing URL setting incorrectly visible (#10643) 2024-06-21 17:16:25 +01:00
Henry Heino 77e74112ad Mobile: Fixes #10637: Fix refocusing the note editor (#10644) 2024-06-21 17:16:12 +01:00
Laurent Cozic 4115e2054f lock file 2024-06-19 16:33:21 +01:00
Laurent Cozic 8485277dcf iOS 13.0.3 2024-06-19 16:29:43 +01:00
Laurent Cozic fac9ea3b42 Android 3.0.5 2024-06-19 16:28:45 +01:00
Laurent Cozic 45f8e27d6a Desktop release v3.0.10 2024-06-19 12:38:09 +01:00
Laurent Cozic c8a478d970 Update translations 2024-06-19 12:37:57 +01:00
pedr 75dfb0af5f Mobile: Fixes #10252 Not able to change notebook when using 'New Note' quick action (#10588) 2024-06-19 12:33:22 +01:00
Mohammad Ashouri 5e592a3096 All: Update farsi/persian translation fa.po (#10634) 2024-06-19 11:00:31 +01:00
Laurent Cozic 84e46ad874 Updating farsi/persian translation (by mimeyn) 2024-06-19 10:59:51 +01:00
XPhyro 0ec917bb96 All: Don't render empty title page for Fountain (#10631) 2024-06-19 10:55:05 +01:00
Henry Heino 818f9f58d1 Desktop: Fixes #10061: Fix paste adds newlines in the Rich Text Editor when certain plugins are enabled (#10635) 2024-06-19 10:54:34 +01:00
renovate[bot] e1abe0b4cb Update contributor-assistant/github-action action to v2.3.2 (#10633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 10:46:09 +01:00
Henry HeinoandLaurent Cozic c972ce223e Mobile: Fixes #10593: Fix plugin list not cached in config screen (#10599)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-18 10:02:42 +01:00
Henry Heino d9dadf28cb Mobile: Resolves #10594: Move mobile plugin setting tabs under a separate section (#10600) 2024-06-18 10:02:11 +01:00
Henry Heino 1fb392ff4e Mobile: Fix cmd-i no longer italicizes text (#10604) 2024-06-18 10:02:01 +01:00
pedr affa620983 Desktop: Fixes: #10030: Prevent application from crashing when the syncInfoCache is corrupted (#10546) 2024-06-18 10:01:35 +01:00
Henry Heino ed31d8202b Mobile: Fixes #10589: Fix selected note changes on moving to a different folder (#10630) 2024-06-18 09:59:08 +01:00
renovate[bot] 573ea6051c Update dependency @react-native-community/netinfo to v11.3.1 (#10632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 23:20:07 +01:00
renovate[bot] f1ec54532f Update dependency jsdom to v23 (#10628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:25:40 +01:00
renovate[bot] 5eb96d71e1 Update dependency sass to v1.71.0 (#10627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:56 +01:00
renovate[bot] 73251bac4a Update dependency react-native-webview to v13.8.1 (#10626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:50 +01:00
renovate[bot] f40a0da195 Update dependency css-loader to v6.10.0 (#10625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:24:34 +01:00
Laurent Cozic 8dc1ab2cc5 Update renovate.json5 2024-06-17 21:23:55 +01:00
renovate[bot] 1b46c9f5e7 Update dependency chokidar to v3.6.0 (#10623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:23:07 +01:00
renovate[bot] 483ab55a36 Update dependency @react-native-community/netinfo to v11.3.0 (#10622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:56 +01:00
renovate[bot] 502002f9f6 Update dependency style-to-js to v1.1.11 (#10619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:16 +01:00
renovate[bot] 8d8014511f Update dependency turndown to v7.1.3 (#10620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:22:09 +01:00
renovate[bot] 28569e652e Update dependency react-native-image-picker to v7.1.1 (#10618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:54 +01:00
renovate[bot] 9acf36d802 Update dependency nodemailer to v6.9.12 (#10617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:45 +01:00
renovate[bot] 06d26767ed Update dependency koa to v2.15.1 (#10615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:26 +01:00
renovate[bot] 88858d4413 Update dependency follow-redirects to v1.15.6 (#10614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:21:13 +01:00
github-actions[bot] 27309427a1 @XPhyro has signed the CLA in laurent22/joplin#10631 2024-06-17 19:50:09 +00:00
renovate[bot] e83a18a907 Update dependency @types/node to v18.19.26 (#10613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 14:25:46 +00:00
ERYpTION 9bd8b11f67 Update da_DK.po (#10606) 2024-06-17 14:50:34 +01:00
renovate[bot] 3a14c7ce2d Update dependency @types/node to v18.19.25 (#10610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 09:13:03 +01:00
github-actions[bot] fe4c9a2401 @kohi9noor has signed the CLA in laurent22/joplin#10607 2024-06-16 08:42:17 +00:00
Arda Kılıçdağı d2fb19cf6d All: Translation: Update tr_TR.po (#10603) 2024-06-15 14:35:15 -04:00
Laurent Cozic 1f8e3fb620 Update translations 2024-06-15 10:17:54 +01:00
Laurent Cozic 8bbe1d30b4 Update French translation 2024-06-15 10:16:16 +01:00
Laurent Cozic ec92f716de Chore: Removed unncessary string 2024-06-15 10:15:22 +01:00
Laurent Cozic ab819d9210 Update translations 2024-06-15 10:06:44 +01:00
Henry Heino e465b45d6e Mobile: Resolves #10592: Make mobile plugin settings screen UI closer to desktop (#10598) 2024-06-15 10:00:21 +01:00
tiberiustengandTib Teng a4a4170d49 Mobile: Resolves #8639: implement callback url (#9803)
Co-authored-by: Tib Teng <661892+tiberiusteng@users.noreply.github.com>
2024-06-15 09:58:23 +01:00
Henry Heino 1dcf528443 Chore: Refactor editor package: Move functions in editorStateUtils into separate files (#10591) 2024-06-14 19:40:07 +01:00
Henry Heino 8cf4ef88b5 Desktop: Fixes #10586: Don't re-order the note list when in search (#10587) 2024-06-14 19:39:26 +01:00
Henry Heino bf634270be Mobile: Mark plugin support as in beta (#10585) 2024-06-14 19:38:50 +01:00
Henry HeinoandLaurent Cozic 56437d3e1b Mobile: Don't show an "expand" arrow by "Installed plugins" when no plugins are installed (#10583)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:38:16 +01:00
Henry HeinoandLaurent Cozic d095ab2be7 All: Fixes #10581: English: Use the plural form of a localization for negative and zero items (#10582)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:37:26 +01:00
Henry HeinoandLaurent Cozic 4751b4dd74 Mobile: Plugin settings screen: Fix plugin states not set correctly when installing multiple plugins at once (#10580)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:36:44 +01:00
Henry HeinoandLaurent Cozic ce22d8238c Mobile: Plugin settings: Fix plugins without settings can't be disabled without reinstall (#10579)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-06-14 19:36:26 +01:00
pedr 9e2b9e5b8d Desktop: Fixes error when Joplin Cloud login is finished before the settings are saved (#10575) 2024-06-14 19:31:50 +01:00
pedr 4952980e0a Desktop, Mobile: Start synchronisation just after login is complete (#10574) 2024-06-14 14:24:35 +01:00
Joplin Bot 59989d2735 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-13 12:22:09 +00:00
Laurent Cozic eb7f2855b0 Chore: Trying to fix Android build on CI by using debug keys 2024-06-13 09:01:45 +01:00
Laurent Cozic a0e3e4fefb Android 3.0.4 2024-06-12 21:41:20 +01:00
Laurent Cozic de9661448b Chore: Fix Android build for RN 0.74.1 (#10577) 2024-06-12 21:33:44 +01:00
Laurent Cozic 562aabafa1 iOS 13.0.2 2024-06-12 21:29:29 +01:00
Laurent Cozic a659e45a68 lock files 2024-06-12 21:20:59 +01:00
Laurent Cozic 8e44a15c8d CI: Assemble Android release instead of debug (#10576) 2024-06-12 21:16:18 +01:00
Laurent Cozic a086358824 Desktop release v3.0.9 2024-06-12 18:31:57 +01:00
pedr 889c395818 Desktop, Mobile: Add Joplin Cloud account information to configuration screen (#10553) 2024-06-12 15:14:32 +01:00
Henry Heino 7d0cc675aa Desktop: Fixes #10236: Fix note disappears while editing during search (#10568) 2024-06-12 15:11:53 +01:00
pedr 73d3f92ae2 Desktop, Mobile: Add button on Synchronization to Joplin Cloud login screen (#10569) 2024-06-12 15:11:30 +01:00
Henry Heino f5ceb4064c Mobile,Desktop: Upgrade KaTeX to v0.16.10 (#10570) 2024-06-12 15:10:19 +01:00
pedr 53d7bc86ca Desktop, Mobile: Hide links to login after process is successful (#10571) 2024-06-12 15:09:52 +01:00
renovate[bot] 4495fc9a03 Update dependency @types/node to v18.19.24 (#10572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-12 15:08:38 +01:00
Henry Heino 7ee5cad21e Chore: Mobile: Increase test timeout to try to fix CI failure (#10552) 2024-06-11 07:49:57 +01:00
Henry Heino 940739ce12 Desktop: Fixes #10199: Rich text editor: Include "ctrl-click to open" in link tooltips (#10547) 2024-06-11 07:49:28 +01:00
Henry Heino 5730c1efcd Mobile: Settings screen: Show touch feedback when pressing a tab (#10544) 2024-06-11 07:41:50 +01:00
Henry Heino b17f28ce94 Desktop: Fixes #10538: Fix wrong text selected when adding a link in the beta editor (#10542) 2024-06-11 07:41:41 +01:00
Henry Heino 629e968878 Windows: Fixes #10525: Prevent notes with titles that differ only in case from being overwritten when exporting (#10541) 2024-06-11 07:41:23 +01:00
Henry Heino 47a924ff4e Mobile: Fix plugins not reloaded when the plugin runner reloads (#10540) 2024-06-11 07:40:36 +01:00
Henry Heino c511fb59c7 Desktop: Fixes #10551: Watch resources for changes when opened from the Rich Text Editor (#10554) 2024-06-10 22:31:38 +01:00
Henry Heino 80aeff6ecd Mobile: Dismiss dialogs on background tap (#10557) 2024-06-10 22:31:06 +01:00
Henry Heino 97d15bb26a Desktop: Rich Text Editor: Allow toggling bulleted and numbered lists from the command palette (#10559) 2024-06-10 22:26:14 +01:00
renovate[bot] 99b36cbff1 Update dependency @types/node to v18.19.23 (#10555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-10 14:11:06 +00:00
Helmut K. C. Tessarek 74c3d2c9fb Update translations 2024-06-07 14:27:47 -04:00
Helmut K. C. Tessarek df8c7fd31c All: Translation: Update ko.po 2024-06-07 14:16:43 -04:00
renovate[bot] 3a780b9490 Update dependency @types/node to v18.19.22 (#10543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-06 11:44:32 +00:00
github-actions[bot] 97ddb67f68 @tushar-coin has signed the CLA in laurent22/joplin#10539 2024-06-04 18:39:24 +00:00
Joplin Bot f50a27985b Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-04 12:21:58 +00:00
Laurent Cozic 061a9d5bff Doc: Add sponsor 2024-06-04 11:22:27 +01:00
Henry Heino 06f42e8246 Mobile: Implement plugin screen redesign (#10465) 2024-06-04 09:57:52 +01:00
pedr 19f0b667b1 Server: Fixes #10118: Missing record validation before trying to add item to user (#10471) 2024-06-04 09:55:57 +01:00
Henry Heino ac7165461a Chore: AsyncActionQueue: Support changing which tasks can be skipped (#10506) 2024-06-04 09:54:06 +01:00
Henry Heino efb48e6145 Mobile: Show WebView version in settings (#10518) 2024-06-04 09:53:49 +01:00
Henry Heino f94c16b22e Mobile: Plugin API: Implement the newNote command (#10524) 2024-06-04 09:52:52 +01:00
Henry Heino 0938dc9d52 Desktop: Fix importing completed tasks (#10528) 2024-06-04 09:52:34 +01:00
Henry Heino e049698012 Desktop: Beta editor: Fix crash when switching between notes that use CRLF line endings (#10531) 2024-06-04 09:52:09 +01:00
Henry Heino c9fb06fd0c Desktop: Fixes #10416: Work around checkbox toggle broken when editor is hidden (#10534) 2024-06-04 09:51:59 +01:00
Henry Heino 96850b7b98 Chore: Migrate mime-utils.js to TypeScript (#10536) 2024-06-04 09:50:18 +01:00
Laurent Cozic 32710e44c3 Desktop: Significantly reduces size of exported HTML files in most cases 2024-06-03 23:50:51 +01:00
Joplin Bot 32e16f6e51 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-06-01 00:43:45 +00:00
Henry HeinoandLaurent Cozic f938d5f489 Chore: Markdown + fontmatter import and export: Extract frontMatter logic into a separate file (#10508)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-05-30 08:40:52 +01:00
Henry Heino 99b840da34 Desktop: Fixes #10284: Sort the note list soon after changing a note's title (#10512) 2024-05-30 08:40:32 +01:00
Henry Heino 55c222c577 Desktop: Fixes #10514: Fix focusing the note list doesn't work when the selected note is off screen (#10515) 2024-05-30 08:38:36 +01:00
Henry Heino 418a6e455f Desktop: Fixes #10466: Fix tables missing source map attributes (#10516) 2024-05-30 08:34:52 +01:00
Henry Heino 789d19b18c Chore: Remove unused NoteTypeToggle event (#10521) 2024-05-30 08:31:54 +01:00
renovate[bot] 59b26f2c63 Update dependency @types/node to v18.19.21 (#10517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-30 02:48:06 +00:00
renovate[bot] f1eeeabdc5 Update dependency @types/node to v18.19.20 (#10513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-29 02:58:46 +00:00
Laurent Cozic d2a33b006b Desktop: Fixes #10224: Images from behind a login cannot be pasted in RTE 2024-05-28 14:33:23 +01:00
Laurent Cozic 2386f583e8 Mobile: Fixes #10237: Automatically set focus on title or body when creating a new note 2024-05-28 14:18:35 +01:00
Laurent Cozic 70c5448402 lock files 2024-05-28 14:18:33 +01:00
Henry Heino 4e3326b12f Desktop: Fixes #10230: Fix new note and to-do buttons greyed when initial selection is "all notes" or a tag (#10434) 2024-05-28 13:43:33 +01:00
Henry Heino 34092d8491 Desktop: Fixes #10381: Fix plugin settings stored in settings.json are lost on startup (#10458) 2024-05-28 13:42:52 +01:00
Henry Heino b8caf08fac Desktop: Re-render note when resources are changed (#10459) 2024-05-28 11:30:56 +01:00
Henry Heino b0d0e641ea Docs: Resolves #10341: Document the drawing option (#10436) 2024-05-28 11:30:22 +01:00
Laurent Cozic 3a1de4e941 Update renovate.json5 2024-05-28 11:28:56 +01:00
Henry Heino 97274c95a5 Chore: Strengthen eventManager types (#10505) 2024-05-28 11:24:20 +01:00
renovate[bot] 300d0e3ca5 Update dependency @types/node to v18.19.19 (#10509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-28 11:13:24 +01:00
Henry Heino 50d08cd178 Chore: Fix spelling of react-native in renovate.json5 (#10502) 2024-05-27 17:16:00 +01:00
Henry Heino 28f3d53b3b Mobile: Improve dialog styling on large and notched screens (#10470) 2024-05-27 09:05:15 +01:00
Henry Heino 768e59938c Chore: Mobile: Fix warnings in NoteExportButton tests (#10472) 2024-05-27 09:01:32 +01:00
adanubandLaurent Cozic 6c7948a087 Doc: Removed outdated RTE info (#10479)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-05-27 09:01:18 +01:00
Laurent Cozic cc2b442519 Update renovate.json5 2024-05-27 08:59:18 +01:00
renovate[bot] fa285a9404 Update dependency ldapts to v7.0.9 (#10484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:58:34 +01:00
renovate[bot] 84faa7229d Update dependency lint-staged to v15.2.2 (#10485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:58:28 +01:00
renovate[bot] 254747ee78 Update dependency nodemailer to v6.9.10 (#10486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:58:04 +01:00
renovate[bot] ef167051d6 Update dependency react-native-device-info to v10.12.1 (#10487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:57:57 +01:00
renovate[bot] 1ccbdc2341 Update dependency react-native-document-picker to v9.1.1 (#10488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:57:50 +01:00
renovate[bot] 14747b79cd Update dependency terminal-kit to v3.0.2 (#10489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:57:32 +01:00
renovate[bot] 111385f1ef Update dependency tesseract.js to v5.0.5 (#10490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:57:21 +01:00
renovate[bot] f36d395a84 Update react monorepo (#10491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:56:49 +01:00
renovate[bot] 5d997084f7 Update dependency @types/koa to v2.14.0 (#10493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:56:35 +01:00
renovate[bot] 132548181f Update dependency css-loader to v6.9.1 (#10494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:56:26 +01:00
renovate[bot] 452c71e8cb Update dependency js-sha512 to v0.9.0 (#10495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:56:05 +01:00
renovate[bot] f7ac95f850 Update dependency react-native-paper to v5.12.3 (#10497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:55:44 +01:00
Laurent Cozic 33286efe9a Update renovate.json5 2024-05-27 08:55:29 +01:00
renovate[bot] f21a93febe Update dependency sass to v1.70.0 (#10499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-27 08:54:18 +01:00
Henry Heino f817c47dc0 Desktop,Mobile: Update Mermaid to v10.9.1 (#10475) 2024-05-27 08:53:57 +01:00
qx100 9ccca16df7 All: Translation: Update zh_CN.po (#10500) 2024-05-27 03:51:00 -04:00
Jonatan 114f5695b7 All: Translation: Update sv.po (#10478) 2024-05-27 03:49:46 -04:00
Laurent Cozic 916fa39012 Server: Print a heartbeat message to log once per minute 2024-05-26 18:27:41 +01:00
github-actions[bot] db77a51129 @adanub has signed the CLA in laurent22/joplin#10479 2024-05-26 10:19:56 +00:00
Henry Heino b09d6e8568 Chore: Refactor: Mobile: Rename CustomButton to IconButton (#10473) 2024-05-25 14:41:27 +01:00
pedr 3bf9438a59 Desktop: Resolves #9959: Display description for settings field in the plugin customization screen (#10469) 2024-05-24 22:46:36 +01:00
renovate[bot] 554894e910 Update dependency @types/node to v18.19.18 (#10467)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 19:03:14 +00:00
Sunny Thakurwar 51f1e0202f All: Translation: Update da_DK.po (#10463) 2024-05-24 15:01:12 -04:00
github-actions[bot] 8b7758442b @sunny-thakurwar has signed the CLA in laurent22/joplin#10445 2024-05-23 20:04:06 +00:00
Joplin Bot 63bf7694f0 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-05-23 06:18:52 +00:00
Joplin Bot df3aaa7dfd Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-05-22 18:19:03 +00:00
Laurent Cozic 826006ce8b Desktop release v3.0.8 2024-05-22 15:00:08 +01:00
Henry Heino 1e085ee619 Mobile: Update js-draw to version 1.20.2 (#10438) 2024-05-22 14:57:53 +01:00
Henry Heino efb753e229 Desktop: Fixes #8960: Fix cursor jumps to the top of the note editor on sync (#10456) 2024-05-22 14:57:17 +01:00
Henry Heino faf332a0e8 Chore: Fix CI build for x86 MacOS (#10452) 2024-05-21 14:28:51 +01:00
Laurent Cozic 83308337b5 Desktop release v3.0.7 2024-05-21 10:17:37 +01:00
Henry Heino 73193df120 Desktop: Sidebar: Remove redundant focus indicator (#10443) 2024-05-21 10:14:49 +01:00
Henry Heino 2dd27cdd00 Desktop: Note attachments screen: Allow searching for attachments (#10442) 2024-05-21 10:14:39 +01:00
Henry Heino c5e3672e9e Desktop, Mobile: Fixes #10439: Maintain cursor position when changing list indentation (#10441) 2024-05-21 10:13:37 +01:00
Henry Heino 366517999f Mobile: Upgrade to React Native 0.74.1 (#10401) 2024-05-21 10:12:20 +01:00
Laurent Cozic 8445ffaa86 Merge branch 'release-2.14' into dev 2024-05-21 10:10:29 +01:00
Laurent Cozic 2a76970461 Desktop release v2.14.21 2024-05-21 10:04:20 +01:00
Henry Heino 9fcaf5bd18 Desktop: Disable eval in pdf.js (#10450) 2024-05-21 10:00:17 +01:00
Henry Heino 652add9af2 Chore: Desktop: Fix eslint issues and strengthen types in NoteEditor.tsx (#10449) 2024-05-21 01:28:19 +01:00
Nitin c632ea5c48 Doc: Update index.md (#10446) 2024-05-19 12:42:22 +01:00
github-actions[bot] 7ee5f68770 @nitingururajk has signed the CLA in laurent22/joplin#10446 2024-05-19 03:12:49 +00:00
pedr 1040675781 Chore: Improve logs to help debugging (#10432) 2024-05-17 09:42:16 +01:00
Laurent Cozic 24a37e0fef Tools: Add vscode settings 2024-05-16 17:28:06 +01:00
Laurent Cozic bfe2d262a5 Tools: Add vscode settings 2024-05-16 17:21:25 +01:00
renovate[bot] 8faf5148a6 Update dependency @types/node to v18.19.17 (#10435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 11:58:31 +01:00
Henry Heino 27c5dd1852 Mobile: Fixes #10409: Fix accepting encrypted shared notebooks (#10429) 2024-05-16 09:54:24 +01:00
Dmitriy Q fae51b90a7 fix russian translate (#10428) 2024-05-16 09:51:56 +01:00
pedr ccd181851c All: Fix logger tests by adding time (#10433) 2024-05-15 15:37:31 +01:00
Laurent Cozic cd0ff94c0c Server: Use slave database for reports 2024-05-13 14:51:19 +01:00
Laurent Cozic e9e6d8a69c Server: Add uploaded data size to report 2024-05-13 14:37:02 +01:00
Laurent Cozic 7ad3b34ec3 Server: Added report page 2024-05-13 11:32:53 +01:00
Laurent Cozic f39021d373 Chore: Make API note.source optional 2024-05-12 11:14:11 +01:00
Laurent Cozic 13116fec76 Server: Allow filtering users with replication 2024-05-12 10:29:12 +01:00
Henry Heino d49b2ec0e9 Chore: Fixes #10418: Fix plugin spec page crashes documentation (#10419) 2024-05-12 10:01:24 +01:00
Henry Heino 09d088b2b5 Mobile,Desktop: Fix nonbreaking spaces and CRLF break search for adjacent words (#10417) 2024-05-12 10:01:12 +01:00
Henry Heino 3312bd27c9 Mobile: Simplify Dropbox sync workarond (#10415) 2024-05-12 10:01:02 +01:00
Liffindra Angga Zaaldian 453bdb293f Update Indonesian translations (#10422) 2024-05-12 09:58:08 +01:00
Laurent Cozic 6b0e1598ed Chore: Fixed types 2024-05-11 19:45:42 +01:00
Laurent Cozic 56f25d3094 Chore: Removed duplicated tests 2024-05-11 19:05:28 +01:00
Laurent Cozic 3e458c0028 fixed tests 2024-05-11 17:24:51 +01:00
Laurent Cozic a747828276 Api: Add support for note.source property 2024-05-11 16:21:07 +01:00
Laurent Cozic a90e3e04a4 Server: Add support for Postgres replication 2024-05-11 16:12:54 +01:00
renovate[bot] 487f01d2ec Update dependency @types/node to v18.19.15 (#10421)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-11 09:34:40 +01:00
Joplin Bot 36c25fdd86 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-05-09 00:40:49 +00:00
Laurent Cozic a9fecb31c3 iOS 12.14.8 2024-05-08 14:57:03 +01:00
Laurent Cozic ca8fd8d7ae iOS 12.14.8 2024-05-08 14:49:45 +01:00
Laurent Cozic fb345b1317 Merge branch 'release-2.14' of github.com:laurent22/joplin into release-2.14 2024-05-08 14:39:59 +01:00
Laurent Cozic 966fe38ae3 iOS: Fixed app for iOS 12 2024-05-08 14:39:46 +01:00
Henry Heino 3042e615ac iOS: Fixes #10396: Fix Dropbox sync for large file collections (#10411) 2024-05-08 14:25:06 +01:00
ERYpTION fd2ae51b93 Update da_DK.po (#10413) 2024-05-08 11:37:37 +01:00
Joplin Bot 1227730393 Doc: Auto-update documentation
Auto-updated using release-website.sh
2024-05-08 00:34:30 +00:00
Laurent Cozic 8622bd506f iOS 12.14.7 2024-05-07 22:40:42 +01:00
Laurent Cozic 5d6a39ce51 lock file 2024-05-07 22:39:55 +01:00
Henry Heino 5245c06ed3 Mobile: Fixes #10396: Fix Dropbox sync (#10400) 2024-05-07 22:38:45 +01:00
Henry Heino 3c5977346e iOS: Add Privacy manifest file (#10406) 2024-05-07 22:38:45 +01:00
Laurent Cozic a3dc9c2721 iOS 12.14.7 2024-05-07 22:30:41 +01:00
Henry Heino 90ec1f5bc6 Chore: Fix failing Dropbox sync test (#10408) 2024-05-07 17:29:23 +01:00
Laurent Cozic 99caa014ca lock file 2024-05-07 17:23:45 +01:00
Laurent Cozic 00d0cd1cf7 Chore: Fix nan 2024-05-07 17:20:29 +01:00
Henry Heino d6480e50d2 Mobile: Fixes #10396: Fix Dropbox sync (#10400) 2024-05-07 16:49:39 +01:00
Henry Heino a1a06dd7d0 iOS: Add Privacy manifest file (#10406) 2024-05-07 11:00:06 +01:00
455 changed files with 46951 additions and 32595 deletions

No files matched your search

+61 -12
View File
@@ -291,6 +291,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
@@ -322,6 +323,8 @@ packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.js
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
packages/app-desktop/gui/NoteEditor/utils/usePluginServiceRegistration.js
packages/app-desktop/gui/NoteEditor/utils/useScheduleSaveCallbacks.js
packages/app-desktop/gui/NoteEditor/utils/useScrollWhenReadyOptions.js
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.js
packages/app-desktop/gui/NoteList/NoteList2.js
@@ -453,6 +456,8 @@ packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/models/Sidebar.js
packages/app-desktop/integration-tests/noteList.spec.js
packages/app-desktop/integration-tests/richTextEditor.spec.js
packages/app-desktop/integration-tests/sidebar.spec.js
packages/app-desktop/integration-tests/simpleBackup.spec.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
@@ -461,6 +466,7 @@ packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/bridge.js
@@ -501,20 +507,23 @@ packages/app-desktop/utils/restartInSafeModeFromMain.test.js
packages/app-desktop/utils/restartInSafeModeFromMain.js
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/commands/index.js
packages/app-mobile/commands/newNote.test.js
packages/app-mobile/commands/newNote.js
packages/app-mobile/commands/openItem.js
packages/app-mobile/commands/openNote.js
packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/components/ActionButton.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
packages/app-mobile/components/CameraView.js
packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
@@ -582,11 +591,14 @@ packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/buttons/TextButton.js
packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.test.js
@@ -599,26 +611,38 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js
packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginInfoButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginToggle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
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
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useRepoApi.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useUpdateState.js
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
@@ -659,7 +683,9 @@ packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/plugins/PluginRunner/types.js
packages/app-mobile/plugins/PluginRunner/utils/createOnLogHandler.js
packages/app-mobile/plugins/hooks/usePlugin.js
packages/app-mobile/plugins/loadPlugins.test.js
packages/app-mobile/plugins/loadPlugins.js
packages/app-mobile/plugins/testing/MockPluginRunner.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
@@ -690,6 +716,7 @@ packages/app-mobile/utils/fs-driver/tarExtract.js
packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
@@ -742,8 +769,9 @@ packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/markdownReformatter.test.js
packages/editor/CodeMirror/markdown/markdownReformatter.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
@@ -756,8 +784,22 @@ packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/util/isInSyntaxNode.js
packages/editor/CodeMirror/util/setupVim.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
packages/editor/CodeMirror/utils/formatting/types.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/types.js
@@ -783,6 +825,7 @@ packages/generator-joplin/generators/app/templates/src/index.js
packages/generator-joplin/tools/updateCategories.js
packages/htmlpack/src/index.js
packages/lib/ArrayUtils.js
packages/lib/AsyncActionQueue.test.js
packages/lib/AsyncActionQueue.js
packages/lib/BaseApplication.js
packages/lib/BaseModel.js
@@ -872,6 +915,7 @@ packages/lib/markdownUtils.js
packages/lib/markdownUtils2.test.js
packages/lib/markupLanguageUtils.js
packages/lib/migrations/42.js
packages/lib/mime-utils.js
packages/lib/models/Alarm.js
packages/lib/models/BaseItem.test.js
packages/lib/models/BaseItem.js
@@ -921,6 +965,7 @@ packages/lib/ntp.js
packages/lib/onedrive-api.test.js
packages/lib/onedrive-api.js
packages/lib/path-utils.js
packages/lib/reducer.test.js
packages/lib/reducer.js
packages/lib/registry.test.js
packages/lib/registry.js
@@ -1203,10 +1248,12 @@ packages/lib/themes/solarizedLight.js
packages/lib/themes/type.js
packages/lib/time.js
packages/lib/types.js
packages/lib/urlUtils.js
packages/lib/utils/ActionLogger.test.js
packages/lib/utils/ActionLogger.js
packages/lib/utils/credentialFiles.js
packages/lib/utils/focusHandler.js
packages/lib/utils/frontMatter.js
packages/lib/utils/ipc/RemoteMessenger.test.js
packages/lib/utils/ipc/RemoteMessenger.js
packages/lib/utils/ipc/TestMessenger.js
@@ -1217,7 +1264,8 @@ packages/lib/utils/ipc/utils/mergeCallbacksAndSerializable.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializable.test.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializable.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializableArray.js
packages/lib/utils/joplinCloud.js
packages/lib/utils/joplinCloud/index.js
packages/lib/utils/joplinCloud/types.js
packages/lib/utils/processStartFlags.js
packages/lib/utils/replaceUnsupportedCharacters.test.js
packages/lib/utils/replaceUnsupportedCharacters.js
@@ -1289,6 +1337,7 @@ packages/renderer/MdToHtml/rules/mermaid.js
packages/renderer/MdToHtml/rules/sanitize_html.js
packages/renderer/MdToHtml/rules/source_map.js
packages/renderer/MdToHtml/rules/tableHorizontallyScrollable.js
packages/renderer/MdToHtml/rules/utils/defaultRule.js
packages/renderer/MdToHtml/setupLinkify.js
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/assetsToHeaders.js
+10 -3
View File
@@ -16,7 +16,7 @@ jobs:
with:
concurrent_skipping: 'same_content_newer'
BuildAndroidDebug:
AssembleRelease:
needs: pre_job
if: github.repository == 'laurent22/joplin' && needs.pre_job.outputs.should_skip != 'true'
runs-on: ubuntu-latest
@@ -25,6 +25,11 @@ jobs:
run: |
sudo apt-get update || true
sudo apt-get install -y libsecret-1-dev
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '20'
- uses: actions/checkout@v4
@@ -40,7 +45,9 @@ jobs:
- name: Install
run: yarn install
- name: Build Android Release
- name: Assemble Android Release
run: |
cd packages/app-mobile/android && ./gradlew assembleDebug
cd packages/app-mobile/android
sed -i -- 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/' app/build.gradle
./gradlew assembleRelease
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
uses: contributor-assistant/github-action@v2.3.1
uses: contributor-assistant/github-action@v2.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
+61 -13
View File
@@ -10,7 +10,6 @@ _vieux/
!var/sessions/.gitkeep
!var/SymfonyRequirements.php
.DS_Store
.vscode/*
*.map
*.pro.user
*.sublime-workspace
@@ -271,6 +270,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
@@ -302,6 +302,8 @@ packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.js
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
packages/app-desktop/gui/NoteEditor/utils/usePluginServiceRegistration.js
packages/app-desktop/gui/NoteEditor/utils/useScheduleSaveCallbacks.js
packages/app-desktop/gui/NoteEditor/utils/useScrollWhenReadyOptions.js
packages/app-desktop/gui/NoteEditor/utils/useSearchMarkers.js
packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.js
packages/app-desktop/gui/NoteList/NoteList2.js
@@ -433,6 +435,8 @@ packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/models/Sidebar.js
packages/app-desktop/integration-tests/noteList.spec.js
packages/app-desktop/integration-tests/richTextEditor.spec.js
packages/app-desktop/integration-tests/sidebar.spec.js
packages/app-desktop/integration-tests/simpleBackup.spec.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
@@ -441,6 +445,7 @@ packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/bridge.js
@@ -481,20 +486,23 @@ packages/app-desktop/utils/restartInSafeModeFromMain.test.js
packages/app-desktop/utils/restartInSafeModeFromMain.js
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/commands/index.js
packages/app-mobile/commands/newNote.test.js
packages/app-mobile/commands/newNote.js
packages/app-mobile/commands/openItem.js
packages/app-mobile/commands/openNote.js
packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/components/ActionButton.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
packages/app-mobile/components/CameraView.js
packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
@@ -562,11 +570,14 @@ packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/buttons/TextButton.js
packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.test.js
@@ -579,26 +590,38 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js
packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginInfoButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginToggle.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
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
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useRepoApi.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useUpdateState.js
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
@@ -639,7 +662,9 @@ packages/app-mobile/plugins/PluginRunner/dialogs/hooks/useWebViewSetup.js
packages/app-mobile/plugins/PluginRunner/types.js
packages/app-mobile/plugins/PluginRunner/utils/createOnLogHandler.js
packages/app-mobile/plugins/hooks/usePlugin.js
packages/app-mobile/plugins/loadPlugins.test.js
packages/app-mobile/plugins/loadPlugins.js
packages/app-mobile/plugins/testing/MockPluginRunner.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
@@ -670,6 +695,7 @@ packages/app-mobile/utils/fs-driver/tarExtract.js
packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
@@ -722,8 +748,9 @@ packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/markdownReformatter.test.js
packages/editor/CodeMirror/markdown/markdownReformatter.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
@@ -736,8 +763,22 @@ packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/util/isInSyntaxNode.js
packages/editor/CodeMirror/util/setupVim.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
packages/editor/CodeMirror/utils/formatting/types.js
packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/types.js
@@ -763,6 +804,7 @@ packages/generator-joplin/generators/app/templates/src/index.js
packages/generator-joplin/tools/updateCategories.js
packages/htmlpack/src/index.js
packages/lib/ArrayUtils.js
packages/lib/AsyncActionQueue.test.js
packages/lib/AsyncActionQueue.js
packages/lib/BaseApplication.js
packages/lib/BaseModel.js
@@ -852,6 +894,7 @@ packages/lib/markdownUtils.js
packages/lib/markdownUtils2.test.js
packages/lib/markupLanguageUtils.js
packages/lib/migrations/42.js
packages/lib/mime-utils.js
packages/lib/models/Alarm.js
packages/lib/models/BaseItem.test.js
packages/lib/models/BaseItem.js
@@ -901,6 +944,7 @@ packages/lib/ntp.js
packages/lib/onedrive-api.test.js
packages/lib/onedrive-api.js
packages/lib/path-utils.js
packages/lib/reducer.test.js
packages/lib/reducer.js
packages/lib/registry.test.js
packages/lib/registry.js
@@ -1183,10 +1227,12 @@ packages/lib/themes/solarizedLight.js
packages/lib/themes/type.js
packages/lib/time.js
packages/lib/types.js
packages/lib/urlUtils.js
packages/lib/utils/ActionLogger.test.js
packages/lib/utils/ActionLogger.js
packages/lib/utils/credentialFiles.js
packages/lib/utils/focusHandler.js
packages/lib/utils/frontMatter.js
packages/lib/utils/ipc/RemoteMessenger.test.js
packages/lib/utils/ipc/RemoteMessenger.js
packages/lib/utils/ipc/TestMessenger.js
@@ -1197,7 +1243,8 @@ packages/lib/utils/ipc/utils/mergeCallbacksAndSerializable.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializable.test.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializable.js
packages/lib/utils/ipc/utils/separateCallbacksFromSerializableArray.js
packages/lib/utils/joplinCloud.js
packages/lib/utils/joplinCloud/index.js
packages/lib/utils/joplinCloud/types.js
packages/lib/utils/processStartFlags.js
packages/lib/utils/replaceUnsupportedCharacters.test.js
packages/lib/utils/replaceUnsupportedCharacters.js
@@ -1269,6 +1316,7 @@ packages/renderer/MdToHtml/rules/mermaid.js
packages/renderer/MdToHtml/rules/sanitize_html.js
packages/renderer/MdToHtml/rules/source_map.js
packages/renderer/MdToHtml/rules/tableHorizontallyScrollable.js
packages/renderer/MdToHtml/rules/utils/defaultRule.js
packages/renderer/MdToHtml/setupLinkify.js
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/assetsToHeaders.js
+3
View File
@@ -0,0 +1,3 @@
{
"cSpell.enabled": true
}
@@ -1,8 +1,8 @@
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
index 0f52b73c61625db2a3081c0950b6bdd2b06e3d40..b0fc3de4be0b3a26b638683613c63c783c2739bb 100644
index 8a719ca35af1cc3a4192c5c5f8258fd4f7fea990..5f8831f81cd164a4f627423427ead92fa286b115 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
@@ -38,7 +38,7 @@ import com.facebook.react.uimanager.common.ViewUtil;
@@ -37,7 +37,7 @@ import com.facebook.react.uimanager.common.ViewUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
@@ -11,7 +11,7 @@ index 0f52b73c61625db2a3081c0950b6bdd2b06e3d40..b0fc3de4be0b3a26b638683613c63c78
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -151,7 +151,10 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
@@ -149,7 +149,10 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
}
private class ConcurrentOperationQueue {
@@ -0,0 +1 @@
<svg viewBox="-253.3 -97.4 506.4 422.6" width="506.4" height="422.6" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"><style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style><path d="M253.1,325.2l0-422.6l-506.4-2.842170943040401e-14l0,422.6l506.4,0" fill="#ffffffa8" class="js-draw-image-background"></path><path d="M253.1,325.2l0-422.6l-506.4,0l0,422.6l506.4,0" fill="#ffffff6d"></path><path d="M253,325.8L253,-113.4L6.4,-113.4L6.4,325.8L253,325.8" fill="#ffffff6d"></path><image href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

@@ -0,0 +1 @@
<svg viewBox="-1.816785937500157 -2.056489453124989 545.4634263671875 435.06855617557267" width="545.4634263671875" height="435.06855617557267" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" class="js-draw--autoresize"><style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style><path d="M543.6466404296873,433.0120667224477L543.6466404296873,-2.056489453124982L-1.816785937500157,-2.056489453124989L-1.816785937500157,433.0120667224477L543.6466404296873,433.0120667224477" fill="#ffffffa8" class="js-draw-image-background"></path><image href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 200 KiB

@@ -0,0 +1 @@
<svg viewBox="174.4 -53.8 359.6 318.9" width="359.6" height="318.9" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"><style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style><path d="M534,265.1l0-318.9l-359.6,1.4210854715202004e-14l0,318.9l359.6,0" fill="#ffffffa8" class="js-draw-image-background"></path><path d="M534,265.1l0-318.9l-359.6,0l0,318.9l359.6,0" fill="#ffffff6d"></path><image href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@@ -0,0 +1 @@
<svg viewBox="-330 -170 306.6375 187.43578125" width="306.6375" height="187.43578125" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" class="js-draw--autoresize"><style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style><path d="M-23.3625,17.43578125l0-187.43578125l-306.6375,0l0,187.43578125l306.6375,0" fill="none" class="js-draw-image-background"></path><image href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1 @@
<svg viewBox="-207.9615496339302 462.0390217320611 292.68421875 392.73609375" width="292.68421875" height="392.73609375" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"><style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style><path d="M84.72266911606982,854.7751154820612l0-392.7360937500001l-292.68421875,0l0,392.7360937500001l292.68421875,0" fill="#180a0a" class="js-draw-image-background"></path><image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4UAAAS5CAYAAABrz774AAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUAV2VkIDE1IE1heSAyMDI0IDEwOjU3OjI1IEFNIFBEVEAVgDkAACAASURBVHic7N1pcxtXni74JzORiZ0EuFNcxU2SJcuWbNmSXZbtai9dtru7rvve6TsTPTMxE/NiPsaNmJiPMG9udPSNie6Znujbt5Zu21Vlu2xLslySLWuzZMmkSHEVVyzEjgQyc16AoAAC4IqNyOcXwZCYTACHAIjMJ885/yOkUikDREREREREZDqKokCsdSOIiIiIiIioVgSGQiIiIiIiIrMSGAqJiIiIiIjMSxQkhkIiIiIiIiKzssDCUEhERERERGRWEnsKiYiIiIiIzEs2FFhu3LhR63YQERERERFRDVgNKyzTk5O1bgcRERERERHVgGJYOXyUiIiIiIjIrKyQGQqJiIiIiIjMSgF7ComIiIiIiEyLoZCIiIiIiMjEJHBJCiIiIiIiItNSoDAUEhERERERmRVDIRERERERkYnJHD5KRERERERkXhZYGAqJiIiIiIjMiqGQiIiIiIjIxCSIDIVERERERERmJUJgKCQiIiIiIjIrrlNIRERERERkcgyFREREREREJsXho0RERERERKbGUEhERERERGRaAkMhERERERGReTEUEhERERERmZgOg6GQiIiIiIjIzBgKiYiIiIiITMpgTyEREREREZF5CRAZComIiIiIiMyKPYVEREREREQmx1BIRERERERkUqw+SkREREREZGIadIZCIiKqjUAgiEQiUetmHDqJRAKBQLDWzSAiogZiqXUDiIjIfAKBIBYWnkCURAwdHYTNZqt1kw6FRCKBqcfT0DUdAOD1emrcIiIiagTsKSQioqrKBkIA0DUdU4+n2WO4C1sD4cLCE/YYEhHRgXFOIRERVZWm61hcWsrbxmC4s62BMGtxaQmarpe4FRER0c4MsKeQiIiqSBIzw0VFKf/ww2BYWqlAmB16K4k8lBMR0f4JEBgKiYioumw2G4PhLu0UCDkXk4iIDkpn9VEiIqoFBsOdMRASEVE1GJxTSEREtcJgWBoDIRERVYuAep9TaGhIpTiBnoioUTEY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 truncated

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

@@ -0,0 +1 @@
<svg viewBox="-250 -200 678.5 506.7" width="678.5" height="506.7" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" class="js-draw--autoresize"><style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style><path d="M428.5,306.7l0-506.7l-678.5,0l0,506.7l678.5,0" fill="none" class="js-draw-image-background"></path><image href="data:image/png;base64,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 truncated

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1 -1
View File
@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a>
<!-- SPONSORS-ORG -->
* * *
+50 -16
View File
@@ -1,24 +1,58 @@
# For development this compose file starts the database only. The app can then
# be started using `yarn start-dev`, which is useful for development, because
# it means the app Docker file doesn't have to be rebuilt on each change.
#
# Note that log is setup to give as much information as possible, including
# whether it's the master or slave database that is being used for a query.
#
# To setup and test replication, use the following config in Joplin Server. Note
# in particular the different port, which means we access the slave and not the
# master.
#
# DB_USE_SLAVE=true
# SLAVE_POSTGRES_PASSWORD=joplin
# SLAVE_POSTGRES_DATABASE=joplin
# SLAVE_POSTGRES_USER=joplin
# SLAVE_POSTGRES_PORT=5433
# SLAVE_POSTGRES_HOST=localhost
# USERS_WITH_REPLICATION=ID1,ID2,...
version: '3'
version: '2'
services:
db:
image: postgres:16
command: postgres -c work_mem=100000
postgresql-master:
image: 'bitnami/postgresql:16.3.0'
ports:
- "5432:5432"
- '5432:5432'
environment:
- POSTGRES_PASSWORD=joplin
- POSTGRES_USER=joplin
- POSTGRES_DB=joplin
# Use this to specify additional Postgres
# config parameters:
#
# command:
# - "postgres"
# - "-c"
# - "log_min_duration_statement=0"
- POSTGRESQL_PASSWORD=joplin
- POSTGRESQL_USERNAME=joplin
- POSTGRESQL_DATABASE=joplin
- POSTGRESQL_REPLICATION_MODE=master
- POSTGRESQL_REPLICATION_USER=repl_user
- POSTGRESQL_REPLICATION_PASSWORD=repl_password
- POSTGRESQL_LOG_HOSTNAME=true
- POSTGRESQL_PGAUDIT_LOG=READ,WRITE
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
postgresql-slave:
image: 'bitnami/postgresql:16.3.0'
ports:
- '5433:5432'
depends_on:
- postgresql-master
environment:
- POSTGRESQL_REPLICATION_MODE=slave
- POSTGRESQL_REPLICATION_USER=repl_user
- POSTGRESQL_REPLICATION_PASSWORD=repl_password
- POSTGRESQL_MASTER_HOST=postgresql-master
- POSTGRESQL_PASSWORD=joplin
- POSTGRESQL_MASTER_PORT_NUMBER=5432
- POSTGRESQL_LOG_HOSTNAME=true
- POSTGRESQL_PGAUDIT_LOG=READ,WRITE
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
-1
View File
@@ -106,7 +106,6 @@
"./packages/renderer/**/node_modules/": true,
".eslintignore": true,
".gitignore": true,
".vscode/*": true,
".yarn/cache": true,
".yarn/install-state.gz": true,
".yarn/plugins": true,
+4 -4
View File
@@ -40,7 +40,7 @@
"postinstall": "gulp build",
"postPreReleasesToForum": "node ./packages/tools/postPreReleasesToForum",
"publishAll": "git pull && yarn buildParallel && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
"releaseAndroid": "PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" node packages/tools/release-android.js",
"releaseAndroid": "PATH=\"/usr/local/opt/openjdk@17/bin:$PATH\" node packages/tools/release-android.js",
"releaseAndroidClean": "node packages/tools/release-android.js",
"releaseCli": "node packages/tools/release-cli.js",
"releaseClipper": "node packages/tools/release-clipper.js",
@@ -86,7 +86,7 @@
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "15.2.0",
"lint-staged": "15.2.2",
"madge": "6.1.0",
"npm-package-json-lint": "7.1.0",
"typescript": "5.2.2"
@@ -104,11 +104,11 @@
"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.52.0#./.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",
"react-native@0.71.10": "patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.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",
"@react-native-community/slider": "patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch",
"husky": "patch:husky@npm%3A3.1.0#./.yarn/patches/husky-npm-3.1.0-5cc13e4e34.patch",
"chokidar@^2.0.0": "3.5.3"
"chokidar@^2.0.0": "3.5.3",
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch"
}
}
+3 -3
View File
@@ -35,7 +35,7 @@
],
"owner": "Laurent Cozic"
},
"version": "3.0.0",
"version": "3.0.1",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
@@ -63,7 +63,7 @@
"string-padding": "1.0.2",
"strip-ansi": "6.0.1",
"tcp-port-used": "1.0.2",
"terminal-kit": "3.0.1",
"terminal-kit": "3.0.2",
"tkwidgets": "0.5.27",
"url-parse": "1.5.10",
"word-wrap": "1.2.5",
@@ -73,7 +73,7 @@
"@joplin/tools": "~3.0",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.8",
"@types/node": "18.19.14",
"@types/node": "18.19.26",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",
+2
View File
@@ -49,6 +49,8 @@ describe('MdToHtml', () => {
checkboxRenderingType: 2,
},
};
} else if (mdFilename.startsWith('sourcemap_')) {
mdToHtmlOptions.mapsToLine = true;
}
const markdown = await shim.fsDriver().readFile(mdFilePath);
@@ -0,0 +1,21 @@
<div class="joplin-table-wrapper">
<table class="maps-to-line" source-line="0" source-line-end="3">
<thead>
<tr>
<th>This</th>
<th>is</th>
<th>a</th>
<th>test</th>
</tr>
</thead>
<tbody>
<tr>
<td>This</td>
<td>table</td>
<td>has</td>
<td>line numbers</td>
</tr>
</tbody>
</table>
</div>
<p class="maps-to-line" source-line="4" source-line-end="5">When <code class="inline-code">mapsToLine</code> is enabled for this file, the table above should have line numbers that link to the original markdown.</p>
@@ -0,0 +1,5 @@
| This | is | a | test |
|------|----|---|------|
| This | table | has | line numbers |
When `mapsToLine` is enabled for this file, the table above should have line numbers that link to the original markdown.
@@ -0,0 +1,5 @@
---
title: Not a task
---
This is a note.
@@ -0,0 +1,6 @@
---
title: Task
completed?: yes
---
This is a test. This task should import as completed.
+5 -2
View File
@@ -26,6 +26,10 @@ export interface AppStateDialog {
props: Record<string, any>;
}
export interface EditorScrollPercents {
[noteId: string]: number;
}
export interface AppState extends State {
route: AppStateRoute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -34,8 +38,7 @@ export interface AppState extends State {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
windowContentSize: any;
watchedNoteFiles: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
lastEditorScrollPercents: any;
lastEditorScrollPercents: EditorScrollPercents;
devToolsVisible: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
visibleDialogs: any; // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
+2 -1
View File
@@ -204,7 +204,7 @@ class Application extends BaseApplication {
public updateEditorFont() {
const fontFamilies = [];
if (Setting.value('style.editor.fontFamily')) fontFamilies.push(`"${Setting.value('style.editor.fontFamily')}"`);
fontFamilies.push('Avenir, Arial, sans-serif');
fontFamilies.push('\'Avenir Next\', Avenir, Arial, sans-serif');
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
// https://github.com/laurent22/joplin/issues/155
@@ -420,6 +420,7 @@ class Application extends BaseApplication {
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
reg.setDispatch(this.dispatch.bind(this));
reg.setShowErrorMessageBoxHandler((message: string) => { bridge().showErrorMessageBox(message); });
if (Setting.value('flagOpenDevTools')) {
@@ -243,6 +243,24 @@ class ConfigScreenComponent extends React.Component<any, any> {
</div>
);
if (settings['sync.target'] === SyncTargetRegistry.nameToId('joplinCloud')) {
const goToJoplinCloudLogin = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'JoplinCloudLogin',
});
};
settingComps.push(
<div key="connect_to_joplin_cloud_button" style={this.rowStyle_}>
<Button
title={_('Connect to Joplin Cloud')}
level={ButtonLevel.Primary}
onClick={goToJoplinCloudLogin}
/>
</div>,
);
}
settingComps.push(
<div key="check_sync_config_button" style={this.rowStyle_}>
<Button
@@ -596,6 +614,9 @@ class ConfigScreenComponent extends React.Component<any, any> {
size={ButtonSize.Small}
/>
</div>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
</div>
</div>
@@ -38,6 +38,7 @@ interface Props {
function manifestToItem(manifest: PluginManifest): PluginItem {
return {
manifest: manifest,
installed: true,
enabled: true,
deleted: false,
devMode: false,
@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import PluginService, { defaultPluginSetting, Plugins, PluginSetting, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { _ } from '@joplin/lib/locale';
import styled from 'styled-components';
@@ -83,6 +83,7 @@ function usePluginItems(plugins: Plugins, settings: PluginSettings): PluginItem[
output.push({
manifest: plugin.manifest,
installed: true,
enabled: setting.enabled,
deleted: setting.deleted,
devMode: plugin.devMode,
@@ -213,8 +214,11 @@ export default function(props: Props) {
props.onChange({ value: pluginService.serializePluginSettings(event.value) });
}, [pluginService, props.onChange]);
const onDelete = useOnDeleteHandler(pluginSettings, onPluginSettingsChange, false);
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettings, repoApi, onPluginSettingsChange, true);
const pluginSettingsRef = useRef(pluginSettings);
pluginSettingsRef.current = pluginSettings;
const onDelete = useOnDeleteHandler(pluginSettingsRef, onPluginSettingsChange, false);
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettingsRef, repoApi, onPluginSettingsChange, true);
const onToolsClick = useCallback(async () => {
const template = [
@@ -40,7 +40,10 @@ export default function(props: Props) {
const [installingPluginsIds, setInstallingPluginIds] = useState<Record<string, boolean>>({});
const [searchResultCount, setSearchResultCount] = useState(null);
const onInstall = useOnInstallHandler(setInstallingPluginIds, props.pluginSettings, props.repoApi, props.onPluginSettingsChange, false);
const pluginSettingsRef = useRef(props.pluginSettings);
pluginSettingsRef.current = props.pluginSettings;
const onInstall = useOnInstallHandler(setInstallingPluginIds, pluginSettingsRef, props.repoApi, props.onPluginSettingsChange, false);
useEffect(() => {
setSearchResultCount(null);
@@ -9,4 +9,25 @@
p {
padding: calc(var(--joplin-font-size) * 0.8);
}
}
.joplin-cloud-account-information {
margin-bottom: var(--joplin-margin);
table {
margin-bottom: var(--joplin-margin);
border: none;
td {
border: none;
border-bottom: 1px solid var(--joplin-border-color4);
padding: var(--joplin-font-size);
}
tr:last-child {
td {
border: none;
}
}
}
}
@@ -4,10 +4,14 @@ import { _ } from '@joplin/lib/locale';
import { clipboard } from 'electron';
import Button from './Button/Button';
import { Fragment } from 'react';
import { accountTypeToString } from '@joplin/lib/utils/joplinCloud/types';
import bridge from '../services/bridge';
type JoplinCloudConfigScreenProps = {
inboxEmail: string;
joplinCloudAccountType: number;
userEmail: string;
joplinCloudWebsite: string;
};
const JoplinCloudConfigScreen = (props: JoplinCloudConfigScreenProps) => {
@@ -17,8 +21,29 @@ const JoplinCloudConfigScreen = (props: JoplinCloudConfigScreenProps) => {
const isEmailToNoteAvailableInAccount = props.joplinCloudAccountType !== 1;
const goToJoplinCloudProfile = async () => {
await bridge().openExternal(`${props.joplinCloudWebsite}/users/me`);
};
return (
<div>
<div className="joplin-cloud-account-information">
<h2>{_('Account information')}</h2>
<table>
<tbody>
<tr>
<td><strong>{_('Account type')}</strong></td>
<td>{accountTypeToString(props.joplinCloudAccountType)}</td>
</tr>
<tr>
<td><strong>{_('Email')}</strong></td>
<td>{props.userEmail}</td>
</tr>
</tbody>
</table>
<Button onClick={goToJoplinCloudProfile} title={_('Go to Joplin Cloud profile')}/>
</div>
<h2>{_('Email to note')}</h2>
<p>{_('Any email sent to this address will be converted into a note and added to your collection. The note will be saved into the Inbox notebook')}</p>
{
@@ -38,6 +63,8 @@ const mapStateToProps = (state: AppState) => {
return {
inboxEmail: state.settings['sync.10.inboxEmail'],
joplinCloudAccountType: state.settings['sync.10.accountType'],
userEmail: state.settings['sync.10.userEmail'],
joplinCloudWebsite: state.settings['sync.10.website'],
};
};
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useReducer, useState } from 'react';
import { Fragment, useEffect, useMemo, useReducer, useState } from 'react';
import ButtonBar from './ConfigScreen/ButtonBar';
import { _ } from '@joplin/lib/locale';
import { clipboard } from 'electron';
@@ -9,6 +9,7 @@ import { Dispatch } from 'redux';
import { reducer, defaultState, generateApplicationConfirmUrl, checkIfLoginWasSuccessful } from '@joplin/lib/services/joplinCloudUtils';
import { AppState } from '../app.reducer';
import Logger from '@joplin/utils/Logger';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('JoplinCloudLoginScreen');
const { connect } = require('react-redux');
@@ -38,6 +39,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
if (response && response.success) {
dispatch({ type: 'COMPLETED' });
clearInterval(interval);
void reg.scheduleSync(0);
}
} catch (error) {
logger.error(error);
@@ -77,22 +79,26 @@ const JoplinCloudScreenComponent = (props: Props) => {
return (
<div className="login-page">
<div className="page-container">
<p className="text">{_('To allow Joplin to synchronise with Joplin Cloud, please login using this URL:')}</p>
<div className="buttons-container">
<Button
onClick={onAuthorizeClicked}
title={_('Authorise')}
iconName='fa fa-external-link-alt'
level={ButtonLevel.Primary}
/>
<Button
onClick={onCopyToClipboardClicked}
title={_('Copy link to website')}
iconName='fa fa-clone'
level={ButtonLevel.Secondary}
/>
{state.active !== 'COMPLETED' ? (
<Fragment>
<p className="text">{_('To allow Joplin to synchronise with Joplin Cloud, please login using this URL:')}</p>
<div className="buttons-container">
<Button
onClick={onAuthorizeClicked}
title={_('Authorise')}
iconName='fa fa-external-link-alt'
level={ButtonLevel.Primary}
/>
<Button
onClick={onCopyToClipboardClicked}
title={_('Copy link to website')}
iconName='fa fa-clone'
level={ButtonLevel.Secondary}
/>
</div>
</div>
</Fragment>
) : null}
<p className={state.className}>{state.message()}
{state.active === 'ERROR' ? (
<span className={state.className}>{state.errorMessage}</span>
@@ -97,6 +97,7 @@ interface Props {
notesSortOrderField: string;
notesSortOrderReverse: boolean;
notesColumns: NoteListColumns;
showInvalidJoplinCloudCredential: boolean;
}
interface ShareFolderDialogOptions {
@@ -592,6 +593,13 @@ class MainScreenComponent extends React.Component<Props, State> {
});
};
const onViewJoplinCloudLoginScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'JoplinCloudLogin',
});
};
const onViewSyncSettingsScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
@@ -684,6 +692,12 @@ class MainScreenComponent extends React.Component<Props, State> {
);
} else if (this.props.mustUpgradeAppMessage) {
msg = this.renderNotificationMessage(this.props.mustUpgradeAppMessage);
} else if (this.props.showInvalidJoplinCloudCredential) {
msg = this.renderNotificationMessage(
_('Your Joplin Cloud credentials are invalid, please login.'),
_('Login to Joplin Cloud.'),
onViewJoplinCloudLoginScreen,
);
}
return (
@@ -705,7 +719,8 @@ class MainScreenComponent extends React.Component<Props, State> {
props.isSafeMode ||
this.showShareInvitationNotification(props) ||
this.props.needApiAuth ||
!!this.props.mustUpgradeAppMessage;
!!this.props.mustUpgradeAppMessage ||
props.showInvalidJoplinCloudCredential;
}
public registerCommands() {
@@ -965,6 +980,7 @@ const mapStateToProps = (state: AppState) => {
notesSortOrderField: state.settings['notes.sortOrder.field'],
notesSortOrderReverse: state.settings['notes.sortOrder.reverse'],
notesColumns: validateColumns(state.settings['notes.columns']),
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && state.mustAuthenticate,
};
};
@@ -3,9 +3,10 @@ import shim from '@joplin/lib/shim';
import { _ } from '@joplin/lib/locale';
import bridge from '../../../services/bridge';
import { openItemById } from '../../NoteEditor/utils/contextMenu';
const { parseResourceUrl, urlProtocol } = require('@joplin/lib/urlUtils');
import { fileUrlToResourceUrl, parseResourceUrl, urlProtocol } from '@joplin/lib/urlUtils';
import { fileUriToPath } from '@joplin/utils/url';
const { urlDecode } = require('@joplin/lib/string-utils');
import { urlDecode } from '@joplin/lib/string-utils';
import Setting from '@joplin/lib/models/Setting';
export const declaration: CommandDeclaration = {
name: 'openItem',
@@ -16,6 +17,11 @@ export const runtime = (): CommandRuntime => {
execute: async (context: CommandContext, link: string) => {
if (!link) throw new Error('Link cannot be empty');
const fromFileUrl = fileUrlToResourceUrl(link, Setting.value('resourceDir'));
if (fromFileUrl) {
link = fromFileUrl;
}
if (link.startsWith('joplin://') || link.startsWith(':/')) {
const parsedUrl = parseResourceUrl(link);
if (parsedUrl) {
@@ -1,7 +1,6 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
import eventManager, { EventName } from '@joplin/lib/eventManager';
export const declaration: CommandDeclaration = {
name: 'toggleNoteType',
@@ -15,14 +14,7 @@ export const runtime = (): CommandRuntime => {
for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]);
const newNote = await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
const eventNote = {
id: newNote.id,
is_todo: newNote.is_todo,
todo_due: newNote.todo_due,
todo_completed: newNote.todo_completed,
};
eventManager.emit(EventName.NoteTypeToggle, { noteId: note.id, note: eventNote });
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
}
},
enabledCondition: '!noteIsReadOnly',
@@ -4,7 +4,7 @@ import KeymapService, { KeymapItem } from '@joplin/lib/services/KeymapService';
import { EditorCommand } from '../../../../utils/types';
import shim from '@joplin/lib/shim';
import { reg } from '@joplin/lib/registry';
import setupVim from '@joplin/editor/CodeMirror/util/setupVim';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import { EventName } from '@joplin/lib/eventManager';
import normalizeAccelerator from '../../utils/normalizeAccelerator';
import { CodeMirrorVersion } from '../../utils/types';
@@ -352,7 +352,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
return {
language: isHTMLNote ? EditorLanguageType.Html : EditorLanguageType.Markdown,
readOnly: props.disabled || props.visiblePanes.indexOf('editor') < 0,
readOnly: props.disabled,
katexEnabled: Setting.value('markdown.plugin.katex'),
themeData: {
...styles.globalTheme,
@@ -366,8 +366,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
indentWithTabs: true,
};
}, [
props.contentMarkupLanguage, props.disabled, props.visiblePanes,
props.keyboardMode, styles.globalTheme,
props.contentMarkupLanguage, props.disabled, props.keyboardMode, styles.globalTheme,
]);
// Update the editor's value
@@ -8,7 +8,7 @@ import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
import shim from '@joplin/lib/shim';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import setupVim from '@joplin/editor/CodeMirror/util/setupVim';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import { dirname } from 'path';
import useKeymap from './utils/useKeymap';
import useEditorSearch from '../utils/useEditorSearchExtension';
@@ -12,25 +12,6 @@ import { focus } from '@joplin/lib/utils/focusHandler';
const logger = Logger.create('CodeMirror 6 commands');
const wrapSelectionWithStrings = (editor: CodeMirrorControl, string1: string, string2 = '', defaultText = '') => {
if (editor.somethingSelected()) {
editor.wrapSelections(string1, string2);
} else {
editor.wrapSelections(string1 + defaultText, string2);
// Now select the default text so the user can replace it
const selections = editor.listSelections();
const newSelections = [];
for (let i = 0; i < selections.length; i++) {
const s = selections[i];
const anchor = { line: s.anchor.line, ch: s.anchor.ch + string1.length };
const head = { line: s.head.line, ch: s.head.ch - string2.length };
newSelections.push({ anchor: anchor, head: head });
}
editor.setSelections(newSelections);
}
};
interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
webviewRef: RefObject<any>;
@@ -92,7 +73,9 @@ const useEditorCommands = (props: Props) => {
textLink: async () => {
const url = await dialogs.prompt(_('Insert Hyperlink'));
focus('useEditorCommands::textLink', editorRef.current);
if (url) wrapSelectionWithStrings(editorRef.current, '[', `](${url})`);
if (url) {
editorRef.current.wrapSelections('[', `](${url})`);
}
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
insertText: (value: any) => editorRef.current.insertText(value),
@@ -32,6 +32,7 @@ import markupRenderOptions from '../../utils/markupRenderOptions';
import { DropHandler } from '../../utils/useDropHandler';
import Logger from '@joplin/utils/Logger';
import useWebViewApi from './utils/useWebViewApi';
import useLinkTooltips from './utils/useLinkTooltips';
import { focus } from '@joplin/lib/utils/focusHandler';
const md5 = require('md5');
const { clipboard } = require('electron');
@@ -363,6 +364,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}, []);
useWebViewApi(editor);
const { resetModifiedTitles: resetLinkTooltips } = useLinkTooltips(editor);
useEffect(() => {
const theme = themeStyle(props.themeId);
@@ -1048,6 +1050,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
nextOnChangeEventInfo.current = null;
resetLinkTooltips();
const contentMd = await prop_htmlToMarkdownRef.current(info.contentMarkupLanguage, info.editor.getContent(), info.contentOriginalCss);
lastOnChangeEventInfo.current.content = contentMd;
@@ -17,6 +17,8 @@ export const joplinCommandToTinyMceCommands: JoplinCommandToTinyMceCommands = {
'textItalic': { name: 'mceToggleFormat', value: 'italic' },
'textCode': { name: 'mceToggleFormat', value: 'code' },
'textLink': { name: 'mceLink' },
'textBulletedList': { name: 'InsertUnorderedList' },
'textNumberedList': { name: 'InsertOrderedList' },
'search': { name: 'SearchReplace' },
'attachFile': { name: 'joplinAttach' },
'insertDateTime': true,
@@ -0,0 +1,83 @@
import type { Editor } from 'tinymce';
import { useCallback, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import shim from '@joplin/lib/shim';
const useLinkTooltips = (editor: Editor|null) => {
const resetModifiedTitles = useCallback(() => {
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');
}
}, [editor]);
useEffect(() => {
if (!editor) return () => {};
const onMouseOver = (event: MouseEvent) => {
let element = event.target as HTMLElement;
// mouseover events seem to only target the lowest applicable node in the DOM.
// If the user's mouse enters <a><strong></strong></a>, the mouseover event will
// target the <strong></strong>. As such, the parent nodes need to be checked:
let counter = 0;
while (element.tagName !== 'A' || !('href' in element)) {
element = element.parentElement;
counter++;
if (!element || counter > 4) {
return;
}
}
if (!element.hasAttribute('data-joplin-original-title')) {
element.setAttribute('data-joplin-original-title', element.title);
}
// Avoid showing internal HREFs for note links.
if (element.hasAttribute('data-resource-id') && !element.title) {
if (shim.isMac()) {
element.title = _('Cmd-click to open');
} else {
element.title = _('Ctrl-click to open');
}
} else {
if (shim.isMac()) {
element.title = _('Cmd-click to open: %s', element.title || element.href);
} else {
element.title = _('Ctrl-click to open: %s', element.title || element.href);
}
}
const onMouseLeave = () => {
resetModifiedTitles();
element.removeEventListener('mouseleave', onMouseLeave);
};
element.addEventListener('mouseleave', onMouseLeave);
};
const clearRootEventListeners = () => {
editor.getDoc().removeEventListener('mouseover', onMouseOver);
};
const setUpRootEventListeners = () => {
clearRootEventListeners();
editor.getDoc().addEventListener('mouseover', onMouseOver);
};
setUpRootEventListeners();
editor.on('SetContent', setUpRootEventListeners);
editor.on('keyup', resetModifiedTitles);
editor.on('click', resetModifiedTitles);
return () => {
resetModifiedTitles();
editor.off('SetContent', setUpRootEventListeners);
editor.off('keyup', resetModifiedTitles);
editor.off('click', resetModifiedTitles);
clearRootEventListeners();
};
}, [editor, resetModifiedTitles]);
return { resetModifiedTitles };
};
export default useLinkTooltips;
@@ -3,19 +3,18 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import TinyMCE from './NoteBody/TinyMCE/TinyMCE';
import { connect } from 'react-redux';
import MultiNoteActions from '../MultiNoteActions';
import { htmlToMarkdown, formNoteToNote } from './utils';
import { htmlToMarkdown } from './utils';
import useSearchMarkers from './utils/useSearchMarkers';
import useNoteSearchBar from './utils/useNoteSearchBar';
import useMessageHandler from './utils/useMessageHandler';
import useWindowCommandHandler from './utils/useWindowCommandHandler';
import useDropHandler from './utils/useDropHandler';
import useMarkupToHtml from './utils/useMarkupToHtml';
import useFormNote, { OnLoadEvent } from './utils/useFormNote';
import useFormNote, { OnLoadEvent, OnSetFormNote } from './utils/useFormNote';
import useEffectiveNoteId from './utils/useEffectiveNoteId';
import useFolder from './utils/useFolder';
import styles_ from './styles';
import { NoteEditorProps, FormNote, ScrollOptions, ScrollOptionTypes, OnChangeEvent, NoteBodyEditorProps, AllAssetsOptions, NoteBodyEditorRef } from './utils/types';
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
import { NoteEditorProps, FormNote, OnChangeEvent, NoteBodyEditorProps, AllAssetsOptions, NoteBodyEditorRef } from './utils/types';
import CommandService from '@joplin/lib/services/CommandService';
import ToolbarButton from '../ToolbarButton/ToolbarButton';
import Button, { ButtonLevel } from '../Button/Button';
@@ -26,7 +25,6 @@ import { _, _n } from '@joplin/lib/locale';
import TagList from '../TagList';
import NoteTitleBar from './NoteTitle/NoteTitleBar';
import markupLanguageUtils from '../../utils/markupLanguageUtils';
import usePrevious from '../hooks/usePrevious';
import Setting from '@joplin/lib/models/Setting';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
@@ -37,7 +35,7 @@ import NoteSearchBar from '../NoteSearchBar';
import { reg } from '@joplin/lib/registry';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
const bridge = require('@electron/remote').require('./bridge').default;
import bridge from '../../services/bridge';
import NoteRevisionViewer from '../NoteRevisionViewer';
import { parseShareCache } from '@joplin/lib/services/share/reducer';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
@@ -51,6 +49,9 @@ import CodeMirror5 from './NoteBody/CodeMirror/v5/CodeMirror';
import { openItemById } from './utils/contextMenu';
import getPluginSettingValue from '@joplin/lib/services/plugins/utils/getPluginSettingValue';
import { MarkupLanguage } from '@joplin/renderer';
import useScrollWhenReadyOptions from './utils/useScrollWhenReadyOptions';
import useScheduleSaveCallbacks from './utils/useScheduleSaveCallbacks';
const debounce = require('debounce');
const commands = [
require('./commands/showRevisions'),
@@ -61,20 +62,21 @@ const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
function NoteEditor(props: NoteEditorProps) {
const [showRevisions, setShowRevisions] = useState(false);
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions>(null);
const [isReadOnly, setIsReadOnly] = useState<boolean>(false);
const editorRef = useRef<NoteBodyEditorRef>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const titleInputRef = useRef<any>();
const titleInputRef = useRef<HTMLInputElement>();
const isMountedRef = useRef(true);
const noteSearchBarRef = useRef(null);
const setFormNoteRef = useRef<OnSetFormNote>();
const { saveNoteIfWillChange, scheduleSaveNote } = useScheduleSaveCallbacks({
setFormNote: setFormNoteRef, dispatch: props.dispatch, editorRef,
});
const formNote_beforeLoad = useCallback(async (event: OnLoadEvent) => {
await saveNoteIfWillChange(event.formNote);
setShowRevisions(false);
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, []);
}, [saveNoteIfWillChange]);
const formNote_afterLoad = useCallback(async () => {
setTitleHasBeenManuallyChanged(false);
@@ -92,7 +94,7 @@ function NoteEditor(props: NoteEditorProps) {
onBeforeLoad: formNote_beforeLoad,
onAfterLoad: formNote_afterLoad,
});
setFormNoteRef.current = setFormNote;
const formNoteRef = useRef<FormNote>();
formNoteRef.current = { ...formNote };
@@ -116,53 +118,6 @@ function NoteEditor(props: NoteEditorProps) {
const styles = styles_(props);
function scheduleSaveNote(formNote: FormNote) {
if (!formNote.saveActionQueue) throw new Error('saveActionQueue is not set!!'); // Sanity check
// reg.logger().debug('Scheduling...', formNote);
const makeAction = (formNote: FormNote) => {
return async function() {
const note = await formNoteToNote(formNote);
reg.logger().debug('Saving note...', note);
const savedNote = await Note.save(note);
setFormNote((prev: FormNote) => {
return { ...prev, user_updated_time: savedNote.user_updated_time, hasChanged: false };
});
void ExternalEditWatcher.instance().updateNoteFile(savedNote);
props.dispatch({
type: 'EDITOR_NOTE_STATUS_REMOVE',
id: formNote.id,
});
eventManager.emit(EventName.NoteContentChange, { note: savedNote });
};
};
formNote.saveActionQueue.push(makeAction(formNote));
}
async function saveNoteIfWillChange(formNote: FormNote) {
if (!formNote.id || !formNote.bodyWillChangeId) return;
const body = await editorRef.current.content();
scheduleSaveNote({
...formNote,
body: body,
bodyWillChangeId: 0,
bodyChangeId: 0,
});
}
async function saveNoteAndWait(formNote: FormNote) {
await saveNoteIfWillChange(formNote);
return formNote.saveActionQueue.waitForAllDone();
}
const whiteBackgroundNoteRendering = formNote.markup_language === MarkupLanguage.Html;
const markupToHtml = useMarkupToHtml({
@@ -201,29 +156,18 @@ function NoteEditor(props: NoteEditorProps) {
id: formNote.id,
});
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [props.isProvisional, formNote.id]);
}, [props.isProvisional, formNote.id, props.dispatch]);
const previousNoteId = usePrevious(formNote.id);
useEffect(() => {
if (formNote.id === previousNoteId) return;
if (editorRef.current) {
editorRef.current.resetScroll();
}
setScrollWhenReady({
type: props.selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent,
value: props.selectedNoteHash ? props.selectedNoteHash : props.lastEditorScrollPercents[formNote.id] || 0,
});
void ResourceEditWatcher.instance().stopWatchingAll();
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [formNote.id, previousNoteId]);
const scheduleNoteListResort = useMemo(() => {
return debounce(() => {
// Although the note list will update automatically, it may take some time. This
// forces an immediate update.
props.dispatch({ type: 'NOTE_SORT' });
}, 100);
}, [props.dispatch]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onFieldChange = useCallback((field: string, value: any, changeId = 0) => {
const onFieldChange = useCallback(async (field: string, value: any, changeId = 0) => {
if (!isMountedRef.current) {
// When the component is unmounted, various actions can happen which can
// trigger onChange events, for example the textarea might be cleared.
@@ -263,19 +207,23 @@ function NoteEditor(props: NoteEditorProps) {
// The previously loaded note, that was modified, will be saved via saveNoteIfWillChange()
} else {
setFormNote(newNote);
scheduleSaveNote(newNote);
await scheduleSaveNote(newNote);
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [handleProvisionalFlag, formNote, isNewNote, titleHasBeenManuallyChanged]);
if (field === 'title') {
// Scheduling a resort needs to be:
// - called after scheduleSaveNote so that the new note title is used for sorting
// - debounced because many calls to scheduleSaveNote can resolve at once
scheduleNoteListResort();
}
}, [handleProvisionalFlag, formNote, setFormNote, isNewNote, titleHasBeenManuallyChanged, scheduleNoteListResort, scheduleSaveNote]);
useWindowCommandHandler({
dispatch: props.dispatch,
formNote,
setShowLocalSearch,
noteSearchBarRef,
editorRef,
titleInputRef,
saveNoteAndWait,
setFormNote,
});
@@ -339,10 +287,15 @@ function NoteEditor(props: NoteEditorProps) {
id: formNote.id,
status: 'saving',
});
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [formNote, handleProvisionalFlag]);
}, [formNote, setFormNote, handleProvisionalFlag, props.dispatch]);
const onMessage = useMessageHandler(scrollWhenReady, setScrollWhenReady, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
const { scrollWhenReady, clearScrollWhenReady } = useScrollWhenReadyOptions({
noteId: formNote.id,
selectedNoteHash: props.selectedNoteHash,
lastEditorScrollPercents: props.lastEditorScrollPercents,
editorRef,
});
const onMessage = useMessageHandler(scrollWhenReady, clearScrollWhenReady, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const externalEditWatcher_noteChange = useCallback((event: any) => {
@@ -355,8 +308,7 @@ function NoteEditor(props: NoteEditorProps) {
setFormNote(newFormNote);
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [formNote]);
}, [formNote, setFormNote]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onNotePropertyChange = useCallback((event: any) => {
@@ -373,8 +325,7 @@ function NoteEditor(props: NoteEditorProps) {
return newFormNote;
});
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, []);
}, [setFormNote]);
useEffect(() => {
eventManager.on(EventName.AlarmChange, onNotePropertyChange);
@@ -409,8 +360,7 @@ function NoteEditor(props: NoteEditorProps) {
});
}, [props.dispatch]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function renderNoNotes(rootStyle: any) {
function renderNoNotes(rootStyle: React.CSSProperties) {
const emptyDivStyle = {
backgroundColor: 'black',
opacity: 0.1,
@@ -493,7 +443,7 @@ function NoteEditor(props: NoteEditorProps) {
}
const onRichTextReadMoreLinkClick = useCallback(() => {
bridge().openExternal('https://joplinapp.org/help/apps/rich_text_editor');
void bridge().openExternal('https://joplinapp.org/help/apps/rich_text_editor');
}, []);
const onRichTextDismissLinkClick = useCallback(() => {
@@ -521,8 +471,7 @@ function NoteEditor(props: NoteEditorProps) {
if (showRevisions) {
const theme = themeStyle(props.themeId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const revStyle: any = {
const revStyle: React.CSSProperties = {
// ...props.style,
display: 'inline-flex',
padding: theme.margin,
@@ -36,8 +36,7 @@ interface Props {
noteTitle: string;
noteIsTodo: number;
isProvisional: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
titleInputRef: any;
titleInputRef: React.RefObject<HTMLInputElement>;
onTitleChange(event: ChangeEvent<HTMLInputElement>): void;
disabled: boolean;
}
@@ -6,7 +6,7 @@ import Resource from '@joplin/lib/models/Resource';
const bridge = require('@electron/remote').require('./bridge').default;
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import htmlUtils from '@joplin/lib/htmlUtils';
import rendererHtmlUtils, { extractHtmlBody } from '@joplin/renderer/htmlUtils';
import rendererHtmlUtils, { extractHtmlBody, removeWrappingParagraphAndTrailingEmptyElements } from '@joplin/renderer/htmlUtils';
import Logger from '@joplin/utils/Logger';
import { fileUriToPath } from '@joplin/utils/url';
import { MarkupLanguage } from '@joplin/renderer';
@@ -15,7 +15,7 @@ import markupRenderOptions from './markupRenderOptions';
import { fileExtension, filename, safeFileExtension, safeFilename } from '@joplin/utils/path';
const joplinRendererUtils = require('@joplin/renderer').utils;
const { clipboard } = require('electron');
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
import * as mimeUtils from '@joplin/lib/mime-utils';
const md5 = require('md5');
const path = require('path');
@@ -220,6 +220,13 @@ export async function processPastedHtml(html: string, htmlToMd: HtmlToMarkdownHa
if (htmlToMd && mdToHtml) {
const md = await htmlToMd(MarkupLanguage.Markdown, html, '');
html = (await mdToHtml(MarkupLanguage.Markdown, md, markupRenderOptions({ bodyOnly: true }))).html;
// When plugins that add to the end of rendered content are installed, bodyOnly can
// fail to remove the wrapping paragraph. This works around that issue by removing
// the wrapping paragraph in more cases. See issue #10061.
if (!md.trim().includes('\n')) {
html = removeWrappingParagraphAndTrailingEmptyElements(html);
}
}
return extractHtmlBody(rendererHtmlUtils.sanitizeHtml(html, {
@@ -1,7 +1,9 @@
import Note from '@joplin/lib/models/Note';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import { renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react-hooks';
import useFormNote, { HookDependencies } from './useFormNote';
import shim from '@joplin/lib/shim';
import Resource from '@joplin/lib/models/Resource';
const defaultFormNoteProps: HookDependencies = {
syncStarted: false,
@@ -75,6 +77,8 @@ describe('useFormNote', () => {
title: 'Test Note!',
});
});
formNote.unmount();
});
// It seems this test is crashing the worker on CI (out of memory), so disabling it for now.
@@ -109,4 +113,43 @@ describe('useFormNote', () => {
// });
// });
test('should refresh resource infos when changed outside the editor', async () => {
let note = await Note.save({});
note = await shim.attachFileToNote(note, __filename);
const resourceIds = Note.linkedItemIds(note.body);
const resource = await Resource.load(resourceIds[0]);
const makeFormNoteProps = (syncStarted: boolean, decryptionStarted: boolean): HookDependencies => {
return {
...defaultFormNoteProps,
syncStarted,
decryptionStarted,
noteId: note.id,
};
};
const formNote = renderHook(props => useFormNote(props), {
initialProps: makeFormNoteProps(true, false),
});
await formNote.waitFor(() => {
return Object.values(formNote.result.current.resourceInfos).length > 0;
});
const initialResourceInfos = formNote.result.current.resourceInfos;
expect(initialResourceInfos).toMatchObject({
[resource.id]: { item: { id: resource.id } },
});
await act(async () => {
await Resource.save({ ...resource, filename: 'test.ts' });
});
await formNote.waitFor(() => {
const resourceInfo = formNote.result.current.resourceInfos[resource.id];
expect(resourceInfo.item).toMatchObject({
id: resource.id, filename: 'test.ts',
});
});
formNote.unmount();
});
});
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, RefObject, useRef } from 'react';
import { FormNote, defaultFormNote, ResourceInfos } from './types';
import { clearResourceCache, attachedResources } from './resourceHandling';
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
@@ -6,15 +6,17 @@ import { handleResourceDownloadMode } from './resourceHandling';
import { splitHtml } from '@joplin/renderer/HtmlToHtml';
import Setting from '@joplin/lib/models/Setting';
import usePrevious from '../../hooks/usePrevious';
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
const { MarkupToHtml } = require('@joplin/renderer');
import { MarkupToHtml } from '@joplin/renderer';
import Note from '@joplin/lib/models/Note';
import { reg } from '@joplin/lib/registry';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
import { NoteEntity } from '@joplin/lib/services/database/types';
import { focus } from '@joplin/lib/utils/focusHandler';
import Logger from '@joplin/utils/Logger';
import eventManager, { EventName } from '@joplin/lib/eventManager';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
const logger = Logger.create('useFormNote');
export interface OnLoadEvent {
formNote: FormNote;
@@ -25,28 +27,28 @@ export interface HookDependencies {
decryptionStarted: boolean;
noteId: string;
isProvisional: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
titleInputRef: any;
titleInputRef: RefObject<HTMLInputElement>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
editorRef: any;
onBeforeLoad(event: OnLoadEvent): void;
onAfterLoad(event: OnLoadEvent): void;
}
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
function installResourceChangeHandler(onResourceChangeHandler: Function) {
type MapFormNoteCallback = (previousFormNote: FormNote)=> FormNote;
export type OnSetFormNote = (newFormNote: FormNote|MapFormNoteCallback)=> void;
function installResourceChangeHandler(onResourceChangeHandler: ()=> void) {
ResourceFetcher.instance().on('downloadComplete', onResourceChangeHandler);
ResourceFetcher.instance().on('downloadStarted', onResourceChangeHandler);
DecryptionWorker.instance().on('resourceDecrypted', onResourceChangeHandler);
ResourceEditWatcher.instance().on('resourceChange', onResourceChangeHandler);
eventManager.on(EventName.ResourceChange, onResourceChangeHandler);
}
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
function uninstallResourceChangeHandler(onResourceChangeHandler: Function) {
function uninstallResourceChangeHandler(onResourceChangeHandler: ()=> void) {
ResourceFetcher.instance().off('downloadComplete', onResourceChangeHandler);
ResourceFetcher.instance().off('downloadStarted', onResourceChangeHandler);
DecryptionWorker.instance().off('resourceDecrypted', onResourceChangeHandler);
ResourceEditWatcher.instance().off('resourceChange', onResourceChangeHandler);
eventManager.off(EventName.ResourceChange, onResourceChangeHandler);
}
function resourceInfosChanged(a: ResourceInfos, b: ResourceInfos): boolean {
@@ -77,11 +79,14 @@ export default function useFormNote(dependencies: HookDependencies) {
const previousNoteId = usePrevious(formNote.id);
const [resourceInfos, setResourceInfos] = useState<ResourceInfos>({});
const formNoteRef = useRef(formNote);
formNoteRef.current = formNote;
// Increasing the value of this counter cancels any ongoing note refreshes and starts
// a new refresh.
const [formNoteRefreshScheduled, setFormNoteRefreshScheduled] = useState<number>(0);
async function initNoteState(n: NoteEntity) {
const initNoteState = useCallback(async (n: NoteEntity, isNewNote: boolean) => {
let originalCss = '';
if (n.markup_language === MarkupToHtml.MARKUP_LANGUAGE_HTML) {
@@ -106,22 +111,39 @@ export default function useFormNote(dependencies: HookDependencies) {
encryption_applied: n.encryption_applied,
};
logger.debug('Initializing note state');
// Note that for performance reason,the call to setResourceInfos should
// be first because it loads the resource infos in an async way. If we
// swap them, the formNote will be updated first and rendered, then the
// the resources will load, and the note will be re-rendered.
setResourceInfos(await attachedResources(n.body));
const resources = await attachedResources(n.body);
// If the user changes the note while resources are loading, this can lead to
// a note being incorrectly marked as "unchanged".
if (!isNewNote && formNoteRef.current?.hasChanged) {
logger.info('Cancelled note refresh -- form note changed while loading attached resources.');
return null;
}
setResourceInfos(resources);
setFormNote(newFormNote);
logger.debug('Resource info and form note set.');
await handleResourceDownloadMode(n.body);
return newFormNote;
}
}, []);
useEffect(() => {
if (formNoteRefreshScheduled <= 0) return () => {};
if (formNoteRef.current.hasChanged) {
logger.info('Form note changed between scheduling a refresh and the refresh itself. Cancelling the refresh.');
return () => {};
}
reg.logger().info('Sync has finished and note has never been changed - reloading it');
logger.info('Sync has finished and note has never been changed - reloading it');
let cancelled = false;
@@ -133,11 +155,12 @@ export default function useFormNote(dependencies: HookDependencies) {
// it would not have been loaded in the editor (due to note selection changing
// on delete)
if (!n) {
reg.logger().warn('Trying to reload note that has been deleted:', noteId);
logger.warn('Trying to reload note that has been deleted:', noteId);
return;
}
await initNoteState(n);
await initNoteState(n, false);
setFormNoteRefreshScheduled(0);
};
@@ -146,7 +169,7 @@ export default function useFormNote(dependencies: HookDependencies) {
return () => {
cancelled = true;
};
}, [formNoteRefreshScheduled, noteId]);
}, [formNoteRefreshScheduled, noteId, initNoteState]);
const refreshFormNote = useCallback(() => {
// Increase the counter to cancel any ongoing refresh attempts
@@ -163,7 +186,9 @@ export default function useFormNote(dependencies: HookDependencies) {
const syncJustEnded = prevSyncStarted && !syncStarted;
if (!decryptionJustEnded && !syncJustEnded) return;
if (formNote.hasChanged) return;
if (formNoteRef.current.hasChanged) return;
logger.debug('Sync or decryption finished with an unchanged formNote.');
// Refresh the form note.
// This is kept separate from the above logic so that when prevSyncStarted is changed
@@ -172,7 +197,7 @@ export default function useFormNote(dependencies: HookDependencies) {
}, [
prevSyncStarted, syncStarted,
prevDecryptionStarted, decryptionStarted,
formNote.hasChanged, refreshFormNote,
refreshFormNote,
]);
useEffect(() => {
@@ -185,7 +210,7 @@ export default function useFormNote(dependencies: HookDependencies) {
let cancelled = false;
reg.logger().debug('Loading existing note', noteId);
logger.debug('Loading existing note', noteId);
function handleAutoFocus(noteIsTodo: boolean) {
if (!isProvisional) return;
@@ -205,11 +230,11 @@ export default function useFormNote(dependencies: HookDependencies) {
const n = await Note.load(noteId);
if (cancelled) return;
if (!n) throw new Error(`Cannot find note with ID: ${noteId}`);
reg.logger().debug('Loaded note:', n);
logger.debug('Loaded note:', n);
await onBeforeLoad({ formNote });
const newFormNote = await initNoteState(n);
const newFormNote = await initNoteState(n, true);
setIsNewNote(isProvisional);
@@ -231,7 +256,8 @@ export default function useFormNote(dependencies: HookDependencies) {
const resourceIds = await Note.linkedResourceIds(formNote.body);
if (!event || resourceIds.indexOf(event.id) >= 0) {
clearResourceCache();
setResourceInfos(await attachedResources(formNote.body));
const newResourceInfos = await attachedResources(formNote.body);
setResourceInfos(newResourceInfos);
}
}, [formNote.body]);
@@ -266,5 +292,24 @@ export default function useFormNote(dependencies: HookDependencies) {
};
}, [formNote.body]);
return { isNewNote, formNote, setFormNote, resourceInfos };
// Currently, useFormNote relies on formNoteRef being up-to-date immediately after the editor
// changes, with no delay during which async code can run. Even a small delay (e.g. that introduced
// by a setState -> useEffect) can lead to a race condition. See https://github.com/laurent22/joplin/issues/8960.
const onSetFormNote: OnSetFormNote = useCallback(newFormNote => {
if (typeof newFormNote === 'function') {
const newNote = newFormNote(formNoteRef.current);
formNoteRef.current = newNote;
setFormNote(newNote);
} else {
formNoteRef.current = newFormNote;
setFormNote(newFormNote);
}
}, [setFormNote]);
return {
isNewNote,
formNote,
setFormNote: onSetFormNote,
resourceInfos,
};
}
@@ -25,7 +25,6 @@ export default function useMarkupToHtml(deps: HookDependencies) {
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
customCss: customCss || '',
});
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [plugins, customCss]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -63,6 +62,5 @@ export default function useMarkupToHtml(deps: HookDependencies) {
});
return result;
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [themeId, customCss, markupToHtml, whiteBackgroundNoteRendering]);
}, [themeId, markupToHtml, whiteBackgroundNoteRendering, deps.settingValue]);
}
@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler } from './types';
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions } from './types';
import contextMenu from './contextMenu';
import CommandService from '@joplin/lib/services/CommandService';
import PostMessageService from '@joplin/lib/services/PostMessageService';
@@ -8,7 +8,7 @@ import { reg } from '@joplin/lib/registry';
const bridge = require('@electron/remote').require('./bridge').default;
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
export default function useMessageHandler(scrollWhenReady: any, setScrollWhenReady: Function, editorRef: any, setLocalSearchResultCount: Function, dispatch: Function, formNote: FormNote, htmlToMd: HtmlToMarkdownHandler, mdToHtml: MarkupToHtmlHandler) {
export default function useMessageHandler(scrollWhenReady: ScrollOptions|null, clearScrollWhenReady: ()=> void, editorRef: any, setLocalSearchResultCount: Function, dispatch: Function, formNote: FormNote, htmlToMd: HtmlToMarkdownHandler, mdToHtml: MarkupToHtmlHandler) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return useCallback(async (event: any) => {
const msg = event.channel ? event.channel : '';
@@ -25,7 +25,7 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
} else if (msg === 'noteRenderComplete') {
if (scrollWhenReady) {
const options = { ...scrollWhenReady };
setScrollWhenReady(null);
clearScrollWhenReady();
editorRef.current.scrollTo(options);
}
} else if (msg === 'setMarkerCount') {
@@ -0,0 +1,66 @@
import Logger from '@joplin/utils/Logger';
import { RefObject, useCallback } from 'react';
import { FormNote, NoteBodyEditorRef } from './types';
import { formNoteToNote } from '.';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import Note from '@joplin/lib/models/Note';
import type { Dispatch } from 'redux';
import eventManager, { EventName } from '@joplin/lib/eventManager';
import type { OnSetFormNote } from './useFormNote';
const logger = Logger.create('useScheduleSaveCallbacks');
interface Props {
setFormNote: RefObject<OnSetFormNote>;
dispatch: Dispatch;
editorRef: RefObject<NoteBodyEditorRef>;
}
const useScheduleSaveCallbacks = (props: Props) => {
const scheduleSaveNote = useCallback((formNote: FormNote) => {
if (!formNote.saveActionQueue) throw new Error('saveActionQueue is not set!!'); // Sanity check
// reg.logger().debug('Scheduling...', formNote);
const makeAction = (formNote: FormNote) => {
return async function() {
const note = await formNoteToNote(formNote);
logger.debug('Saving note...', note);
const savedNote = await Note.save(note);
props.setFormNote.current((prev: FormNote) => {
return { ...prev, user_updated_time: savedNote.user_updated_time, hasChanged: false };
});
void ExternalEditWatcher.instance().updateNoteFile(savedNote);
props.dispatch({
type: 'EDITOR_NOTE_STATUS_REMOVE',
id: formNote.id,
});
eventManager.emit(EventName.NoteContentChange, { note: savedNote });
};
};
formNote.saveActionQueue.push(makeAction(formNote));
return formNote.saveActionQueue.waitForAllDone();
}, [props.dispatch, props.setFormNote]);
const saveNoteIfWillChange = useCallback(async (formNote: FormNote) => {
if (!formNote.id || !formNote.bodyWillChangeId) return;
const body = await props.editorRef.current.content();
void scheduleSaveNote({
...formNote,
body: body,
bodyWillChangeId: 0,
bodyChangeId: 0,
});
}, [scheduleSaveNote, props.editorRef]);
return { saveNoteIfWillChange, scheduleSaveNote };
};
export default useScheduleSaveCallbacks;
@@ -0,0 +1,44 @@
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
import { NoteBodyEditorRef, ScrollOptions, ScrollOptionTypes } from './types';
import usePrevious from '@joplin/lib/hooks/usePrevious';
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher';
import type { EditorScrollPercents } from '../../../app.reducer';
interface Props {
noteId: string;
selectedNoteHash: string;
lastEditorScrollPercents: EditorScrollPercents;
editorRef: RefObject<NoteBodyEditorRef>;
}
const useScrollWhenReadyOptions = ({ noteId, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions|null>(null);
const previousNoteId = usePrevious(noteId);
const lastScrollPercentsRef = useRef<EditorScrollPercents>();
lastScrollPercentsRef.current = lastEditorScrollPercents;
useEffect(() => {
if (noteId === previousNoteId) return;
if (editorRef.current) {
editorRef.current.resetScroll();
}
const lastScrollPercent = lastScrollPercentsRef.current[noteId] || 0;
setScrollWhenReady({
type: selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent,
value: selectedNoteHash ? selectedNoteHash : lastScrollPercent,
});
void ResourceEditWatcher.instance().stopWatchingAll();
}, [noteId, previousNoteId, selectedNoteHash, editorRef]);
const clearScrollWhenReady = useCallback(() => {
setScrollWhenReady(null);
}, []);
return { scrollWhenReady, clearScrollWhenReady };
};
export default useScrollWhenReadyOptions;
@@ -15,7 +15,6 @@ const commandsWithDependencies = [
type SetFormNoteCallback = (callback: (prev: FormNote)=> FormNote)=> void;
interface HookDependencies {
formNote: FormNote;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
setShowLocalSearch: Function;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
@@ -23,10 +22,7 @@ interface HookDependencies {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
noteSearchBarRef: any;
editorRef: RefObject<NoteBodyEditorRef>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
titleInputRef: any;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
saveNoteAndWait: Function;
titleInputRef: RefObject<HTMLInputElement>;
setFormNote: SetFormNoteCallback;
}
@@ -109,6 +105,5 @@ export default function useWindowCommandHandler(dependencies: HookDependencies)
CommandService.instance().unregisterRuntime(command.declaration.name);
}
};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef]);
}, [editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef, setFormNote]);
}
@@ -65,7 +65,7 @@ const NoteList = (props: Props) => {
props.notes.length,
);
const focusNote = useFocusNote(itemRefs);
const focusNote = useFocusNote(itemRefs, props.notes, makeItemIndexVisible);
const moveNote = useMoveNote(
props.notesParentType,
@@ -1,19 +1,31 @@
import shim from '@joplin/lib/shim';
import { useRef, useCallback, MutableRefObject } from 'react';
import { focus } from '@joplin/lib/utils/focusHandler';
import { NoteEntity } from '@joplin/lib/services/database/types';
export type FocusNote = (noteId: string)=> void;
type ItemRefs = MutableRefObject<Record<string, HTMLDivElement>>;
type OnMakeIndexVisible = (i: number)=> void;
const useFocusNote = (itemRefs: MutableRefObject<Record<string, HTMLDivElement>>) => {
const useFocusNote = (itemRefs: ItemRefs, notes: NoteEntity[], makeItemIndexVisible: OnMakeIndexVisible) => {
const focusItemIID = useRef(null);
const notesRef = useRef(notes);
notesRef.current = notes;
const focusNote: FocusNote = useCallback((noteId: string) => {
// - We need to focus the item manually otherwise focus might be lost when the
// list is scrolled and items within it are being rebuilt.
// - We need to use an interval because when leaving the arrow pressed, the rendering
// of items might lag behind and so the ref is not yet available at this point.
// - We need to use an interval because when leaving the arrow pressed or scrolling
// offscreen items into view, the rendering of items might lag behind and so the
// ref is not yet available at this point.
if (!itemRefs.current[noteId]) {
const targetIndex = notesRef.current.findIndex(note => note.id === noteId);
if (targetIndex > -1) {
makeItemIndexVisible(targetIndex);
}
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
focusItemIID.current = shim.setInterval(() => {
if (itemRefs.current[noteId]) {
@@ -26,7 +38,7 @@ const useFocusNote = (itemRefs: MutableRefObject<Record<string, HTMLDivElement>>
if (focusItemIID.current) shim.clearInterval(focusItemIID.current);
focus('useFocusNote2', itemRefs.current[noteId]);
}
}, [itemRefs]);
}, [itemRefs, makeItemIndexVisible]);
return focusNote;
};
@@ -104,10 +104,17 @@ const useOnKeyDown = (
event.preventDefault();
}
if (noteIds.length && (key === 'Delete' || (key === 'Backspace' && event.metaKey))) {
event.preventDefault();
if (CommandService.instance().isEnabled('deleteNote')) {
void CommandService.instance().execute('deleteNote', noteIds);
if (noteIds.length) {
if (key === 'Delete' && event.shiftKey) {
event.preventDefault();
if (CommandService.instance().isEnabled('permanentlyDeleteNote')) {
void CommandService.instance().execute('permanentlyDeleteNote', noteIds);
}
} else if (key === 'Delete' || (key === 'Backspace' && event.metaKey)) {
event.preventDefault();
if (CommandService.instance().isEnabled('deleteNote')) {
void CommandService.instance().execute('deleteNote', noteIds);
}
}
}
+68 -11
View File
@@ -7,6 +7,7 @@ const { themeStyle } = require('@joplin/lib/theme');
import bridge from '../services/bridge';
const prettyBytes = require('pretty-bytes');
import Resource from '@joplin/lib/models/Resource';
import { LoadOptions } from '@joplin/lib/models/utils/types';
interface Style {
width: number;
@@ -31,6 +32,7 @@ interface State {
resources: InnerResource[] | undefined;
sorting: ActiveSorting;
isLoading: boolean;
filter: string;
}
interface ResourceTable {
@@ -42,6 +44,7 @@ interface ResourceTable {
onResourceDelete: (resource: InnerResource)=> any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onToggleSorting: (order: SortingOrder)=> any;
filter: string;
themeId: number;
style: Style;
}
@@ -89,6 +92,10 @@ const ResourceTableComp = (props: ResourceTable) => {
fontWeight: 'bold',
};
const filteredResources = props.resources.filter(
(resource: InnerResource) => !props.filter || resource.title?.includes(props.filter) || resource.id.includes(props.filter),
);
return (
<table style={{ width: '100%' }}>
<thead>
@@ -100,7 +107,7 @@ const ResourceTableComp = (props: ResourceTable) => {
</tr>
</thead>
<tbody>
{props.resources.map((resource: InnerResource, index: number) =>
{filteredResources.map((resource: InnerResource, index: number) =>
<tr key={index}>
<td style={titleCellStyle} className="titleCell">
<a
@@ -136,13 +143,15 @@ const getNextSortingOrderType = (s: SortingType): SortingType => {
}
};
const MAX_RESOURCES = 10000;
const defaultMaxResources = 10000;
const searchMaxResources = 1000;
class ResourceScreenComponent extends React.Component<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
resources: undefined,
filter: '',
sorting: {
type: 'asc',
order: 'name',
@@ -151,22 +160,57 @@ class ResourceScreenComponent extends React.Component<Props, State> {
};
}
public async reloadResources(sorting: ActiveSorting) {
private get maxResources() {
// Use a smaller maximum when searching for performance -- results update
// when the search input changes.
if (this.state.filter) {
return searchMaxResources;
} else {
return defaultMaxResources;
}
}
private reloadResourcesCounter = 0;
public async reloadResources() {
this.setState({ isLoading: true });
this.reloadResourcesCounter ++;
const currentCounterValue = this.reloadResourcesCounter;
let searchOptions: Partial<LoadOptions> = {};
if (this.state.filter) {
const search = `%${this.state.filter}%`;
searchOptions = {
where: 'id LIKE ? OR title LIKE ?',
whereParams: [search, search],
};
}
const resources = await Resource.all({
order: [{
by: getSortingOrderColumn(sorting.order),
dir: sorting.type,
by: getSortingOrderColumn(this.state.sorting.order),
dir: this.state.sorting.type,
caseInsensitive: true,
}],
limit: MAX_RESOURCES,
limit: this.maxResources,
fields: ['title', 'id', 'size', 'file_extension'],
...searchOptions,
});
const cancelled = currentCounterValue !== this.reloadResourcesCounter;
if (cancelled) return;
this.setState({ resources, isLoading: false });
}
public componentDidMount() {
void this.reloadResources(this.state.sorting);
void this.reloadResources();
}
public componentDidUpdate(_prevProps: Props, prevState: State) {
if (prevState.sorting !== this.state.sorting || prevState.filter !== this.state.filter) {
void this.reloadResources();
}
}
public onResourceDelete(resource: InnerResource) {
@@ -185,7 +229,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
})
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
.finally(() => {
void this.reloadResources(this.state.sorting);
void this.reloadResources();
});
}
@@ -208,9 +252,12 @@ class ResourceScreenComponent extends React.Component<Props, State> {
};
}
this.setState({ sorting: newSorting });
void this.reloadResources(newSorting);
}
public onFilterUpdate = (updateEvent: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ filter: updateEvent.target.value });
};
public render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);
@@ -236,20 +283,30 @@ class ResourceScreenComponent extends React.Component<Props, State> {
<div style={{ ...theme.notificationBox, marginBottom: 10 }}>{
_('This is an advanced tool to show the attachments that are linked to your notes. Please be careful when deleting one of them as they cannot be restored afterwards.')
}</div>
<div style={{ float: 'right' }}>
<input
style={theme.inputStyle}
type="search"
value={this.state.filter}
onChange={this.onFilterUpdate}
placeholder={_('Search...')}
/>
</div>
{this.state.isLoading && <div>{_('Please wait...')}</div>}
{!this.state.isLoading && <div>
{!this.state.resources && <div>
{_('No resources!')}
</div>
}
{this.state.resources && this.state.resources.length === MAX_RESOURCES &&
<div>{_('Warning: not all resources shown for performance reasons (limit: %s).', MAX_RESOURCES)}</div>
{this.state.resources && this.state.resources.length === this.maxResources &&
<div>{_('Warning: not all resources shown for performance reasons (limit: %s).', this.maxResources)}</div>
}
{this.state.resources && <ResourceTableComp
themeId={this.props.themeId}
style={style}
resources={this.state.resources}
sorting={this.state.sorting}
filter={this.state.filter}
onToggleSorting={(order) => this.onToggleSortOrder(order)}
onResourceClick={(resource) => this.openResource(resource)}
onResourceDelete={(resource) => this.onResourceDelete(resource)}
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { FolderListItem, HeaderId, HeaderListItem, ListItem, ListItemType, TagListItem } from '../types';
import { FolderEntity, TagsWithNoteCountEntity } from '@joplin/lib/services/database/types';
import { renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
import { buildFolderTree, renderFolders, renderTags } from '@joplin/lib/components/shared/side-menu-shared';
import { _ } from '@joplin/lib/locale';
import CommandService from '@joplin/lib/services/CommandService';
import Setting from '@joplin/lib/models/Setting';
@@ -35,10 +35,13 @@ const useSidebarListData = (props: Props): ListItem[] => {
});
}, [props.tags]);
const folderTree = useMemo(() => {
return buildFolderTree(props.folders);
}, [props.folders]);
const folderItems = useMemo(() => {
const renderProps = {
folders: props.folders,
folderTree,
collapsedFolderIds: props.collapsedFolderIds,
};
return renderFolders<ListItem>(renderProps, (folder, hasChildren, depth): FolderListItem => {
@@ -50,7 +53,7 @@ const useSidebarListData = (props: Props): ListItem[] => {
key: folder.id,
};
});
}, [props.folders, props.collapsedFolderIds]);
}, [folderTree, props.collapsedFolderIds]);
return useMemo(() => {
const foldersHeader: HeaderListItem = {
@@ -84,6 +84,11 @@ export const StyledListItemAnchor = styled.a`
align-items: center;
user-select: none;
height: 100%;
/* A different background color is already used to indicate focus for sidebar list items. */
&:focus-visible {
outline: none;
}
`;
export const StyledShareIcon = styled.i`
@@ -207,7 +207,7 @@
for (const [assetId, asset] of Object.entries(pluginAssetsAdded_)) {
if (!processedAssetIds.includes(assetId)) {
try {
asset.element.remove();
if (asset?.element) asset.element.remove();
} catch (error) {
// We don't throw an exception but we log it since
// it shouldn't happen
@@ -1,6 +1,5 @@
import { utils as pluginUtils, PluginStates } from '@joplin/lib/services/plugins/reducer';
import CommandService from '@joplin/lib/services/CommandService';
import eventManager, { EventName } from '@joplin/lib/eventManager';
import InteropService from '@joplin/lib/services/interop/InteropService';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
import InteropServiceHelper from '../../InteropServiceHelper';
@@ -78,7 +77,6 @@ export default class NoteListUtils {
const newNote = Note.changeNoteType(note, type);
if (newNote === note) continue;
await Note.save(newNote, { userSideValidation: true });
eventManager.emit(EventName.NoteTypeToggle, { noteId: note.id });
}
};
@@ -89,50 +89,6 @@ test.describe('main', () => {
await expect(viewerFrame.locator('.joplin-editable > .katex').first()).toBeAttached();
});
test('HTML links should be preserved when editing a note in the WYSIWYG editor', async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
await mainScreen.createNewNote('Testing!');
const editor = mainScreen.noteEditor;
// Set the note's content
await editor.focusCodeMirrorEditor();
// Attach this file to the note (create a resource ID)
await setFilePickerResponse(electronApp, [__filename]);
await editor.attachFileButton.click();
// Wait to render
const viewerFrame = editor.getNoteViewerIframe();
await viewerFrame.locator('a[data-from-md]').waitFor();
// Should have an attached resource
const codeMirrorContent = await editor.codeMirrorEditor.innerText();
const resourceUrlExpression = /\[.*\]\(:\/(\w+)\)/;
expect(codeMirrorContent).toMatch(resourceUrlExpression);
const resourceId = codeMirrorContent.match(resourceUrlExpression)[1];
// Create a new note with just an HTML link
await mainScreen.createNewNote('Another test');
await editor.codeMirrorEditor.click();
await mainWindow.keyboard.type(`<a href=":/${resourceId}">HTML Link</a>`);
// Switch to the RTE
await editor.toggleEditorsButton.click();
await editor.richTextEditor.waitFor();
// Edit the note to cause the original content to update
await editor.getTinyMCEFrameLocator().locator('a').click();
await mainWindow.keyboard.type('Test...');
await editor.toggleEditorsButton.click();
await editor.codeMirrorEditor.waitFor();
// Note should still contain the resource ID and note title
const finalCodeMirrorContent = await editor.codeMirrorEditor.innerText();
expect(finalCodeMirrorContent).toContain(`:/${resourceId}`);
});
test('should correctly resize large images', async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
await mainScreen.createNewNote('Image resize test (part 1)');
@@ -48,4 +48,9 @@ export default class MainScreen {
throw new Error('Unable to find settings menu item in application menus.');
}
}
public async search(text: string) {
const searchBar = this.page.getByPlaceholder('Search...');
await searchBar.fill(text);
}
}
@@ -0,0 +1,78 @@
import { test, expect } from './util/test';
import MainScreen from './models/MainScreen';
import activateMainMenuItem from './util/activateMainMenuItem';
import setMessageBoxResponse from './util/setMessageBoxResponse';
test.describe('noteList', () => {
test('should be possible to edit notes in a different notebook when searching', async ({ mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
const sidebar = mainScreen.sidebar;
const folderAHeader = await sidebar.createNewFolder('Folder A');
await expect(folderAHeader).toBeVisible();
const folderBHeader = await sidebar.createNewFolder('Folder B');
await expect(folderBHeader).toBeVisible();
await folderBHeader.click();
await mainScreen.createNewNote('note-1');
await folderAHeader.click();
await mainScreen.createNewNote('note-2');
// Search for and focus a note different from the folder we were in before searching.
await mainScreen.search('/note-1');
const note1Result = mainScreen.noteListContainer.getByText('note-1');
await expect(note1Result).toBeAttached();
await note1Result.click();
// Typing should not cause the note to disappear
const editor = mainScreen.noteEditor;
await editor.codeMirrorEditor.click();
await mainWindow.keyboard.type('[Testing...](http://example.com/)');
// Wait to render
await expect(editor.getNoteViewerIframe().locator('a', { hasText: 'Testing...' })).toBeVisible();
// Updating the title should force the sidebar to update sooner
await expect(editor.noteTitleInput).toHaveValue('note-1');
});
test('shift-delete should ask to permanently delete notes, but only when the note list is focused', async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
const sidebar = mainScreen.sidebar;
const folderBHeader = await sidebar.createNewFolder('Folder B');
const folderAHeader = await sidebar.createNewFolder('Folder A');
await expect(folderAHeader).toBeVisible();
await mainScreen.createNewNote('test note 1');
await mainScreen.createNewNote('test note 2');
await activateMainMenuItem(electronApp, 'Note list', 'Focus');
await expect(mainScreen.noteListContainer.getByText('test note 1')).toBeVisible();
await setMessageBoxResponse(electronApp, /^Delete/i);
const pressShiftDelete = async () => {
await mainWindow.keyboard.press('Shift');
await mainWindow.keyboard.press('Delete');
await mainWindow.keyboard.up('Delete');
await mainWindow.keyboard.up('Shift');
};
await pressShiftDelete();
await folderBHeader.click();
await folderAHeader.click();
await expect(mainScreen.noteListContainer.getByText('test note 2')).not.toBeVisible();
// Should not delete when the editor is focused
await mainScreen.noteEditor.focusCodeMirrorEditor();
await mainWindow.keyboard.type('test');
await pressShiftDelete();
await folderBHeader.click();
await folderAHeader.click();
await expect(mainScreen.noteListContainer.getByText('test note 1')).toBeVisible();
});
});
@@ -0,0 +1,86 @@
import { test, expect } from './util/test';
import MainScreen from './models/MainScreen';
import setFilePickerResponse from './util/setFilePickerResponse';
import waitForNextOpenPath from './util/waitForNextOpenPath';
import { basename } from 'path';
test.describe('richTextEditor', () => {
test('HTML links should be preserved when editing a note', async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
await mainScreen.createNewNote('Testing!');
const editor = mainScreen.noteEditor;
// Set the note's content
await editor.focusCodeMirrorEditor();
// Attach this file to the note (create a resource ID)
await setFilePickerResponse(electronApp, [__filename]);
await editor.attachFileButton.click();
// Wait to render
const viewerFrame = editor.getNoteViewerIframe();
await viewerFrame.locator('a[data-from-md]').waitFor();
// Should have an attached resource
const codeMirrorContent = await editor.codeMirrorEditor.innerText();
const resourceUrlExpression = /\[.*\]\(:\/(\w+)\)/;
expect(codeMirrorContent).toMatch(resourceUrlExpression);
const resourceId = codeMirrorContent.match(resourceUrlExpression)[1];
// Create a new note with just an HTML link
await mainScreen.createNewNote('Another test');
await editor.codeMirrorEditor.click();
await mainWindow.keyboard.type(`<a href=":/${resourceId}">HTML Link</a>`);
// Switch to the RTE
await editor.toggleEditorsButton.click();
await editor.richTextEditor.waitFor();
// Edit the note to cause the original content to update
await editor.getTinyMCEFrameLocator().locator('a').click();
await mainWindow.keyboard.type('Test...');
await editor.toggleEditorsButton.click();
await editor.codeMirrorEditor.waitFor();
// Note should still contain the resource ID and note title
const finalCodeMirrorContent = await editor.codeMirrorEditor.innerText();
expect(finalCodeMirrorContent).toContain(`:/${resourceId}`);
});
test('should watch resources for changes when opened with ctrl+click', async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
await mainScreen.createNewNote('Testing!');
const editor = mainScreen.noteEditor;
// Set the note's content
await editor.focusCodeMirrorEditor();
// Attach this file to the note (create a resource ID)
const pathToAttach = __filename;
await setFilePickerResponse(electronApp, [pathToAttach]);
await editor.attachFileButton.click();
// Switch to the RTE
await editor.toggleEditorsButton.click();
await editor.richTextEditor.waitFor();
await editor.richTextEditor.click();
// Click on the attached file URL
const openPathResult = waitForNextOpenPath(electronApp);
const targetLink = editor.getTinyMCEFrameLocator().getByRole('link', { name: basename(pathToAttach) });
if (process.platform === 'darwin') {
await targetLink.click({ modifiers: ['Meta'] });
} else {
await targetLink.click({ modifiers: ['Control'] });
}
// Should watch the file
await mainWindow.getByText(/^The following attachments are being watched for changes/i).waitFor();
expect(await openPathResult).toContain(basename(pathToAttach));
});
});
@@ -0,0 +1,16 @@
import { ElectronApplication } from '@playwright/test';
const waitForNextOpenPath = (electronApp: ElectronApplication) => {
return electronApp.evaluate(async ({ shell }) => {
return new Promise<string>(resolve => {
const originalOpenPath = shell.openPath;
shell.openPath = async (path: string) => {
shell.openPath = originalOpenPath;
resolve(path);
return '';
};
});
});
};
export default waitForNextOpenPath;
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.0.6",
"version": "3.0.13",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -126,8 +126,8 @@
"@playwright/test": "1.42.1",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.8",
"@types/node": "18.19.14",
"@types/react": "18.2.48",
"@types/node": "18.19.26",
"@types/react": "18.2.58",
"@types/react-redux": "7.1.33",
"@types/styled-components": "5.1.32",
"@types/tesseract.js": "2.0.0",
@@ -137,7 +137,7 @@
"gulp": "4.0.2",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"js-sha512": "0.8.0",
"js-sha512": "0.9.0",
"nan": "2.19.0",
"react-test-renderer": "18.2.0",
"ts-node": "10.9.2",
@@ -192,7 +192,7 @@
"styled-components": "5.3.11",
"styled-system": "5.1.5",
"taboverride": "4.0.3",
"tesseract.js": "5.0.4",
"tesseract.js": "5.0.5",
"tinymce": "5.10.6"
}
}
+7 -2
View File
@@ -1,4 +1,9 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby '>= 2.6.10'
gem 'cocoapods', '>= 1.11.3'
ruby ">= 2.6.10"
# Cocoapods 1.15 introduced a bug which breaks the build. We will remove the upper
# bound in the template on Cocoapods with next React Native release.
gem 'cocoapods', '>= 1.13', '< 1.15'
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
+15 -95
View File
@@ -1,28 +1,28 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
import com.android.build.OutputFile
import org.apache.tools.ant.taskdefs.condition.Os
/* This is the configuration block to customize your React Native Android app.
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
// codegenDir = file("../node_modules/react-native-codegen")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
@@ -42,6 +42,7 @@ react {
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
@@ -50,14 +51,6 @@ react {
// hermesFlags = ["-O", "-output-source-map"]
}
/**
* Set this to true to create four separate APKs instead of one,
* one for each native architecture. This is useful if you don't
* use App Bundles (https://developer.android.com/guide/app-bundle/)
* and want to have separate APKs to upload to the Play Store.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
@@ -74,44 +67,20 @@ def enableProguardInReleaseBuilds = false
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
// We need the intl variant to support natural sorting of notes.
// https://github.com/laurent22/joplin/pull/4272
def jscFlavor = 'org.webkit:android-jsc-intl:+'
/**
* Private function to get the list of Native Architectures you want to build.
* This reads the value from reactNativeArchitectures in your gradle.properties
* file and works together with the --active-arch-only flag of react-native run-android.
*/
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def jscFlavor = 'org.webkit:android-jsc:+'
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// dexOptions {
// // To fix "GC overhead limit exceeded"
// // https://stackoverflow.com/q/32133013/561309
// javaMaxHeapSize "4g"
// }
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace "net.cozic.joplin"
defaultConfig {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097742
versionName "3.0.3"
versionCode 2097747
versionName "3.0.8"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
@@ -120,17 +89,7 @@ android {
missingDimensionStrategy 'react-native-camera', 'general'
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
multiDexEnabled true
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
// include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
include (*reactNativeArchitectures())
}
multiDexEnabled true
}
signingConfigs {
debug {
@@ -147,7 +106,7 @@ android {
keyPassword JOPLIN_RELEASE_KEY_PASSWORD
}
}
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
@@ -160,32 +119,6 @@ android {
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
// To fix this error:
//
// > Execution failed for task ':app:lintVitalRelease'
//
// https://stackoverflow.com/a/62603296/561309
// lintOptions {
// disable 'InvalidPackage'
// checkReleaseBuilds false
// }
}
dependencies {
@@ -195,27 +128,14 @@ dependencies {
exclude group: 'com.google.android.gms', module: 'play-services-vision'
}
// implementation fileTree(dir: "libs", include: ["*.jar"])
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
// implementation 'com.android.support:multidex:2.0.1'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
@@ -7,7 +7,5 @@
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
</application>
tools:ignore="GoogleAppIndexingWarning"/>
</manifest>
@@ -1,75 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package net.cozic.joplin;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
/**
* Class responsible of loading Flipper inside your React Native application. This is the debug
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}
@@ -92,6 +92,15 @@
<data android:mimeType="*/*" />
</intent-filter>
<!-- /SHARE EXTENSION -->
<!-- EXTERNAL LINK -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="joplin" />
</intent-filter>
<!-- /EXTERNAL LINK -->
</activity>
</application>
@@ -1,35 +0,0 @@
package net.cozic.joplin;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "Joplin";
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
* (aka React 18) with two boolean flags.
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new DefaultReactActivityDelegate(
this,
getMainComponentName(),
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
// If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
);
}
}
@@ -0,0 +1,22 @@
package net.cozic.joplin
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
class MainActivity : ReactActivity() {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
override fun getMainComponentName(): String = "Joplin"
/**
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
*/
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
@@ -1,94 +0,0 @@
package net.cozic.joplin;
import android.app.Application;
import android.database.CursorWindow;
import android.webkit.WebView;
// import androidx.multidex.MultiDex;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import net.cozic.joplin.share.SharePackage;
import net.cozic.joplin.ssl.SslPackage;
import net.cozic.joplin.textinput.TextInputPackage;
import java.lang.reflect.Field;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
// @Override
// protected void attachBaseContext(Context base) {
// super.attachBaseContext(base);
// MultiDex.install(this);
// }
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new SharePackage());
packages.add(new SslPackage());
packages.add(new TextInputPackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
// To try to fix the error "Row too big to fit into CursorWindow"
// https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-526423153
// https://github.com/laurent22/joplin/issues/1767#issuecomment-515617991
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
field.setAccessible(true);
field.set(null, 50 * 1024 * 1024); //the 102400 is the new size added
} catch (Exception e) {
e.printStackTrace();
}
SoLoader.init(this, /* native exopackage */ false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
}
@@ -0,0 +1,61 @@
package net.cozic.joplin
import android.app.Application
import android.database.CursorWindow
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
import net.cozic.joplin.versioninfo.SystemVersionInformationPackage
import net.cozic.joplin.share.SharePackage
import net.cozic.joplin.ssl.SslPackage
import net.cozic.joplin.textinput.TextInputPackage
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
add(SharePackage())
add(SslPackage())
add(TextInputPackage())
add(SystemVersionInformationPackage())
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
// To try to fix the error "Row too big to fit into CursorWindow"
// https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-526423153
// https://github.com/laurent22/joplin/issues/1767#issuecomment-515617991
try {
val field = CursorWindow::class.java.getDeclaredField("sCursorWindowSize")
field.isAccessible = true
field[null] = 50 * 1024 * 1024 //the 102400 is the new size added
} catch (e: Exception) {
e.printStackTrace()
}
SoLoader.init(this, /* native exopackage */false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
}
}
@@ -0,0 +1,61 @@
package net.cozic.joplin.versioninfo;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.webkit.WebView;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.ReactMethod;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SystemVersionInformationPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.singletonList(new SystemVersionInformationModule(reactContext));
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
public static class SystemVersionInformationModule extends ReactContextBaseJavaModule {
public SystemVersionInformationModule(@NonNull ReactApplicationContext context) {
super(context);
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> result = new HashMap<String, Object>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PackageInfo webViewPackage = WebView.getCurrentWebViewPackage();
if (webViewPackage != null) {
result.put("webViewVersion", webViewPackage.versionName);
result.put("webViewPackage", webViewPackage.packageName);
}
}
return result;
}
@NonNull
@Override
public String getName() {
return "SystemVersionInformationModule";
}
}
}
@@ -16,7 +16,7 @@
android:insetTop="@dimen/abc_edit_text_inset_top_material"
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
<selector>
<!--
<!--
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
@@ -1,18 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package net.cozic.joplin;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
/**
* Class responsible of loading Flipper inside your React Native application. This is the release
* flavor of it so it's empty as we don't want to load Flipper.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
// Do nothing as we don't want to initialize Flipper on Release.
}
}
+10 -8
View File
@@ -2,23 +2,23 @@
buildscript {
ext {
buildToolsVersion = "33.0.0"
minSdkVersion = 21
buildToolsVersion = "34.0.0"
minSdkVersion = 23
compileSdkVersion = 33
targetSdkVersion = 33
compileSdkVersion = 34
targetSdkVersion = 34
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
ndkVersion = "26.1.10909125"
kotlinVersion = "1.9.22"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("de.undercouch:gradle-download-task:5.0.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
@@ -55,3 +55,5 @@ allprojects {
maven { url 'https://www.jitpack.io' }
}
}
apply plugin: "com.facebook.react.rootproject"
@@ -25,9 +25,6 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
Binary file not shown.
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+29 -14
View File
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
@@ -231,4 +246,4 @@ eval "set -- $(
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
exec "$JAVACMD" "$@"
+22 -33
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -54,48 +55,36 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
+1 -1
View File
@@ -3,4 +3,4 @@ include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
includeBuild('../node_modules/@react-native/gradle-plugin')
+1 -1
View File
@@ -1,3 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
};
+2
View File
@@ -1,9 +1,11 @@
// AUTO-GENERATED using `gulp buildScriptIndexes`
import * as newNote from './newNote';
import * as openItem from './openItem';
import * as openNote from './openNote';
import * as scrollToHash from './scrollToHash';
const index: any[] = [
newNote,
openItem,
openNote,
scrollToHash,
@@ -0,0 +1,33 @@
import NavService from '@joplin/lib/services/NavService';
import { runtime } from './newNote';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import Setting from '@joplin/lib/models/Setting';
describe('newNote', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
});
test('should create and navigate to a new note', async () => {
const dispatchMock = jest.fn();
NavService.dispatch = dispatchMock;
// The command needs an active folder ID.
const activeFolder = await Folder.save({ title: 'folder' });
Setting.setValue('activeFolderId', activeFolder.id);
await runtime().execute(null, 'test note', true);
expect(dispatchMock).toHaveBeenCalledTimes(1);
// Correct note should have been created
const noteId = dispatchMock.mock.lastCall[0].noteId;
expect(await Note.load(noteId)).toMatchObject({ body: 'test note', parent_id: activeFolder.id });
// Should have tried to navigate to the note.
expect(dispatchMock.mock.lastCall).toMatchObject([
{ noteId: noteId, noteHash: '' },
]);
});
});
+32
View File
@@ -0,0 +1,32 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import Logger from '@joplin/utils/Logger';
import goToNote from './util/goToNote';
import Note from '@joplin/lib/models/Note';
import Setting from '@joplin/lib/models/Setting';
const logger = Logger.create('newNoteCommand');
export const declaration: CommandDeclaration = {
name: 'newNote',
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, body = '', todo = false) => {
const folderId = Setting.value('activeFolderId');
if (!folderId) {
logger.warn('Not creating new note -- no active folder ID.');
return;
}
const note = await Note.save({
body,
parent_id: folderId,
is_todo: todo ? 1 : 0,
}, { provisional: true });
logger.info(`Navigating to note ${note.id}`);
await goToNote(note.id, '');
},
};
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import shim from '@joplin/lib/shim';
import { _ } from '@joplin/lib/locale';
const { parseResourceUrl, urlProtocol } = require('@joplin/lib/urlUtils');
import { parseResourceUrl, urlProtocol } from '@joplin/lib/urlUtils';
import Logger from '@joplin/utils/Logger';
import goToNote from './util/goToNote';
Loaded 100 of 455 files, more files were not shown because too many files have changed in this diff. Show more