1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-08 23:07:32 +02:00

Compare commits

...

244 Commits

Author SHA1 Message Date
palerdot
2ea412331b Merge remote-tracking branch 'origin/dev' into note-list-display-fix 2023-03-09 12:45:24 +05:30
palerdot
0bf4da3cf5 refactor: logger.warn for ItemList notelist blank space fixes 2023-03-09 11:08:21 +05:30
renovate[bot]
3e299f1ab9 Update dependency yargs to v17.7.1 (#7892)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-09 01:14:55 +00:00
renovate[bot]
42873d3829 Update dependency yargs to v17.7.0 (#7890) 2023-03-08 22:17:54 +00:00
palerdot
016311a0ae refactor: comments 2023-03-08 20:05:47 +05:30
palerdot
8d592ac1e0 refactor: comments 2023-03-08 19:13:59 +05:30
palerdot
93f4928ea8 Merge remote-tracking branch 'origin/dev' into note-list-display-fix 2023-03-08 19:12:02 +05:30
palerdot
ea855602b9 fix(desktop): handle bottom blank space due to scroll position mismatch 2023-03-08 19:07:50 +05:30
palerdot
45da775649 fix(desktop): handle blank top spacing when toggling note list 2023-03-08 14:48:00 +05:30
renovate[bot]
4983327f90 Update dependency node-persist to v3.1.3 (#7887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-07 21:28:30 +00:00
Mr-Kanister
fddbe6cf6c All: Translation: Update de_DE.po (#7883) 2023-03-07 13:22:43 -05:00
Laurent Cozic
57f00c612d Desktop: Resolves #7867: Cache code blocks in notes to speed up rendering 2023-03-07 17:55:20 +00:00
Joplin Bot
235288e903 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-07 12:23:39 +00:00
Laurent Cozic
38be744c3e Tools: Update stats script so that it runs at the beginning of the month 2023-03-07 10:01:24 +00:00
Laurent Cozic
2384ec8792 Chore: Disable flaky test 2023-03-06 17:54:01 +00:00
Laurent Cozic
85f7caa0eb Merge branch 'release-2.10' into dev 2023-03-06 15:48:49 +00:00
Laurent Cozic
ad0f0414c4 iOS 12.10.5 2023-03-06 15:48:23 +00:00
Laurent Cozic
437320f90c lock file 2023-03-06 14:24:14 +00:00
Laurent Cozic
c1db7182ac Tools: Add class member accessibility modifiers and converted rule @typescript-eslint/explicit-member-accessibility to an error 2023-03-06 14:22:37 +00:00
renovate[bot]
aa4af69afc Update dependency yeoman-generator to v5.8.0 (#7878) 2023-03-06 14:00:03 +00:00
renovate[bot]
21a39af97b Update dependency node-persist to v3.1.2 (#7876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-06 00:10:07 +00:00
Laurent Cozic
0812cc5944 Doc: Typo 2023-03-04 12:19:23 +00:00
renovate[bot]
1fb5d2c6c5 Update dependency raw-body to v2.5.2 (#7869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-04 08:26:49 +00:00
Laurent Cozic
1b9901d232 Doc: Update coding style 2023-03-03 12:09:45 +00:00
Laurent Cozic
f15d2793cc Doc: Update coding style
Removed rules that are not enforced by esling,
and added new ones
2023-03-03 11:45:01 +00:00
Self Not Found
ad4d71dbe1 Desktop: Fixes #7831: Skip the resources which haven't been downloaded yet when exporting (#7843) 2023-03-03 11:31:49 +00:00
Julien
4bee6ffc90 Desktop: Always show new note buttons (Regression) (#7850) 2023-03-03 11:28:12 +00:00
Julien
01f63b3d97 Desktop: Fix lines alignment when sort order buttons are disabled (Regression) (#7849) 2023-03-03 11:27:11 +00:00
renovate[bot]
b19b590efc Update dependency open to v8.4.2 (#7860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-03 01:06:42 +00:00
Joplin Bot
6b7577f94d Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-03-02 15:19:57 +00:00
Laurent Cozic
4d09b14522 Doc: Added new for JdLL 2023 2023-03-02 13:27:02 +00:00
Laurent Cozic
9f1e95324d Doc: Added new for JdLL 2023 2023-03-02 13:25:46 +00:00
renovate[bot]
f98314346d Update contributor-assistant/github-action action to v2.3.0 (#7857) 2023-03-01 12:57:31 +00:00
Arun Kumar
fa659b615a Doc: Fix minor typo in technical spec documentation (#7856) 2023-03-01 05:42:30 -05:00
github-actions[bot]
e4aafa7edb @palerdot has signed the CLA from Pull Request #7856 2023-03-01 10:00:41 +00:00
renovate[bot]
811c40b074 Update dependency sass to v1.58.3 (#7852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-28 23:41:07 +00:00
Laurent Cozic
405c528ef0 Android 2.10.8 2023-02-28 18:19:09 +00:00
Laurent Cozic
a176216374 Update translations 2023-02-28 18:02:09 +00:00
renovate[bot]
8d0b090f66 Update dependency madge to v6 (#7853) 2023-02-28 15:06:57 +00:00
Joplin Bot
337c5ed61c Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-28 00:45:05 +00:00
Joplin Bot
b32d39860b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-27 18:19:06 +00:00
renovate[bot]
a091608f72 Update dependency react-native-share to v8.2.0 (#7845) 2023-02-27 12:22:55 +00:00
renovate[bot]
9686ee7833 Update dependency react-native-paper to v5.2.0 (#7844) 2023-02-27 12:22:38 +00:00
renovate[bot]
3b55dcac65 Update dependency sass to v1.58.2 (#7846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-27 06:00:02 +00:00
Joplin Bot
d524d11d8a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-26 18:16:41 +00:00
Tao Klerks
9c080ec631 Desktop: Fixes #7776: Drag-dropping notes to top or bottom, in custom sort, is finicky (#7777) 2023-02-26 15:40:13 +00:00
Laurent Cozic
da11476fd7 CLI v2.10.3 2023-02-26 13:04:03 +00:00
Laurent Cozic
d157b9cfc7 Cli: Fixed "sync" command when calling it in non-interactive mode 2023-02-26 13:02:50 +00:00
Laurent Cozic
bc8c61748a CLI v2.10.2 2023-02-26 12:41:43 +00:00
Laurent Cozic
3ad727889b Lock file 2023-02-26 12:40:57 +00:00
Laurent Cozic
8df128bb7a Releasing sub-packages 2023-02-26 12:37:29 +00:00
Laurent Cozic
5a4568f4db CI: Fixed publishall 2023-02-26 12:33:59 +00:00
Laurent Cozic
170295919a CI: Fixed publishall 2023-02-26 12:17:17 +00:00
Laurent Cozic
2262cbfdfd Desktop release v2.10.8 2023-02-26 12:15:07 +00:00
Andrej Lifinzew
43e40bcf5a CLI: Resolves #1728: Create subnotebooks (#6722) 2023-02-26 12:13:45 +00:00
Helmut K. C. Tessarek
e60595f0de All: Translation: Update da_DK.po (thanks ERYpTION) 2023-02-25 16:54:55 -05:00
renovate[bot]
527a7c115d Update jest monorepo to v29.4.3 (#7841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 20:42:10 +00:00
Laurent Cozic
c22f910500 Merge branch 'release-2.10' into dev 2023-02-25 16:50:09 +00:00
Laurent Cozic
667b7969ff CLI v2.10.1 2023-02-25 16:49:54 +00:00
Laurent Cozic
1bbf065142 Releasing sub-packages 2023-02-25 16:47:37 +00:00
renovate[bot]
606cad3c3a Update dependency react-native-image-picker to v5.0.2 (#7829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 07:42:17 +00:00
renovate[bot]
c9612dc8b8 Update dependency lint-staged to v13.1.2 (#7832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 04:15:09 +00:00
renovate[bot]
6ee30e22a6 Update dependency @react-native-community/datetimepicker to v6.7.5 (#7835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-25 02:11:33 +00:00
Laurent Cozic
a6536e1ef9 Chore: Improve logging on api/notes 2023-02-24 18:50:04 +00:00
Laurent Cozic
92cf5abd08 Desktop: Fixed clipping certain pages that contain images within links 2023-02-24 18:49:27 +00:00
Laurent Cozic
d1e545ac2c Desktop: Note background does not change when theme automatically updated via system 2023-02-24 18:00:14 +00:00
Laurent Cozic
ec6567c68d Update translations 2023-02-24 15:30:26 +00:00
Joplin Bot
3b236307f7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-24 12:23:01 +00:00
Laurent Cozic
14ac54bf3d Update translations 2023-02-24 12:07:30 +00:00
Laurent Cozic
e53b796523 iOS 12.10.4 2023-02-24 11:50:40 +00:00
Laurent Cozic
9d189b2b34 iOS 12.10.4 2023-02-24 11:50:16 +00:00
renovate[bot]
84545cf26d Update dependency sass to v1.58.1 (#7818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-24 10:39:20 +00:00
Joplin Bot
c427a6d0a5 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-24 06:18:07 +00:00
Laurent Cozic
42dc6e1ea6 Desktop release v2.10.7 2023-02-23 13:52:29 +00:00
Laurent Cozic
431b95ff7b Android 2.10.7 2023-02-23 13:51:39 +00:00
Laurent Cozic
041fb731a6 lock file 2023-02-23 13:36:59 +00:00
pedr
5289f80394 Chore: Mobile: Fixes #7822: Spellcheck was hidden because of deprecated logic (#7830) 2023-02-23 13:31:08 +00:00
Laurent Cozic
6c12ce0e04 Chore: Set type of main BrowserWindow, and fixed popup calls 2023-02-22 18:16:28 +00:00
Teko-uy
46982c7d64 Completed spanish translation (#7817) 2023-02-22 14:36:53 +00:00
Julien
359448eb69 Chore: Regression: Revert changes made to the note list' height (#7823) 2023-02-22 13:13:39 +00:00
Julien
32bb256cca All: Resolves #7661: Stop synchronization with unsupported WebDAV providers (#7819) 2023-02-22 13:12:53 +00:00
github-actions[bot]
219585bbcf @adidevs has signed the CLA from Pull Request #7824 2023-02-22 10:33:39 +00:00
renovate[bot]
1a9dbcbd1d Update dependency yeoman-generator to v5.7.1 (#7821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-22 04:34:29 +00:00
pedr
6a9848ebe7 Desktop: Fixes #7782: Markdown + Front Matter export fails when tag(s) lost (#7820) 2023-02-21 17:57:04 +00:00
Laurent Cozic
9e73d3590b Tools: Add eslint rule "no-unneeded-ternary" 2023-02-21 17:19:19 +00:00
Laurent Cozic
08c9a25182 Chore: Desktop: Do not wait for updating geolocation (regression) 2023-02-21 17:19:19 +00:00
renovate[bot]
5b9a45dc1d Update dependency sass to v1.58.0 (#7813) 2023-02-21 15:38:04 +00:00
Laurent Cozic
9dd2fb9674 Fixed regression 2023-02-21 15:28:00 +00:00
github-actions[bot]
234b5c8363 @Teko-uy has signed the CLA from Pull Request #7817 2023-02-21 13:37:05 +00:00
github-actions[bot]
1dc7ec3701 @rddunphy has signed the CLA from Pull Request #7815 2023-02-21 11:22:29 +00:00
Polaris66
72773caf58 Desktop: Resolves #7692: Added "Move Line Up" and "Move Line Down" shortcuts (#7755) 2023-02-21 10:55:17 +00:00
Joplin Bot
094249d074 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-20 18:17:07 +00:00
Laurent Cozic
c324f17453 Tools: Fixed Android build error with JDK 19 2023-02-20 16:09:34 +00:00
Laurent Cozic
25a31b0689 Android: Fixes #7791: Fixed issue where app would close after sharing a file 2023-02-20 16:09:33 +00:00
pedr
f2995dd196 Tools: Add prefer-arrow-callbacks to ESlint rules (#7810) 2023-02-20 15:02:29 +00:00
Tao Klerks
ca575162f7 Desktop: Fixes #7731: Make note sort update logic use correct prior sort and drop-grouping (#7737) 2023-02-20 13:23:26 +00:00
pedr
f0ade02435 Mobile: Fixes #7762: Hide main content while biometric is enabled and not authenticated (#7781) 2023-02-20 12:55:40 +00:00
pedr
b13c02017a Desktop: Add a link to twitter inside the help menu (#7796) 2023-02-20 12:45:23 +00:00
Laurent Cozic
716c8c1ce4 Desktop release v2.10.6 2023-02-20 11:48:44 +00:00
Laurent Cozic
30a49b84ad Chore: Mobile: Restore broken back button when file is shared 2023-02-19 19:24:44 +00:00
Laurent Cozic
b8e4150bfd iOS: Fixes #7791: Sharing pictures to Joplin creates recurring duplications 2023-02-19 19:06:17 +00:00
Laurent Cozic
ed0edcb36c iOS: Fixed sharing file 2023-02-19 19:00:17 +00:00
Laurent Cozic
e93046e8e2 lock file 2023-02-19 18:27:43 +00:00
javad mnjd
18a0ca0881 Android: Fixes #7791: Sharing pictures to Joplin creates recurring duplications (#7807) 2023-02-19 18:23:00 +00:00
javad mnjd
21dbc800d5 Android: Fixes #6942: Fixed error when sharing a file (#7801) 2023-02-19 18:21:49 +00:00
Laurent Cozic
5c1eda3392 Chore: Mobile: Refactor note-screen-shared utility lib 2023-02-19 13:09:07 +00:00
Laurent Cozic
1139317788 Chore: Mobile: Refactor note-screen-shared utility lib 2023-02-19 13:09:06 +00:00
Laurent Cozic
a24ccb8da9 Chore: Mobile: Convert Notes component to TSX and fixed AppState listener issue 2023-02-19 13:09:06 +00:00
vikneshwar
0aaa396315 Mobile: Fixes #7675: Fix camera attachment (#7775) 2023-02-19 13:08:53 +00:00
jbfagot
f9dc19e1c4 Typo correction (#7803) 2023-02-19 11:33:06 +00:00
github-actions[bot]
1839724d7c @jbfagot has signed the CLA from Pull Request #7803 2023-02-19 08:58:20 +00:00
renovate[bot]
a5aeb3a2f8 Update dependency open to v8.4.1 (#7800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 18:31:53 +00:00
javad mnjd
d5d57aa360 Android: Fixes #7791: Fixed duplicate sharing issue (#7799) 2023-02-18 16:30:56 +00:00
renovate[bot]
9aa5df7790 Update dependency @react-native-community/datetimepicker to v6.7.4 (#7798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 12:14:30 +00:00
renovate[bot]
a7697465a8 Update jest monorepo to v29.4.2 (#7793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 07:13:34 +00:00
renovate[bot]
aeb7e5ce47 Update dependency gettext-extractor to v3.6.2 (#7795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 01:48:20 +00:00
Laurent Cozic
42cef1e918 CLI: Validate required flags 2023-02-17 16:31:20 +00:00
ahk02
b832930512 Cipper: fixed display cutoff due to increase in browser scale (#7757) 2023-02-17 13:29:15 +00:00
Henry Heino
057ac550bd Chore: Renderer: Refactor and test long-press and click handlers (#7774) 2023-02-17 13:13:28 +00:00
pedr
3a14b76a61 Desktop: Add a menu option to reset the application layout (#7786) 2023-02-17 13:07:18 +00:00
Julien
e1a8c76598 Desktop: New design for "New note" and "New todo" buttons (#7780) 2023-02-17 13:02:47 +00:00
Julien
b62e6552cd Chore: Remove test spy (#7790) 2023-02-17 12:52:06 +00:00
renovate[bot]
ca6e50e80c Update dependency lint-staged to v13.1.1 (#7788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-17 11:51:44 +00:00
renovate[bot]
ad8947a85d Update dependency pg to v8.9.0 (#7784) 2023-02-17 00:01:46 +00:00
github-actions[bot]
fb562210b2 @adibullu123 has signed the CLA from Pull Request #7785 2023-02-16 16:20:58 +00:00
melsonic
34940d1c4f Desktop: Fixes #7662: Ctrl-X behaviour when no text is selected (#7778) 2023-02-16 14:08:28 +00:00
Laurent Cozic
7fa1459dc3 Tools: Apply eslint rules "no-console" and "id-denylist" 2023-02-16 10:58:24 +00:00
Laurent Cozic
625689dbb1 Tools: Add "@typescript-eslint/object-curly-spacing" rule 2023-02-16 10:58:23 +00:00
Julien
dc976047d2 Desktop: Fixes #7658: Clicking on Save saves changes when updating a link (#7753) 2023-02-16 09:01:50 +00:00
github-actions[bot]
a014e830e7 @laurent22 has signed the CLA from Pull Request #7778 2023-02-15 21:40:20 +00:00
github-actions[bot]
1e828bfdca @laurent22 has signed the CLA from Pull Request #7775 2023-02-15 21:40:16 +00:00
github-actions[bot]
60309ec6a3 @laurent22 has signed the CLA from Pull Request #7772 2023-02-15 21:39:56 +00:00
github-actions[bot]
aabba090b1 @laurent22 has signed the CLA from Pull Request #7757 2023-02-15 21:39:52 +00:00
github-actions[bot]
6ae903ef4d @laurent22 has signed the CLA from Pull Request #7755 2023-02-15 21:38:49 +00:00
pedr
dd86940c6b Desktop: Add 'Paste as text' to the Context menu of the Rich Text Editor (#7769) 2023-02-15 13:59:32 +00:00
pedr
7d7b7ed6f3 Desktop: Fixes #7634: App freezes and displays fatal error when text provided in the search bar is too long (#7764) 2023-02-14 15:13:31 +00:00
renovate[bot]
8de904cd3c Update jest monorepo to v29.4.1 (#7760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-14 04:26:21 +00:00
pedr
c706b8dd2f Desktop: Allow 'Paste as Text' on the Rich Text Editor (#7751) 2023-02-13 19:16:33 +00:00
Laurent Cozic
d55d4d42e5 Doc: Fixed licence info 2023-02-13 17:10:09 +00:00
Tao Klerks
bbfeffec69 All: Fixes #6956: Custom sort order not synchronized (#7729) 2023-02-13 16:41:55 +00:00
renovate[bot]
3c471dc120 Update jest monorepo to v29.4.0 (#7759) 2023-02-13 13:39:42 +00:00
Laurent Cozic
be0fa69b3b Revert "Mobile: Resolves #1044: Add create sub-notebook feature (#7728)"
This reverts commit f5fc1f2f22.

Cancel button is gone with this change
2023-02-10 23:48:46 +00:00
Laurent Cozic
bf1bdf0951 Android 2.10.6 2023-02-10 16:31:18 +00:00
Laurent Cozic
a1a10a6c55 Android: Fixes #7691: Sharing file to Joplin does not work 2023-02-10 16:12:49 +00:00
Laurent Cozic
02e8307093 lock file 2023-02-10 16:12:48 +00:00
renovate[bot]
285a3a211e Update dependency @react-native-community/slider to v4.4.2 (#7752)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-10 16:02:45 +00:00
Joplin Bot
80cb1471fc Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-10 06:17:49 +00:00
Joplin Bot
a7a194f835 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-10 00:43:53 +00:00
github-actions[bot]
41a0b3359a @pedr has signed the CLA from Pull Request #7751 2023-02-09 20:10:04 +00:00
Laurent Cozic
1f70357d37 Server v2.10.9 2023-02-09 19:43:09 +00:00
Laurent Cozic
107f2e128e Server: Clean up share logic 2023-02-09 19:42:16 +00:00
Laurent Cozic
e1cd8d9b85 Server v2.10.8 2023-02-09 18:24:30 +00:00
Laurent Cozic
293f621e46 Server: Fixed sharing issue when a user no longer has a user item associated with their account 2023-02-09 18:24:09 +00:00
Joplin Bot
c5b551bbcb Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-09 18:17:12 +00:00
Laurent Cozic
ac776eabc1 Server v2.10.7 2023-02-09 15:57:38 +00:00
Laurent Cozic
05c17fbfac Server: Fixed sharing issue for changes that are associated with deleted items 2023-02-09 15:56:44 +00:00
Laurent Cozic
6b9de394ca Tools: Update post release commands 2023-02-09 11:48:22 +00:00
Laurent Cozic
bef9a29581 Renovate exclude 2023-02-09 01:34:02 +00:00
renovate[bot]
1546aad7e9 Update dependency punycode to v2.3.0 (#7749) 2023-02-09 01:33:32 +00:00
renovate[bot]
fe47fef261 Update dependency redux to v4.2.1 (#7743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 00:24:35 +00:00
renovate[bot]
288f4ab43b Update dependency react-native-image-picker to v5.0.1 (#7747)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-08 20:36:31 +00:00
renovate[bot]
294cbaea4d Update dependency react-native-image-picker to v5 (#7746) 2023-02-08 17:01:40 +00:00
Carlos
f5fc1f2f22 Mobile: Resolves #1044: Add create sub-notebook feature (#7728) 2023-02-08 14:34:29 +00:00
Henry Heino
c90865c4d2 Mobile: Fixes #7700: Fix double-scroll issue in long notes (#7701) 2023-02-08 14:24:59 +00:00
Helmut K. C. Tessarek
322641ccd6 Desktop: Fixes #7694: Markdown editor not surrounding highlighted text with backticks (#7697) 2023-02-08 14:24:20 +00:00
Henry Heino
2656666ed8 Mobile: Fixes #7687: Fix startup error (#7688) 2023-02-08 14:21:59 +00:00
andy1631
2d673902a4 Desktop: Fix highlighting in GotoAnything dialogue (#7592) 2023-02-08 14:20:54 +00:00
Julien
631c41a1ff Desktop: Resolves #6143: Show installed plugins in Help - About Joplin (#7711) 2023-02-08 14:16:09 +00:00
renovate[bot]
7841c99c02 Update dependency @rmp135/sql-ts to v1.16.0 (#7736) 2023-02-08 14:13:42 +00:00
renovate[bot]
ac75d8f6ac Update dependency react-native-safe-area-context to v4.5.0 (#7744) 2023-02-08 14:13:28 +00:00
github-actions[bot]
200ff617dc @rakeshhotker has signed the CLA from Pull Request #7745 2023-02-08 06:07:30 +00:00
Joplin Bot
bbdc18f371 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-07 18:18:46 +00:00
Joplin Bot
0d35b64f9a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-07 12:21:36 +00:00
renovate[bot]
765c4482d6 Update dependency react-native-webview to v11.26.1 (#7735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-07 06:18:23 +00:00
renovate[bot]
0a0e31a37c Update dependency madge to v5.0.2 (#7734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-07 02:34:11 +00:00
Laurent Cozic
0c1f4031b4 Server v2.10.6 2023-02-06 19:01:57 +00:00
Laurent Cozic
9ed022458b Chore: Server: Clean up 2023-02-06 18:59:36 +00:00
Laurent Cozic
ba5f0bc6e3 Server: Fixed issue when an item is associated with a share that no longer exists 2023-02-06 18:59:36 +00:00
renovate[bot]
793e8f6c0f Update dependency jsdom to v21 (#7732) 2023-02-06 17:29:47 +00:00
renovate[bot]
6182ce521d Update dependency nodemailer to v6.9.1 (#7726) 2023-02-06 16:17:15 +00:00
Joplin Bot
544c50663a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-02-06 10:17:57 +00:00
Laurent Cozic
53aa9e2b42 Chore: Restore stats 2023-02-06 10:07:25 +00:00
renovate[bot]
884260189c Update dependency punycode to v2.2.2 (#7724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-05 20:28:43 +00:00
Laurent Cozic
2f9464f21f Tools: Revert CI issue 2023-02-05 19:10:31 +00:00
github-actions[bot]
c6e993b04a @TaoK has signed the CLA from Pull Request #7729 2023-02-05 17:06:32 +00:00
Laurent Cozic
89eb012b25 Tools: Add repeat mechanism when electron-builder randomly fails to build 2023-02-05 16:51:47 +00:00
Laurent Cozic
1e2aa4e2b5 Tools: Fixed Renovate patterns 2023-02-05 12:27:09 +00:00
Laurent Cozic
0019bb8d6b Tools: Add eslint rule "@typescript-eslint/no-inferrable-types" 2023-02-05 12:27:09 +00:00
renovate[bot]
e629a4d325 Update dependency nodemailer to v6.9.0 (#7722) 2023-02-05 11:43:37 +00:00
renovate[bot]
049c769d37 Update dependency eslint-plugin-react to v7.32.0 (#7698) 2023-02-05 11:41:13 +00:00
renovate[bot]
47aed8742a Update dependency punycode to v2.2.0 (#7695) 2023-02-05 11:40:58 +00:00
Adarsh Singh
8aad67ccfe Desktop: Fixes #7521: Mermaid images are incorrectly sized when exported as PNG (#7546) 2023-02-05 11:39:26 +00:00
renovate[bot]
af7cbcbca7 Update dependency eslint-plugin-import to v2.27.4 (#7717) 2023-02-05 11:06:20 +00:00
Laurent Cozic
88a91314af Tools: Reduce noise with Renovate updates 2023-02-05 11:06:10 +00:00
renovate[bot]
9873c2d756 Update dependency glob to v8.1.0 (#7718) 2023-02-04 16:12:16 +00:00
renovate[bot]
46b68cf461 Update typescript-eslint monorepo to v5.48.2 (#7705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 07:58:54 +00:00
renovate[bot]
6b96b1f355 Update dependency react-native-paper to v5.1.4 (#7714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 05:37:03 +00:00
renovate[bot]
dc819700bb Update dependency knex to v2.4.2 (#7713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-04 01:11:49 +00:00
renovate[bot]
1d1d5fea06 Update dependency @react-native-community/datetimepicker to v6.7.3 (#7712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 21:02:58 +00:00
renovate[bot]
c3afc0ede7 Update dependency prettier to v2.8.3 (#7704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 17:08:36 +00:00
github-actions[bot]
2092110a6e @julien-me has signed the CLA from Pull Request #7711 2023-02-03 12:51:14 +00:00
github-actions[bot]
fc940e9a7c @abhiippili has signed the CLA from Pull Request #7709 2023-02-01 17:12:16 +00:00
github-actions[bot]
8718310dd0 @deepampriyadarshi has signed the CLA from Pull Request #7708 2023-02-01 15:00:52 +00:00
renovate[bot]
1b527f2bbe Update dependency @react-native-community/slider to v4.4.1 (#7702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-01 00:38:41 +00:00
Joplin Bot
4da217bc2f Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-31 00:43:35 +00:00
github-actions[bot]
d3abd4ebf2 @tessus has signed the CLA from Pull Request #7697 2023-01-31 00:13:17 +00:00
Mr-Kanister
38851edf86 All: Translation: Update de_DE.po (#7696) 2023-01-30 18:15:05 -05:00
Joplin Bot
05efb765d6 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-30 18:16:44 +00:00
majsterkovic
28dc4a6abd All: Translation: Update pl_PL.po (#7690) 2023-01-29 22:54:17 -05:00
renovate[bot]
2f7b56f96f Update dependency @react-native-community/datetimepicker to v6.7.2 (#7689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-30 02:26:11 +00:00
renovate[bot]
fdaa3735fb Update dependency @types/yargs to v17.0.20 (#7685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 17:03:51 +00:00
Laurent Cozic
7dfaea12f7 Chore: Fixed build following conversion from JSX to TSX 2023-01-29 13:11:53 +00:00
renovate[bot]
18199b27d9 Update dependency @types/react to v17.0.53 (#7684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 06:18:39 +00:00
renovate[bot]
a7c52082bb Update dependency @types/react to v16.14.35 (#7683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-29 02:18:29 +00:00
renovate[bot]
3b5357e0c1 Update dependency @types/jest to v29.2.6 (#7682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 22:41:15 +00:00
Self Not Found
10dd4e45ed Desktop: Fixes #7678: Fix open files with non-ASCII characters in path (#7679) 2023-01-28 12:28:01 +00:00
github-actions[bot]
1963835309 @carlosngo has signed the CLA from Pull Request #7681 2023-01-28 10:07:12 +00:00
renovate[bot]
46ec0c1381 Update dependency knex to v2.4.1 (#7676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 03:43:16 +00:00
Laurent Cozic
bb84ae4d68 Tools: Disable tests that randomly fail onn CI 2023-01-27 18:55:39 +00:00
Laurent Cozic
b3ff53c0da Tools: Added hack to try to fix issue with broken GitHub API 2023-01-27 17:16:23 +00:00
Self Not Found
acd7bfd9f5 Desktop: Remove auto-matching for greater than character (#7669) 2023-01-27 16:50:07 +00:00
renovate[bot]
1dff50d080 Update dependency knex to v2.4.0 (#7674) 2023-01-27 16:47:20 +00:00
Laurent Cozic
af40970d09 Ignore package 2023-01-27 16:46:37 +00:00
Joplin Bot
07535a494e Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-27 06:17:16 +00:00
Joplin Bot
907422cefa Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-01-27 00:45:49 +00:00
renovate[bot]
f643baea25 Update dependency tap to v16.3.4 (#7671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-26 08:50:49 +00:00
github-actions[bot]
55a4f33982 @majsterkovic has signed the CLA from Pull Request #7668 2023-01-25 21:26:20 +00:00
Dmitriy Q
df6700959a All: Translation: Update ru_RU.po (#7665) 2023-01-25 16:10:50 -05:00
github-actions[bot]
fde8235f3e @krote5k has signed the CLA from Pull Request #7665 2023-01-25 12:59:46 +00:00
Laurent Cozic
f6ba56d966 Doc: Added GSoC idea 2023-01-24 15:56:22 +00:00
Laurent Cozic
4a5312823b Doc: Add info for GSoC 2023 2023-01-24 15:18:51 +00:00
Light
31a27b0e1c Desktop: Fixes #7565: Fix text editor text highlighting when used with special IME methods (#7630) 2023-01-24 14:46:40 +00:00
github-actions[bot]
984ad868e8 @trevor-james-nangosha has signed the CLA from Pull Request #7663 2023-01-24 12:56:39 +00:00
Betty Alagwu
9b657eeda2 Desktop: Resolves #7602: Fix copy text with no selection (#7641) 2023-01-23 18:50:24 +00:00
Laurent Cozic
6f3ad4b3b0 Doc: Allow setting period from parameter 2023-01-23 17:49:16 +00:00
renovate[bot]
56f06fae3c Update dependency ts-jest to v29.0.5 (#7659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 15:52:54 +00:00
ThetaDev
c22d884357 Doc: Update rscss link (#7653) 2023-01-23 10:11:34 +00:00
github-actions[bot]
35dc22197d @Theta-Dev has signed the CLA from Pull Request #7653 2023-01-22 22:43:08 +00:00
renovate[bot]
70d56ca0be Update dependency ts-jest to v29.0.4 (#7651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-22 02:05:55 +00:00
Laurent Cozic
bca09b9476 Android 2.10.5 2023-01-21 14:28:32 +00:00
javad mnjd
b82bf16505 Android: Resolves #6942: Improve filesystem sync performance (#7637) 2023-01-21 14:11:37 +00:00
Laurent Cozic
2f254d81cd Ignore some dependencies 2023-01-21 12:20:22 +00:00
Self Not Found
b14ce03e5b All: Translation: Update zh_CN.po (#7643) 2023-01-20 20:38:45 -05:00
Laurent Cozic
90b04cbd37 Doc: Fixes #7642: Fixed menu padding in narrower view 2023-01-20 18:07:50 +00:00
585 changed files with 27866 additions and 22078 deletions

View File

@@ -79,6 +79,8 @@ packages/app-cli/app/LinkSelector.js
packages/app-cli/app/base-command.js
packages/app-cli/app/command-done.test.js
packages/app-cli/app/command-e2ee.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
@@ -177,6 +179,7 @@ packages/app-desktop/gui/MainScreen/commands/openTag.js
packages/app-desktop/gui/MainScreen/commands/print.js
packages/app-desktop/gui/MainScreen/commands/renameFolder.js
packages/app-desktop/gui/MainScreen/commands/renameTag.js
packages/app-desktop/gui/MainScreen/commands/resetLayout.js
packages/app-desktop/gui/MainScreen/commands/revealResourceFile.js
packages/app-desktop/gui/MainScreen/commands/search.js
packages/app-desktop/gui/MainScreen/commands/setTags.js
@@ -221,6 +224,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.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/useScroll.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
@@ -228,6 +232,7 @@ packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/index.js
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
@@ -404,6 +409,7 @@ packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Notes.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/search.js
@@ -479,6 +485,7 @@ packages/lib/commands/index.js
packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/synchronize.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/note-screen-shared.js
packages/lib/database-driver-better-sqlite.js
packages/lib/database.js
packages/lib/debug/DebugService.js
@@ -508,6 +515,7 @@ packages/lib/markdownUtils.js
packages/lib/markdownUtils.test.js
packages/lib/markdownUtils2.test.js
packages/lib/markupLanguageUtils.js
packages/lib/migrations/42.js
packages/lib/models/Alarm.js
packages/lib/models/BaseItem.js
packages/lib/models/Folder.js
@@ -754,8 +762,11 @@ packages/lib/themes/type.js
packages/lib/time.js
packages/lib/utils/credentialFiles.js
packages/lib/utils/joplinCloud.js
packages/lib/utils/webDAVUtils.js
packages/lib/utils/webDAVUtils.test.js
packages/lib/uuid.js
packages/lib/versionInfo.js
packages/lib/versionInfo.test.js
packages/pdf-viewer/FullViewer.js
packages/pdf-viewer/Page.js
packages/pdf-viewer/PdfDocument.js
@@ -796,6 +807,8 @@ packages/renderer/HtmlToHtml.js
packages/renderer/InMemoryCache.js
packages/renderer/MarkupToHtml.js
packages/renderer/MdToHtml.js
packages/renderer/MdToHtml/createEventHandlingAttrs.js
packages/renderer/MdToHtml/createEventHandlingAttrs.test.js
packages/renderer/MdToHtml/linkReplacement.js
packages/renderer/MdToHtml/linkReplacement.test.js
packages/renderer/MdToHtml/renderMedia.js
@@ -822,6 +835,7 @@ packages/renderer/index.js
packages/renderer/noteStyle.js
packages/renderer/pathUtils.js
packages/renderer/utils.js
packages/tools/build-release-stats.js
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.test.js
packages/tools/bundleDefaultPlugins.js

View File

@@ -77,6 +77,7 @@ module.exports = {
'no-array-constructor': ['error'],
'radix': ['error'],
'eqeqeq': ['error', 'always'],
'no-console': ['error', { 'allow': ['warn', 'error'] }],
// Warn only for now because fixing everything would take too much
// refactoring, but new code should try to stick to it.
@@ -91,6 +92,7 @@ module.exports = {
// "react-hooks/exhaustive-deps": "warn",
'promise/prefer-await-to-then': 'error',
'no-unneeded-ternary': 'error',
// -------------------------------
// Formatting
@@ -135,6 +137,14 @@ module.exports = {
'spaced-comment': ['error', 'always'],
'keyword-spacing': ['error', { 'before': true, 'after': true }],
'no-multi-spaces': ['error'],
// Regarding the keyword blacklist:
// - err: We generally avoid using too many abbreviations, so it should
// be "error", not "err"
// - notebook: In code, it should always be "folder" (not "notebook").
// In user-facing text, it should be "notebook".
'id-denylist': ['error', 'err', 'notebook', 'notebooks'],
'prefer-arrow-callback': ['error'],
},
'plugins': [
'react',
@@ -147,6 +157,16 @@ module.exports = {
'promise',
],
'overrides': [
{
'files': [
'packages/tools/**',
'packages/app-mobile/tools/**',
'packages/app-desktop/tools/**',
],
'rules': {
'no-console': 'off',
},
},
{
// enable the rule specifically for TypeScript files
'files': ['*.ts', '*.tsx'],
@@ -155,10 +175,9 @@ module.exports = {
'project': './tsconfig.eslint.json',
},
'rules': {
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we
// make everything public which is not great. New code however should specify member accessibility.
'@typescript-eslint/explicit-member-accessibility': ['warn'],
'@typescript-eslint/explicit-member-accessibility': ['error'],
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
'@typescript-eslint/comma-dangle': ['error', {
'arrays': 'always-multiline',
'objects': 'always-multiline',
@@ -169,6 +188,7 @@ module.exports = {
'tuples': 'always-multiline',
'functions': 'never',
}],
'@typescript-eslint/object-curly-spacing': ['error', 'always'],
'@typescript-eslint/semi': ['error', 'always'],
'@typescript-eslint/member-delimiter-style': ['error', {
'multiline': {

View File

@@ -12,7 +12,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.2.1
uses: contributor-assistant/github-action@v2.3.0
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

14
.gitignore vendored
View File

@@ -67,6 +67,8 @@ packages/app-cli/app/LinkSelector.js
packages/app-cli/app/base-command.js
packages/app-cli/app/command-done.test.js
packages/app-cli/app/command-e2ee.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
@@ -165,6 +167,7 @@ packages/app-desktop/gui/MainScreen/commands/openTag.js
packages/app-desktop/gui/MainScreen/commands/print.js
packages/app-desktop/gui/MainScreen/commands/renameFolder.js
packages/app-desktop/gui/MainScreen/commands/renameTag.js
packages/app-desktop/gui/MainScreen/commands/resetLayout.js
packages/app-desktop/gui/MainScreen/commands/revealResourceFile.js
packages/app-desktop/gui/MainScreen/commands/search.js
packages/app-desktop/gui/MainScreen/commands/setTags.js
@@ -209,6 +212,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.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/useScroll.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
@@ -216,6 +220,7 @@ packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/index.js
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
@@ -392,6 +397,7 @@ packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Notes.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/search.js
@@ -467,6 +473,7 @@ packages/lib/commands/index.js
packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/synchronize.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/note-screen-shared.js
packages/lib/database-driver-better-sqlite.js
packages/lib/database.js
packages/lib/debug/DebugService.js
@@ -496,6 +503,7 @@ packages/lib/markdownUtils.js
packages/lib/markdownUtils.test.js
packages/lib/markdownUtils2.test.js
packages/lib/markupLanguageUtils.js
packages/lib/migrations/42.js
packages/lib/models/Alarm.js
packages/lib/models/BaseItem.js
packages/lib/models/Folder.js
@@ -742,8 +750,11 @@ packages/lib/themes/type.js
packages/lib/time.js
packages/lib/utils/credentialFiles.js
packages/lib/utils/joplinCloud.js
packages/lib/utils/webDAVUtils.js
packages/lib/utils/webDAVUtils.test.js
packages/lib/uuid.js
packages/lib/versionInfo.js
packages/lib/versionInfo.test.js
packages/pdf-viewer/FullViewer.js
packages/pdf-viewer/Page.js
packages/pdf-viewer/PdfDocument.js
@@ -784,6 +795,8 @@ packages/renderer/HtmlToHtml.js
packages/renderer/InMemoryCache.js
packages/renderer/MarkupToHtml.js
packages/renderer/MdToHtml.js
packages/renderer/MdToHtml/createEventHandlingAttrs.js
packages/renderer/MdToHtml/createEventHandlingAttrs.test.js
packages/renderer/MdToHtml/linkReplacement.js
packages/renderer/MdToHtml/linkReplacement.test.js
packages/renderer/MdToHtml/renderMedia.js
@@ -810,6 +823,7 @@ packages/renderer/index.js
packages/renderer/noteStyle.js
packages/renderer/pathUtils.js
packages/renderer/utils.js
packages/tools/build-release-stats.js
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.test.js
packages/tools/bundleDefaultPlugins.js

View File

@@ -0,0 +1,20 @@
diff --git a/src/RNCamera.js b/src/RNCamera.js
index b7a271ad64771c0f654dbd5fe3c0d9e0d2e2c4ef..1182a40ace081a32fbaefe2bc4a499b79c2e7dac 100644
--- a/src/RNCamera.js
+++ b/src/RNCamera.js
@@ -5,7 +5,6 @@ import {
findNodeHandle,
Platform,
NativeModules,
- ViewPropTypes,
requireNativeComponent,
View,
ActivityIndicator,
@@ -14,6 +13,7 @@ import {
PermissionsAndroid,
} from 'react-native';
+import ViewPropTypes from 'deprecated-react-native-prop-types';
import type { FaceFeature } from './FaceDetector';
const Rationale = PropTypes.shape({

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

View File

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

View File

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

View File

@@ -530,47 +530,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 80%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 23%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 58%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 45%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 45%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 77%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 44%
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 26%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Villaverde](mailto:teko.gr@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 25%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 95%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 29%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 79%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 56%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 55%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 51%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 37%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 66%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [KaneGreen](mailto:737445366KG@Gmail.com) | 95%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 80%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 36%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 72%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Dmitriy Q](mailto:krotesk@mail.ru) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 65%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [wh201906](mailto:wh201906@yandex.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 89%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -6,7 +6,6 @@ const tasks = {
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
completePublishAll: {
fn: async () => {
await utils.execCommandVerbose('git', ['add', '-A']);
await utils.execCommandVerbose('git', ['commit', '-m', 'Releasing sub-packages']);
@@ -17,6 +16,10 @@ const tasks = {
// https://github.com/lerna/lerna/issues/2788
await utils.execCommandVerbose('lerna', ['publish', 'from-package', '-y', '--no-verify-access']);
await utils.execCommandVerbose('yarn', ['install']);
await utils.execCommandVerbose('git', ['add', '-A']);
await utils.execCommandVerbose('git', ['commit', '-m', 'Lock file']);
await utils.execCommandVerbose('git', ['push']);
},
},

View File

@@ -65,21 +65,21 @@
},
"devDependencies": {
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.48.0",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"cspell": "5.21.2",
"eslint": "8.31.0",
"eslint-interactive": "10.3.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.27.4",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react": "7.32.0",
"fs-extra": "11.1.0",
"glob": "8.0.3",
"glob": "8.1.0",
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "13.1.0",
"madge": "5.0.1",
"lint-staged": "13.1.2",
"madge": "6.0.0",
"npm-package-json-lint": "6.4.0",
"typedoc": "0.17.8",
"typescript": "4.9.4"
@@ -90,5 +90,8 @@
"node-gyp": "9.3.1",
"nodemon": "2.0.20"
},
"packageManager": "yarn@3.3.1"
"packageManager": "yarn@3.3.1",
"resolutions": {
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch"
}
}

View File

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

View File

@@ -246,6 +246,7 @@ class Application extends BaseApplication {
showConsole: () => {},
maximizeConsole: () => {},
stdout: text => {
// eslint-disable-next-line no-console
console.info(text);
},
fullScreen: () => {},
@@ -407,6 +408,7 @@ class Application extends BaseApplication {
if (this.showStackTraces_) {
console.error(error);
} else {
// eslint-disable-next-line no-console
console.info(error.message);
}
process.exit(1);

View File

@@ -125,14 +125,14 @@ async function handleAutocompletionPromise(line) {
}
function handleAutocompletion(str, callback) {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
handleAutocompletionPromise(str).then(function(res) {
handleAutocompletionPromise(str).then((res) => {
callback(undefined, res);
});
}
function toCommandLine(args) {
if (Array.isArray(args)) {
return args
.map(function(a) {
.map((a) => {
if (a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
return `'${a}'`;
} else if (a.indexOf('\'') !== -1) {

View File

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

View File

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

View File

@@ -131,6 +131,7 @@ async function main() {
const commandsText = commandBlocks.join('\n\n');
const footerText = getFooter();
// eslint-disable-next-line no-console
console.info(`${headerText}\n\n` + 'USAGE' + `\n\n${commandsText}\n\n${footerText}`);
}

View File

@@ -1,5 +1,7 @@
'use strict';
/* eslint-disable no-console */
const fs = require('fs-extra');
const Logger = require('@joplin/lib/Logger').default;
const { dirname } = require('@joplin/lib/path-utils');

View File

@@ -82,6 +82,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
const options = cmd.options();
const booleanFlags = [];
const aliases = {};
const flagSpecs = [];
for (let i = 0; i < options.length; i++) {
if (options[i].length !== 2) throw new Error(`Invalid options: ${options[i]}`);
let flags = options[i][0];
@@ -96,6 +97,8 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
if (flags.short && flags.long) {
aliases[flags.long] = [flags.short];
}
flagSpecs.push(flags);
}
const args = yargParser(argv, {
@@ -121,6 +124,19 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
argOptions[key] = args[key];
}
for (const [key, value] of Object.entries(argOptions)) {
const flagSpec = flagSpecs.find(s => {
return s.short === key || s.long === key;
});
if (flagSpec?.arg?.required) {
// If a flag is required, and no value is provided for it, Yargs
// sets the value to `true`.
if (value === true) {
throw new Error(_('Missing required flag value: %s', `-${flagSpec.short} <${flagSpec.arg.name}>`));
}
}
}
output.options = argOptions;
return output;

View File

@@ -39,9 +39,9 @@ class Command extends BaseCommand {
let settingsObj;
try {
settingsObj = JSON.parse(json);
} catch (err) {
} catch (error) {
isSettled = true;
return reject(new Error(`Invalid JSON passed to config --import: \n${err.message}.`));
return reject(new Error(`Invalid JSON passed to config --import: \n${error.message}.`));
}
if (settingsObj) {
Object.entries(settingsObj)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,19 +18,19 @@ function itemCount(args: any) {
}
class Command extends BaseCommand {
usage() {
public usage() {
return 'testing <command> [arg0]';
}
description() {
public description() {
return 'testing';
}
enabled() {
public enabled() {
return false;
}
options(): any[] {
public options(): any[] {
return [
['--folder-count <count>', 'Folders to create'],
['--note-count <count>', 'Notes to create'],
@@ -40,7 +40,7 @@ class Command extends BaseCommand {
];
}
async action(args: any) {
public async action(args: any) {
const { command, options } = args;
if (command === 'populate') {
@@ -118,6 +118,7 @@ class Command extends BaseCommand {
}
await Promise.all(promises);
// eslint-disable-next-line no-console
console.info(await api.exec('GET', 'api/items/root:/testing:'));
}

View File

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

View File

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

View File

@@ -75,14 +75,14 @@ if (process.platform === 'win32') {
output: process.stdout,
});
rl.on('SIGINT', function() {
rl.on('SIGINT', () => {
process.emit('SIGINT');
});
}
process.stdout.on('error', function(err) {
process.stdout.on('error', (error) => {
// https://stackoverflow.com/questions/12329816/error-write-epipe-when-piping-node-output-to-head#15884508
if (err.code === 'EPIPE') {
if (error.code === 'EPIPE') {
process.exit(0);
}
});

View File

@@ -11,6 +11,7 @@ function createConsoleWrapper(pluginId: string) {
const wrapper: any = {};
for (const n in console) {
// eslint-disable-next-line no-console
if (!console.hasOwnProperty(n)) continue;
wrapper[n] = (...args: any[]) => {
const newArgs = args.slice();
@@ -34,7 +35,7 @@ export default class PluginRunner extends BasePluginRunner {
private eventHandlers_: EventHandlers = {};
private activeSandboxCalls_: any = {};
constructor() {
public constructor() {
super();
this.eventHandler = this.eventHandler.bind(this);
@@ -63,7 +64,7 @@ export default class PluginRunner extends BasePluginRunner {
};
}
async run(plugin: Plugin, sandbox: Global): Promise<void> {
public async run(plugin: Plugin, sandbox: Global): Promise<void> {
return new Promise((resolve: Function, reject: Function) => {
const onStarted = () => {
plugin.off('started', onStarted);

View File

@@ -34,7 +34,7 @@
],
"owner": "Laurent Cozic"
},
"version": "2.10.0",
"version": "2.10.3",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
@@ -51,7 +51,7 @@
"keytar": "7.9.0",
"md5": "2.3.0",
"node-rsa": "1.1.1",
"open": "8.4.0",
"open": "8.4.2",
"proper-lockfile": "4.1.2",
"read-chunk": "2.1.0",
"server-destroy": "1.0.1",
@@ -70,10 +70,11 @@
"devDependencies": {
"@joplin/tools": "~2.10",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.2.5",
"@types/jest": "29.2.6",
"@types/node": "18.11.18",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.3.1",
"jest": "29.4.3",
"temp": "0.9.4",
"typescript": "4.9.4"
}

View File

@@ -12,7 +12,7 @@ const shim = require('@joplin/lib/shim').default;
const HtmlToHtml = require('@joplin/renderer/HtmlToHtml').default;
const { enexXmlToMd } = require('@joplin/lib/import-enex-md-gen.js');
describe('HtmlToHtml', function() {
describe('HtmlToHtml', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
@@ -49,6 +49,7 @@ describe('HtmlToHtml', function() {
}
if (actualHtml !== expectedHtml) {
/* eslint-disable no-console */
console.info('');
console.info(`Error converting file: ${htmlSourceFilename}`);
console.info('--------------------------------- Got:');
@@ -59,6 +60,7 @@ describe('HtmlToHtml', function() {
console.info(expectedHtml.split('\n'));
console.info('--------------------------------------------');
console.info('');
/* eslint-enable */
expect(false).toBe(true);
// return;

View File

@@ -3,7 +3,7 @@ const os = require('os');
const { filename } = require('@joplin/lib/path-utils');
import HtmlToMd from '@joplin/lib/HtmlToMd';
describe('HtmlToMd', function() {
describe('HtmlToMd', () => {
it('should convert from Html to Markdown', (async () => {
const basePath = `${__dirname}/html_to_md`;
@@ -57,6 +57,7 @@ describe('HtmlToMd', function() {
result.push('--------------------------------------------');
result.push('');
// eslint-disable-next-line no-console
console.info(result.join('\n'));
// console.info('');

View File

@@ -1,7 +1,7 @@
const MarkupToHtml = require('@joplin/renderer/MarkupToHtml').default;
describe('MarkupToHtml', function() {
describe('MarkupToHtml', () => {
it('should strip markup', (async () => {
const service = new MarkupToHtml();

View File

@@ -16,7 +16,7 @@ function newTestMdToHtml(options: any = null) {
return new MdToHtml(options);
}
describe('MdToHtml', function() {
describe('MdToHtml', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
@@ -72,6 +72,7 @@ describe('MdToHtml', function() {
'',
];
// eslint-disable-next-line no-console
console.info(msg.join('\n'));
expect(false).toBe(true);

View File

@@ -22,7 +22,7 @@ const goToNote = (testApp, note) => {
testApp.dispatch({ type: 'NOTE_SELECT', id: note.id });
};
describe('feature_NoteHistory', function() {
describe('feature_NoteHistory', () => {
beforeEach(async () => {
testApp = new TestApp();
await testApp.start(['--no-welcome']);

View File

@@ -8,7 +8,7 @@ const time = require('@joplin/lib/time').default;
let testApp = null;
describe('integration_NoteList', function() {
describe('integration_NoteList', () => {
beforeEach(async () => {
testApp = new TestApp();

View File

@@ -22,7 +22,7 @@ const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids.js');
let testApp = null;
describe('integration_ShowAllNotes', function() {
describe('integration_ShowAllNotes', () => {
beforeEach(async () => {
testApp = new TestApp();

View File

@@ -8,7 +8,7 @@ const time = require('@joplin/lib/time').default;
let testApp = null;
describe('integration_TagList', function() {
describe('integration_TagList', () => {
beforeEach(async () => {
testApp = new TestApp();

View File

@@ -11,7 +11,7 @@ function describeIfCompatible(name: string, fn: any, elseFn: any) {
}
}
describeIfCompatible('services_KeychainService', function() {
describeIfCompatible('services_KeychainService', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1, { keychainEnabled: true });

View File

@@ -29,7 +29,7 @@ function newPluginService(appVersion: string = '1.4') {
return service;
}
describe('services_PluginService', function() {
describe('services_PluginService', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -8,7 +8,7 @@ async function newRepoApi(): Promise<RepositoryApi> {
return repo;
}
describe('services_plugins_RepositoryApi', function() {
describe('services_plugins_RepositoryApi', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -24,7 +24,7 @@ function newPluginService(appVersion: string = '1.4') {
return service;
}
describe('defaultPluginsUtils', function() {
describe('defaultPluginsUtils', () => {
const pluginsId = ['joplin.plugin.ambrt.backlinksToNote', 'org.joplinapp.plugins.ToggleSidebars'];

View File

@@ -1,7 +1,7 @@
const sandboxProxy = require('@joplin/lib/services/plugins/sandboxProxy');
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
describe('services_plugins_sandboxProxy', function() {
describe('services_plugins_sandboxProxy', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-console */
// This script can be used to simulate a running production environment, by
// having multiple users in parallel changing notes and synchronising.
//

View File

@@ -50,7 +50,7 @@ async function browserGetZoom(tabId) {
});
}
browser_.runtime.onInstalled.addListener(function() {
browser_.runtime.onInstalled.addListener(() => {
if (window.joplinEnv() === 'dev') {
browser_.browserAction.setIcon({
path: 'icons/32-dev.png',
@@ -165,7 +165,7 @@ async function sendClipMessage(clipType) {
}
}
browser_.commands.onCommand.addListener(function(command) {
browser_.commands.onCommand.addListener((command) => {
// We could enumerate these twice, but since we're in here first,
// why not save ourselves the trouble with this convention
if (command.startsWith('clip')) {

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-console */
(function() {
if (window.jopext_hasRun) return;

View File

@@ -1,5 +1,7 @@
'use strict';
/* eslint-disable no-console */
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
@@ -104,9 +106,9 @@ checkBrowsers(paths.appPath, isInteractive)
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
devServer.listen(port, HOST, error => {
if (error) {
return console.log(error);
}
if (isInteractive) {
clearConsole();
@@ -128,16 +130,16 @@ checkBrowsers(paths.appPath, isInteractive)
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
['SIGINT', 'SIGTERM'].forEach((sig) => {
process.on(sig, () => {
devServer.close();
process.exit();
});
});
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
.catch(error => {
if (error && error.message) {
console.log(error.message);
}
process.exit(1);
});

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-console */
const { randomClipperPort } = require('./randomClipperPort');
function msleep(ms) {

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-console */
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
@@ -114,7 +116,13 @@ async function main() {
console.info('Popup: Creating React app...');
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
ReactDOM.render(
<div style = {{ maxHeight: screen.height * 0.65, overflowY: 'scroll' }}>
<Provider store={store}>
<App />
</Provider>
</div>,
document.getElementById('root'));
}
main().catch((error) => {

View File

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

View File

@@ -40,6 +40,7 @@ export default class InteropServiceHelper {
const service = InteropService.instance();
const result = await service.export(fullExportOptions);
// eslint-disable-next-line no-console
console.info('Export HTML result: ', result);
return tempFile;
}
@@ -190,6 +191,7 @@ export default class InteropServiceHelper {
try {
const result = await service.export(exportOptions);
// eslint-disable-next-line no-console
console.info('Export result: ', result);
} catch (error) {
console.error(error);

View File

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

View File

@@ -38,6 +38,7 @@ export interface AppState extends State {
watchedResources: any;
mainLayout: LayoutItem;
dialogs: AppStateDialog[];
isResettingLayout: boolean;
}
export function createAppDefaultState(windowContentSize: any, resourceEditWatcherDefaultState: any): AppState {
@@ -60,6 +61,7 @@ export function createAppDefaultState(windowContentSize: any, resourceEditWatche
mainLayout: null,
startupPluginsLoaded: false,
dialogs: [],
isResettingLayout: false,
...resourceEditWatcherDefaultState,
};
}
@@ -308,7 +310,15 @@ export default function(state: AppState, action: any) {
};
break;
case 'RESET_LAYOUT':
newState = {
...state,
isResettingLayout: action.value,
};
break;
}
} catch (error) {
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
throw error;

View File

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

View File

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

View File

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

View File

@@ -55,7 +55,7 @@ export interface PluginItem {
hasBeenUpdated: boolean;
}
const CellRoot = styled.div<{isCompatible: boolean}>`
const CellRoot = styled.div<{ isCompatible: boolean }>`
display: flex;
box-sizing: border-box;
background-color: ${props => props.theme.backgroundColor};
@@ -104,7 +104,7 @@ const DevModeLabel = styled.div`
color: ${props => props.theme.color};
`;
const StyledNameAndVersion = styled.div<{mb: any}>`
const StyledNameAndVersion = styled.div<{ mb: any }>`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize}px;

View File

@@ -20,7 +20,7 @@ const { space } = require('styled-system');
const logger = Logger.create('PluginState');
const maxWidth: number = 320;
const maxWidth = 320;
const Root = styled.div`
display: flex;
@@ -225,7 +225,7 @@ export default function(props: Props) {
];
const menu = bridge().Menu.buildFromTemplate(template);
menu.popup(bridge().window());
menu.popup({ window: bridge().window() });
}, [onInstall, onBrowsePlugins]);
const onSearchQueryChange = useCallback((event: OnChangeEvent) => {

View File

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

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import versionInfo from '@joplin/lib/versionInfo';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';
import restart from '../services/restart';
const packageInfo = require('../packageInfo.js');
@@ -21,6 +21,7 @@ interface State {
error: Error;
errorInfo: ErrorInfo;
pluginInfos: PluginInfo[];
plugins: Plugins;
}
interface Props {
@@ -29,14 +30,16 @@ interface Props {
export default class ErrorBoundary extends React.Component<Props, State> {
public state: State = { error: null, errorInfo: null, pluginInfos: [] };
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
componentDidCatch(error: any, errorInfo: ErrorInfo) {
public componentDidCatch(error: any, errorInfo: ErrorInfo) {
if (typeof error === 'string') error = { message: error };
const pluginInfos: PluginInfo[] = [];
let plugins: Plugins = {};
try {
const service = PluginService.instance();
plugins = service.plugins;
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
for (const pluginId in pluginSettings) {
const plugin = PluginService.instance().pluginById(pluginId);
@@ -52,10 +55,10 @@ export default class ErrorBoundary extends React.Component<Props, State> {
console.error('Could not get plugin info:', error);
}
this.setState({ error, errorInfo, pluginInfos });
this.setState({ error, errorInfo, pluginInfos, plugins });
}
componentDidMount() {
public componentDidMount() {
const onAppClose = () => {
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: true,
@@ -65,12 +68,12 @@ export default class ErrorBoundary extends React.Component<Props, State> {
ipcRenderer.on('appClose', onAppClose);
}
renderMessage() {
public renderMessage() {
const message = this.props.message || 'Joplin encountered a fatal error and could not continue.';
return <p>{message}</p>;
}
render() {
public render() {
if (this.state.error) {
const safeMode_click = async () => {
Setting.setValue('isSafeMode', true);
@@ -91,7 +94,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
output.push(
<section key="versionInfo">
<h2>Version info</h2>
<pre>{versionInfo(packageInfo).message}</pre>
<pre>{versionInfo(packageInfo, this.state.plugins).message}</pre>
</section>
);

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,8 +61,8 @@ export const KeymapConfigScreen = ({ themeId }: KeymapConfigScreenProps) => {
try {
const keymapFile = await shim.fsDriver().readFile(actualFilePath, 'utf-8');
overrideKeymapItems(JSON.parse(keymapFile));
} catch (err) {
bridge().showErrorMessageBox(_('Error: %s', err.message));
} catch (error) {
bridge().showErrorMessageBox(_('Error: %s', error.message));
}
}
};
@@ -77,8 +77,8 @@ export const KeymapConfigScreen = ({ themeId }: KeymapConfigScreenProps) => {
try {
// KeymapService is already synchronized with the in-state keymap
await keymapService.saveCustomKeymap(filePath);
} catch (err) {
bridge().showErrorMessageBox(err.message);
} catch (error) {
bridge().showerrororMessageBox(error.message);
}
}
};

View File

@@ -62,11 +62,11 @@ const useKeymap = (): [
// Then, update the state with the data from KeymapService
// Side-effect: Changes will also be saved to the disk
setKeymapItems(keymapService.getKeymapItems());
} catch (err) {
} catch (error) {
// oldKeymapItems includes even the unchanged keymap items
// However, it is not an issue because the logic accounts for such scenarios
keymapService.overrideKeymap(oldKeymapItems);
throw err;
throw error;
}
};
@@ -80,8 +80,8 @@ const useKeymap = (): [
keymapService.overrideKeymap(keymapItems);
await keymapService.saveCustomKeymap();
setKeymapError(null);
} catch (err) {
const error = new Error(`Could not save file: ${err.message}`);
} catch (error) {
error.message = `Could not save file: ${error.message}`;
setKeymapError(error);
}
}

View File

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

View File

@@ -20,6 +20,7 @@ import * as openTag from './openTag';
import * as print from './print';
import * as renameFolder from './renameFolder';
import * as renameTag from './renameTag';
import * as resetLayout from './resetLayout';
import * as revealResourceFile from './revealResourceFile';
import * as search from './search';
import * as setTags from './setTags';
@@ -61,6 +62,7 @@ const index:any[] = [
print,
renameFolder,
renameTag,
resetLayout,
revealResourceFile,
search,
setTags,

View File

@@ -14,7 +14,6 @@ export const runtime = (): CommandRuntime => {
const resource = await Resource.load(resourceId);
if (!resource) throw new Error(`No such resource: ${resourceId}`);
if (resource.mime !== 'application/pdf') throw new Error(`Not a PDF: ${resource.mime}`);
console.log('Opening PDF', resource);
context.dispatch({
type: 'DIALOG_OPEN',
name: 'pdfViewer',

View File

@@ -0,0 +1,25 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import dialogs from '../../dialogs';
export const declaration: CommandDeclaration = {
name: 'resetLayout',
label: () => _('Reset application layout'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext) => {
const message = _('Are you sure you want to return to the default layout? The current layout configuration will be lost.');
const isConfirmed = await dialogs.confirm(message);
if (!isConfirmed) return;
context.dispatch({
type: 'RESET_LAYOUT',
value: true,
});
},
};
};

View File

@@ -20,7 +20,7 @@ export const runtime = (): CommandRuntime => {
const menuItems = SpellCheckerService.instance().spellCheckerConfigMenuItems(selectedLanguages, useSpellChecker);
const menu = Menu.buildFromTemplate(menuItems as any);
menu.popup(bridge().window());
menu.popup({ window: bridge().window() });
},
mapStateToTitle(state: AppState): string {

View File

@@ -21,6 +21,7 @@ import checkForUpdates from '../checkForUpdates';
const { connect } = require('react-redux');
import { reg } from '@joplin/lib/registry';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const packageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;
@@ -270,6 +271,7 @@ function useMenu(props: Props) {
const service = InteropService.instance();
try {
const result = await service.import(importOptions);
// eslint-disable-next-line no-console
console.info('Import result: ', result);
} catch (error) {
bridge().showErrorMessageBox(error.message);
@@ -485,7 +487,8 @@ function useMenu(props: Props) {
}
function _showAbout() {
const v = versionInfo(packageInfo);
const v = versionInfo(packageInfo, PluginService.instance().plugins);
const copyToClipboard = bridge().showMessageBox(v.message, {
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,
@@ -509,14 +512,14 @@ function useMenu(props: Props) {
// Issue: https://github.com/laurent22/joplin/issues/934
submenu: [{
label: _('About Joplin'),
visible: shim.isMac() ? true : false,
visible: !!shim.isMac(),
click: () => _showAbout(),
}, {
type: 'separator',
visible: shim.isMac() ? true : false,
visible: !!shim.isMac(),
}, {
label: _('Preferences...'),
visible: shim.isMac() ? true : false,
visible: !!shim.isMac(),
accelerator: shim.isMac() && keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
@@ -526,11 +529,11 @@ function useMenu(props: Props) {
},
}, {
label: _('Check for updates...'),
visible: shim.isMac() ? true : false,
visible: !!shim.isMac(),
click: () => _checkForUpdates(),
}, {
type: 'separator',
visible: shim.isMac() ? true : false,
visible: !!shim.isMac(),
},
shim.isMac() ? noItem : newNoteItem,
shim.isMac() ? noItem : newTodoItem,
@@ -538,14 +541,14 @@ function useMenu(props: Props) {
shim.isMac() ? noItem : newSubFolderItem,
{
type: 'separator',
visible: shim.isMac() ? false : true,
visible: !shim.isMac(),
}, {
label: _('Import'),
visible: shim.isMac() ? false : true,
visible: !shim.isMac(),
submenu: importItems,
}, {
label: _('Export all'),
visible: shim.isMac() ? false : true,
visible: !shim.isMac(),
submenu: exportItems,
}, {
type: 'separator',
@@ -583,7 +586,7 @@ function useMenu(props: Props) {
const rootMenuFileMacOs = {
label: _('&File'),
visible: shim.isMac() ? true : false,
visible: !!shim.isMac(),
submenu: [
newNoteItem,
newTodoItem,
@@ -632,6 +635,7 @@ function useMenu(props: Props) {
menuItemDic.textCopy,
menuItemDic.textCut,
menuItemDic.textPaste,
menuItemDic.pasteAsText,
menuItemDic.textSelectAll,
separator(),
// Using the generic "undo"/"redo" roles mean the menu
@@ -671,6 +675,7 @@ function useMenu(props: Props) {
label: _('&View'),
submenu: [
menuItemDic.toggleLayoutMoveMode,
menuItemDic.resetLayout,
separator(),
menuItemDic.toggleSideBar,
menuItemDic.toggleNoteList,
@@ -783,12 +788,15 @@ function useMenu(props: Props) {
}, {
label: _('Joplin Forum'),
click() { void bridge().openExternal('https://discourse.joplinapp.org'); },
}, {
label: _('Join us on Twitter'),
click() { void bridge().openExternal('https://twitter.com/joplinapp'); },
}, {
label: _('Make a donation'),
click() { void bridge().openExternal('https://joplinapp.org/donate/'); },
}, {
label: _('Check for updates...'),
visible: shim.isMac() ? false : true,
visible: !shim.isMac(),
click: () => _checkForUpdates(),
},
separator(),
@@ -810,10 +818,10 @@ function useMenu(props: Props) {
{
type: 'separator',
visible: shim.isMac() ? false : true,
visible: !shim.isMac(),
}, {
label: _('About Joplin'),
visible: shim.isMac() ? false : true,
visible: !shim.isMac(),
click: () => _showAbout(),
}],
},

View File

@@ -42,7 +42,7 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) {
const multiNotesButton_click = (item: any) => {
if (item.submenu) {
item.submenu.popup(bridge().window());
item.submenu.popup({ window: bridge().window() });
} else {
item.click();
}

View File

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

View File

@@ -31,7 +31,8 @@ import dialogs from '../../../dialogs';
import convertToScreenCoordinates from '../../../utils/convertToScreenCoordinates';
import { MarkupToHtml } from '@joplin/renderer';
const { clipboard } = require('electron');
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
const debounce = require('debounce');
import shared from '@joplin/lib/components/shared/note-screen-shared';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import { reg } from '@joplin/lib/registry';
@@ -275,11 +276,22 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const editorCutText = useCallback(() => {
if (editorRef.current) {
const selections = editorRef.current.getSelections();
if (selections.length > 0) {
if (selections.length > 0 && selections[0]) {
clipboard.writeText(selections[0]);
// Easy way to wipe out just the first selection
selections[0] = '';
editorRef.current.replaceSelections(selections);
} else {
const cursor = editorRef.current.getCursor();
const line = editorRef.current.getLine(cursor.line);
clipboard.writeText(`${line}\n`);
const startLine = editorRef.current.getCursor('head');
startLine.ch = 0;
const endLine = {
line: startLine.line + 1,
ch: 0,
};
editorRef.current.replaceRange('', startLine, endLine);
}
}
}, []);
@@ -287,8 +299,17 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const editorCopyText = useCallback(() => {
if (editorRef.current) {
const selections = editorRef.current.getSelections();
if (selections.length > 0) {
// Handle the case when there is a selection - copy the selection to the clipboard
// When there is no selection, the selection array contains an empty string.
if (selections.length > 0 && selections[0]) {
clipboard.writeText(selections[0]);
} else {
// This is the case when there is no selection - copy the current line to the clipboard
const cursor = editorRef.current.getCursor();
const line = editorRef.current.getLine(cursor.line);
clipboard.writeText(line);
}
}
}, []);
@@ -342,7 +363,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
let cancelled = false;
async function loadScripts() {
const scriptsToLoad: {src: string; id: string; loaded: boolean}[] = [
const scriptsToLoad: { src: string; id: string; loaded: boolean }[] = [
{
src: `${bridge().vendorDir()}/lib/codemirror/addon/dialog/dialog.css`,
id: 'codemirrorDialogStyle',
@@ -404,7 +425,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const maxWidthCss = props.contentMaxWidth ? `
margin-right: auto !important;
margin-left: auto !important;
max-width: ${props.contentMaxWidth}px !important;
max-width: ${props.contentMaxWidth}px !important;
` : '';
const element = document.createElement('style');
@@ -673,7 +694,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}, [renderedBody, webviewReady]);
useEffect(() => {
if (!props.searchMarkers) return;
if (!props.searchMarkers) return () => {};
// If there is a currently active search, it's important to re-search the text as the user
// types. However this is slow for performance so we ONLY want it to happen when there is
@@ -688,11 +709,19 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
webviewRef.current.send('setMarkers', props.searchMarkers.keywords, props.searchMarkers.options);
if (editorRef.current) {
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
// Fixes https://github.com/laurent22/joplin/issues/7565
const debouncedMarkers = debounce(() => {
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
props.setLocalSearchResultCount(matches);
props.setLocalSearchResultCount(matches);
}, 50);
debouncedMarkers();
return () => {
debouncedMarkers.clear();
};
}
}
return () => {};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [props.searchMarkers, previousSearchMarkers, props.setLocalSearchResultCount, props.content, previousContent, renderedBody, previousRenderedBody, renderedBody]);
@@ -851,7 +880,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
function renderEditor() {
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '<>()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '``()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
return (
<div style={cellEditorStyle}>
@@ -909,4 +938,3 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
export default forwardRef(CodeMirror);

View File

@@ -128,8 +128,18 @@ export default function useEditorSearch(CodeMirror: any) {
// We only want to scroll the first keyword into view in the case of a multi keyword search
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
if (match) marks.push(match);
try {
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
if (match) marks.push(match);
} catch (error) {
if (error.name !== 'SyntaxError') {
throw error;
}
// An error of 'Regular expression too large' might occour in the markJs library
// when the input is really big, this catch is here to avoid the application crashing
// https://github.com/laurent22/joplin/issues/7634
console.error('Error while trying to highlight words from search: ', error);
}
}
setMarkers(marks);

View File

@@ -1,7 +1,7 @@
// Helper commands added to the the CodeMirror instance
export default function useJoplinCommands(CodeMirror: any) {
CodeMirror.defineExtension('commandExists', function(name: string) {
CodeMirror.defineExtension('commandExists', (name: string) => {
return !!CodeMirror.commands[name];
});
}

View File

@@ -93,7 +93,7 @@ export default function useKeymap(CodeMirror: any) {
}
CodeMirror.defineExtension('supportsCommand', function(cmd: EditorCommand) {
CodeMirror.defineExtension('supportsCommand', (cmd: EditorCommand) => {
return isEditorCommand(cmd.name) && editorCommandToCodeMirror(cmd.name) in CodeMirror.commands;
});

View File

@@ -24,6 +24,7 @@ import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
import { themeStyle } from '@joplin/lib/theme';
import { loadScript } from '../../../utils/loadScript';
import bridge from '../../../../services/bridge';
import { TinyMceEditorEvents } from './utils/types';
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
@@ -76,6 +77,16 @@ function stripMarkup(markupLanguage: number, markup: string, options: any = null
return markupToHtml_.stripMarkup(markupLanguage, markup, options);
}
function createSyntheticClipboardEventWithoutHTML(): ClipboardEvent {
const clipboardData = new DataTransfer();
for (const format of clipboard.availableFormats()) {
if (format !== 'text/html') {
clipboardData.setData(format, clipboard.read(format));
}
}
return new ClipboardEvent('paste', { clipboardData });
}
interface TinyMceCommand {
name: string;
value?: any;
@@ -102,7 +113,7 @@ interface LastOnChangeEventInfo {
let loadedCssFiles_: string[] = [];
let loadedJsFiles_: string[] = [];
let dispatchDidUpdateIID_: any = null;
let changeId_: number = 1;
let changeId_ = 1;
const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const [editor, setEditor] = useState(null);
@@ -159,7 +170,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const nodeName = event.target ? event.target.nodeName : '';
if (nodeName === 'INPUT' && event.target.getAttribute('type') === 'checkbox') {
editor.fire('joplinChange');
editor.fire(TinyMceEditorEvents.JoplinChange);
dispatchDidUpdate(editor);
}
@@ -251,7 +262,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
},
replaceSelection: (value: any) => {
editor.selection.setContent(value);
editor.fire('joplinChange');
editor.fire(TinyMceEditorEvents.JoplinChange);
dispatchDidUpdate(editor);
// It doesn't make sense but it seems calling setContent
@@ -260,6 +271,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
// https://github.com/tinymce/tinymce/issues/3745
window.requestAnimationFrame(() => editor.undoManager.add());
},
pasteAsText: () => editor.fire(TinyMceEditorEvents.PasteAsText),
};
if (additionalCommands[cmd.name]) {
@@ -339,6 +351,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
continue;
}
// eslint-disable-next-line no-console
console.info('Loading script', s.src);
await loadScript(s);
@@ -661,9 +674,9 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
props_onDrop.current(event);
});
editor.on('ObjectResized', function(event: any) {
editor.on('ObjectResized', (event: any) => {
if (event.target.nodeName === 'IMG') {
editor.fire('joplinChange');
editor.fire(TinyMceEditorEvents.JoplinChange);
dispatchDidUpdate(editor);
}
});
@@ -972,6 +985,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
}
const onSetAttrib = (event: any) => {
// Dispatch onChange when a link is edited
if (event.attrElm[0].nodeName === 'A') {
if (event.attrName === 'title' || event.attrName === 'href' || event.attrName === 'rel') {
onChangeHandler();
}
}
};
// Keypress means that a printable key (letter, digit, etc.) has been
// pressed so we want to always trigger onChange in this case
function onKeypress() {
@@ -992,7 +1014,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
}
async function onPaste(event: any) {
async function onPaste(event: ClipboardEvent) {
// We do not use the default pasting behaviour because the input has
// to be processed in various ways.
event.preventDefault();
@@ -1070,33 +1092,41 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
}
editor.on('keyup', onKeyUp);
editor.on('keydown', onKeyDown);
editor.on('keypress', onKeypress);
editor.on('paste', onPaste);
editor.on('copy', onCopy);
async function onPasteAsText() {
await onPaste(createSyntheticClipboardEventWithoutHTML());
}
editor.on(TinyMceEditorEvents.KeyUp, onKeyUp);
editor.on(TinyMceEditorEvents.KeyDown, onKeyDown);
editor.on(TinyMceEditorEvents.KeyPress, onKeypress);
editor.on(TinyMceEditorEvents.Paste, onPaste);
editor.on(TinyMceEditorEvents.PasteAsText, onPasteAsText);
editor.on(TinyMceEditorEvents.Copy, onCopy);
// `compositionend` means that a user has finished entering a Chinese
// (or other languages that require IME) character.
editor.on('compositionend', onChangeHandler);
editor.on('cut', onCut);
editor.on('joplinChange', onChangeHandler);
editor.on('Undo', onChangeHandler);
editor.on('Redo', onChangeHandler);
editor.on('ExecCommand', onExecCommand);
editor.on(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
editor.on(TinyMceEditorEvents.Cut, onCut);
editor.on(TinyMceEditorEvents.JoplinChange, onChangeHandler);
editor.on(TinyMceEditorEvents.Undo, onChangeHandler);
editor.on(TinyMceEditorEvents.Redo, onChangeHandler);
editor.on(TinyMceEditorEvents.ExecCommand, onExecCommand);
editor.on(TinyMceEditorEvents.SetAttrib, onSetAttrib);
return () => {
try {
editor.off('keyup', onKeyUp);
editor.off('keydown', onKeyDown);
editor.off('keypress', onKeypress);
editor.off('paste', onPaste);
editor.off('copy', onCopy);
editor.off('compositionend', onChangeHandler);
editor.off('cut', onCut);
editor.off('joplinChange', onChangeHandler);
editor.off('Undo', onChangeHandler);
editor.off('Redo', onChangeHandler);
editor.off('ExecCommand', onExecCommand);
editor.off(TinyMceEditorEvents.KeyUp, onKeyUp);
editor.off(TinyMceEditorEvents.KeyDown, onKeyDown);
editor.off(TinyMceEditorEvents.KeyPress, onKeypress);
editor.off(TinyMceEditorEvents.Paste, onPaste);
editor.off(TinyMceEditorEvents.PasteAsText, onPasteAsText);
editor.off(TinyMceEditorEvents.Copy, onCopy);
editor.off(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
editor.off(TinyMceEditorEvents.Cut, onCut);
editor.off(TinyMceEditorEvents.JoplinChange, onChangeHandler);
editor.off(TinyMceEditorEvents.Undo, onChangeHandler);
editor.off(TinyMceEditorEvents.Redo, onChangeHandler);
editor.off(TinyMceEditorEvents.ExecCommand, onExecCommand);
editor.off(TinyMceEditorEvents.SetAttrib, onSetAttrib);
} catch (error) {
console.warn('Error removing events', error);
}

View File

@@ -1,5 +1,6 @@
import { _ } from '@joplin/lib/locale';
import { MarkupToHtml } from '@joplin/renderer';
import { TinyMceEditorEvents } from './types';
const taboverride = require('taboverride');
interface SourceInfo {
@@ -102,7 +103,7 @@ export default function openEditDialog(editor: any, markupToHtml: any, dispatchD
}
dialogApi.close();
editor.fire('joplinChange');
editor.fire(TinyMceEditorEvents.JoplinChange);
dispatchDidUpdate(editor);
},
onClose: () => {

View File

@@ -51,7 +51,7 @@ export default function(editor: any) {
editor.execCommand('mceToggleFormat', false, def.name);
},
onSetup: function(api: any) {
editor.formatter.formatChanged(def.name, function(state: boolean) {
editor.formatter.formatChanged(def.name, (state: boolean) => {
api.setActive(state);
});
},

View File

@@ -0,0 +1,16 @@
// eslint-disable-next-line import/prefer-default-export
export enum TinyMceEditorEvents {
KeyUp = 'keyup',
KeyDown = 'keydown',
KeyPress = 'keypress',
Paste = 'paste',
PasteAsText = 'pasteAsText',
Copy = 'copy',
CompositionEnd = 'compositionend',
Cut = 'cut',
JoplinChange = 'joplinChange',
Undo = 'Undo',
Redo = 'Redo',
ExecCommand = 'ExecCommand',
SetAttrib = 'SetAttrib',
}

View File

@@ -11,6 +11,7 @@ import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordin
import Setting from '@joplin/lib/models/Setting';
import Resource from '@joplin/lib/models/Resource';
import { TinyMceEditorEvents } from './types';
const menuUtils = new MenuUtils(CommandService.instance());
@@ -77,6 +78,9 @@ export default function(editor: any, plugins: PluginStates, dispatch: Function)
editor.insertContent(content);
},
isReadOnly: false,
fireEditorEvent: (event: TinyMceEditorEvents) => {
editor.fire(event);
},
};
let template = [];
@@ -103,7 +107,7 @@ export default function(editor: any, plugins: PluginStates, dispatch: Function)
template = template.concat(menuUtils.pluginContextMenuItems(plugins, MenuItemLocation.EditorContextMenu));
const menu = bridge().Menu.buildFromTemplate(template);
menu.popup(bridge().window());
menu.popup({ window: bridge().window() });
}
bridge().window().webContents.on('context-menu', onContextMenu);

View File

@@ -1,12 +1,14 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as focusElementNoteBody from './focusElementNoteBody';
import * as focusElementNoteTitle from './focusElementNoteTitle';
import * as pasteAsText from './pasteAsText';
import * as showLocalSearch from './showLocalSearch';
import * as showRevisions from './showRevisions';
const index:any[] = [
focusElementNoteBody,
focusElementNoteTitle,
pasteAsText,
showLocalSearch,
showRevisions,
];

View File

@@ -0,0 +1,16 @@
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
export const declaration: CommandDeclaration = {
name: 'pasteAsText',
label: () => _('Paste as text'),
};
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async () => {
comp.editorRef.current.execCommand({ name: 'pasteAsText' });
},
enabledCondition: 'oneNoteSelected && richTextEditorVisible',
};
};

View File

@@ -2,7 +2,7 @@ import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng, svgDimensions } from './contextMenuUtils';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import Resource from '@joplin/lib/models/Resource';
@@ -10,6 +10,7 @@ import BaseItem from '@joplin/lib/models/BaseItem';
import BaseModel from '@joplin/lib/BaseModel';
import { processPastedHtml } from './resourceHandling';
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
@@ -106,8 +107,10 @@ export function menuItems(dispatch: Function): ContextMenuItems {
if (!options.filename) {
throw new Error('Filename is needed to save as png');
}
// double dimensions to make sure it's always big enough even on hdpi screens
const [width, height] = svgDimensions(document, options.textToCopy).map((x: number) => x * 2 || undefined);
const dataUri = textToDataUri(options.textToCopy, options.mime);
const png = await svgUriToPng(document, dataUri);
const png = await svgUriToPng(document, dataUri, width, height);
const filename = options.filename.replace('.svg', '.png');
await saveFileData(png, filename);
},
@@ -174,6 +177,13 @@ export function menuItems(dispatch: Function): ContextMenuItems {
},
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!clipboard.readText() || !!clipboard.readHTML()),
},
pasteAsText: {
label: _('Paste as text'),
onAction: async (options: ContextMenuOptions) => {
options.fireEditorEvent(TinyMceEditorEvents.PasteAsText);
},
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!clipboard.readText() || !!clipboard.readHTML()),
},
copyLinkUrl: {
label: _('Copy Link Address'),
onAction: async (options: ContextMenuOptions) => {

View File

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

View File

@@ -9,7 +9,7 @@ export default function(dependencies: HookDependencies) {
const { folderId } = dependencies;
const [folder, setFolder] = useState(null);
useEffect(function() {
useEffect(() => {
let cancelled = false;
async function loadFolder() {

View File

@@ -192,7 +192,7 @@ export default function useFormNote(dependencies: HookDependencies) {
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [noteId, isProvisional, formNote]);
const onResourceChange = useCallback(async function(event: any = null) {
const onResourceChange = useCallback(async (event: any = null) => {
const resourceIds = await Note.linkedResourceIds(formNote.body);
if (!event || resourceIds.indexOf(event.id) >= 0) {
clearResourceCache();

View File

@@ -62,6 +62,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
postMessageSyntax: 'ipcProxySendToHost',
splitted: true,
externalAssetsOnly: true,
codeHighlightCacheKey: 'useMarkupToHtml',
}, options));
return result;

View File

@@ -13,6 +13,7 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
const args = event.args;
const arg0 = args && args.length >= 1 ? args[0] : null;
// eslint-disable-next-line no-console
if (msg !== 'percentScroll') console.info(`Got ipc-message: ${msg}`, arg0);
if (msg.indexOf('error:') === 0) {
@@ -41,9 +42,10 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
linkToCopy: arg0.linkToCopy || null,
htmlToCopy: '',
insertContent: () => { console.warn('insertContent() not implemented'); },
fireEditorEvent: () => { console.warn('fireEditorEvent() not implemented'); },
}, dispatch);
menu.popup(bridge().window());
menu.popup({ window: bridge().window() });
} else if (msg.indexOf('#') === 0) {
// This is an internal anchor, which is handled by the WebView so skip this case
} else if (msg === 'contentScriptExecuteCommand') {

View File

@@ -9,6 +9,7 @@ const commandsWithDependencies = [
require('../commands/showLocalSearch'),
require('../commands/focusElementNoteTitle'),
require('../commands/focusElementNoteBody'),
require('../commands/pasteAsText'),
];
interface HookDependencies {

View File

@@ -123,7 +123,7 @@ const NoteListComponent = (props: Props) => {
customCss: props.customCss,
});
menu.popup(bridge().window());
menu.popup({ window: bridge().window() });
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles, props.plugins, props.selectedFolderId, props.customCss]);
const onGlobalDrop_ = () => {
@@ -160,21 +160,27 @@ const NoteListComponent = (props: Props) => {
}
};
const noteItem_noteDrop = async (event: any) => {
if (props.notesParentType !== 'Folder') return;
const canManuallySortNotes = async () => {
if (props.notesParentType !== 'Folder') return false;
if (props.noteSortOrder !== 'order') {
const doIt = await bridge().showConfirmMessageBox(_('To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"', _('Custom order'), _('View'), _('Sort notes by')), {
buttons: [_('Do it now'), _('Cancel')],
});
if (!doIt) return;
if (!doIt) return false;
Setting.setValue('notes.sortOrder.field', 'order');
return;
return false;
}
return true;
};
const noteItem_noteDrop = async (event: any) => {
// TODO: check that parent type is folder
if (!canManuallySortNotes()) {
return;
}
const dt = event.dataTransfer;
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
@@ -182,7 +188,7 @@ const NoteListComponent = (props: Props) => {
const targetNoteIndex = dragTargetNoteIndex_(event);
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex);
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex, props.uncompletedTodosOnTop, props.showCompletedTodos);
};
@@ -269,7 +275,6 @@ const NoteListComponent = (props: Props) => {
onCheckboxClick={noteItem_checkboxClick}
onDragStart={noteItem_dragStart}
onNoteDragOver={noteItem_noteDragOver}
onNoteDrop={noteItem_noteDrop}
onTitleClick={noteItem_titleClick}
onContextMenu={itemContextMenu}
/>;
@@ -340,11 +345,31 @@ const NoteListComponent = (props: Props) => {
return noteIndex;
};
const noteItem_noteMove = async (direction: number) => {
if (!canManuallySortNotes()) {
return;
}
const noteIds = props.selectedNoteIds;
const noteId = noteIds[0];
let targetNoteIndex = BaseModel.modelIndexById(props.notes, noteId);
if ((direction === 1)) {
targetNoteIndex += 2;
}
if ((direction === -1)) {
targetNoteIndex -= 1;
}
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex, props.uncompletedTodosOnTop, props.showCompletedTodos);
};
const onKeyDown = async (event: any) => {
const keyCode = event.keyCode;
const noteIds = props.selectedNoteIds;
if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode === 36)) {
if ((keyCode === 40 || keyCode === 38) && event.altKey) {
// (DOWN / UP) & ALT
await noteItem_noteMove(keyCode === 40 ? 1 : -1);
event.preventDefault();
} else if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode === 36)) {
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(props.notes, noteId);
@@ -500,6 +525,7 @@ const NoteListComponent = (props: Props) => {
style={props.size}
itemRenderer={renderItem}
onKeyDown={onKeyDown}
onNoteDrop={noteItem_noteDrop}
/>
);
};
@@ -528,6 +554,8 @@ const mapStateToProps = (state: AppState) => {
provisionalNoteIds: state.provisionalNoteIds,
isInsertingNotes: state.isInsertingNotes,
noteSortOrder: state.settings['notes.sortOrder.field'],
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
showCompletedTodos: state.settings.showCompletedTodos,
highlightedWords: state.highlightedWords,
plugins: state.pluginService.plugins,
customCss: state.customCss,

View File

@@ -12,6 +12,8 @@ export interface Props {
customCss: string;
notesParentType: string;
noteSortOrder: string;
uncompletedTodosOnTop: boolean;
showCompletedTodos: boolean;
resizableLayoutEventEmitter: any;
isInsertingNotes: boolean;
folders: FolderEntity[];

View File

@@ -7,6 +7,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import { runtime as focusSearchRuntime } from './commands/focusSearch';
import Note from '@joplin/lib/models/Note';
import { notesSortOrderNextField } from '../../services/sortOrder/notesSortOrderUtils';
import { _ } from '@joplin/lib/locale';
const { connect } = require('react-redux');
const styled = require('styled-components').default;
@@ -23,21 +24,24 @@ const StyledRoot = styled.div`
box-sizing: border-box;
height: ${(props: any) => props.height}px;
display: flex;
flex-direction: row;
flex-direction: column;
padding: ${(props: any) => props.theme.mainPadding}px;
background-color: ${(props: any) => props.theme.backgroundColor3};
gap: 5px;
`;
const StyledButton = styled(Button)`
margin-left: 8px;
width: 26px;
width: auto;
height: 26px;
min-width: 26px;
min-height: 26px;
flex: 1 0 auto;
.fa, .fas {
font-size: 11px;
}
`;
const StyledPairButtonL = styled(Button)`
margin-left: 8px;
border-radius: 3px 0 0 3px;
min-width: ${(props: any) => buttonSizePx(props)}px;
max-width: ${(props: any) => buttonSizePx(props)}px;
@@ -45,21 +49,28 @@ const StyledPairButtonL = styled(Button)`
const StyledPairButtonR = styled(Button)`
min-width: 8px;
margin-left: 0px;
border-radius: 0 3px 3px 0;
border-width: 1px 1px 1px 0;
width: auto;
`;
const ButtonContainer = styled.div`
const RowContainer = styled.div`
display: flex;
flex-direction: row;
flex: 1 1 auto;
gap: 8px;
`;
const SortOrderButtonsContainer = styled.div`
display: flex;
flex-direction: row;
flex: 1 1 auto;
`;
function NoteListControls(props: Props) {
const searchBarRef = useRef(null);
useEffect(function() {
useEffect(() => {
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
return function() {
@@ -116,58 +127,63 @@ function NoteListControls(props: Props) {
if (!props.showNewNoteButtons) return null;
return (
<ButtonContainer>
{showsSortOrderButtons() &&
<StyledPairButtonL
className="sort-order-field-button"
tooltip={sortOrderFieldTooltip()}
iconName={sortOrderFieldIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderFieldButtonClick}
/>
}
{showsSortOrderButtons() &&
<StyledPairButtonR
className="sort-order-reverse-button"
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
iconName={sortOrderReverseIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderReverseButtonClick}
/>
}
<StyledButton
className="new-todo-button"
tooltip={CommandService.instance().label('newTodo')}
iconName="far fa-check-square"
level={ButtonLevel.Primary}
size={ButtonSize.Small}
onClick={onNewTodoButtonClick}
/>
<RowContainer>
<StyledButton
className="new-note-button"
tooltip={CommandService.instance().label('newNote')}
iconName="icon-note"
iconName="fas fa-plus"
title={_('%s', 'New note')}
level={ButtonLevel.Primary}
size={ButtonSize.Small}
onClick={onNewNoteButtonClick}
/>
</ButtonContainer>
<StyledButton
className="new-todo-button"
tooltip={CommandService.instance().label('newTodo')}
iconName="fas fa-plus"
title={_('%s', 'New to-do')}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onNewTodoButtonClick}
/>
</RowContainer>
);
}
return (
<StyledRoot height={props.height}>
<SearchBar inputRef={searchBarRef}/>
<StyledRoot>
{renderNewNoteButtons()}
<RowContainer>
<SearchBar inputRef={searchBarRef}/>
{showsSortOrderButtons() &&
<SortOrderButtonsContainer>
<StyledPairButtonL
className="sort-order-field-button"
tooltip={sortOrderFieldTooltip()}
iconName={sortOrderFieldIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderFieldButtonClick}
/>
<StyledPairButtonR
className="sort-order-reverse-button"
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
iconName={sortOrderReverseIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderReverseButtonClick}
/>
</SortOrderButtonsContainer>
}
</RowContainer>
</StyledRoot>
);
}
const mapStateToProps = (state: AppState) => {
return {
showNewNoteButtons: state.focusedField !== 'globalSearch',
// TODO: showNewNoteButtons and the logic associated is not needed anymore.
showNewNoteButtons: true,
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],
sortOrderField: state.settings['notes.sortOrder.field'],
sortOrderReverse: state.settings['notes.sortOrder.reverse'],

View File

@@ -56,7 +56,6 @@ interface NoteListItemProps {
onCheckboxClick: any;
onDragStart: any;
onNoteDragOver: any;
onNoteDrop: any;
onTitleClick: any;
onContextMenu(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void;
}
@@ -127,13 +126,21 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
mark.unmark();
for (let i = 0; i < props.highlightedWords.length; i++) {
const w = props.highlightedWords[i];
markJsUtils.markKeyword(mark, w, {
pregQuote: pregQuote,
replaceRegexDiacritics: replaceRegexDiacritics,
});
try {
for (const wordToBeHighlighted of props.highlightedWords) {
markJsUtils.markKeyword(mark, wordToBeHighlighted, {
pregQuote: pregQuote,
replaceRegexDiacritics: replaceRegexDiacritics,
});
}
} catch (error) {
if (error.name !== 'SyntaxError') {
throw error;
}
// An error of 'Regular expression too large' might occour in the markJs library
// when the input is really big, this catch is here to avoid the application crashing
// https://github.com/laurent22/joplin/issues/7634
console.error('Error while trying to highlight words from search: ', error);
}
// Note: in this case it is safe to use dangerouslySetInnerHTML because titleElement
@@ -167,7 +174,6 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
<StyledRoot
className={classNames}
onDragOver={props.onNoteDragOver}
onDrop={props.onNoteDrop}
width={props.width}
height={props.height}
isProvisional={props.isProvisional}

View File

@@ -33,7 +33,7 @@ export default function NoteListWrapper(props: Props) {
return (
<StyledRoot>
<NoteListControls height={controlHeight} />
<NoteListControls height={controlHeight}/>
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
</StyledRoot>
);

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