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

Compare commits

...

167 Commits

Author SHA1 Message Date
Laurent Cozic
39c8fc812d Android 2.13.10 2023-12-01 13:07:42 +01:00
Henry Heino
0638d711d7 Mobile: Resolves #9427: Drawing: Revert recent changes to input system (#9426) 2023-12-01 11:11:14 +01:00
Laurent Cozic
9bad668cc5 CLI v2.13.2 2023-11-30 19:12:07 +01:00
Laurent Cozic
c18c31ab7f Lock file 2023-11-30 19:10:57 +01:00
Laurent Cozic
7c24a2f4be Releasing sub-packages 2023-11-30 19:10:02 +01:00
Laurent Cozic
56438ea644 iOS 12.13.9 2023-11-30 18:56:49 +01:00
Laurent Cozic
7f9bc1e15c Android 2.13.9 2023-11-30 18:56:17 +01:00
Henry Heino
b1c8cb5632 Mobile: Fixes #9374: Fix tooltips don't disappear on some devices (upgrade to js-draw 1.13.2) (#9401) 2023-11-29 15:17:29 +01:00
Henry Heino
f0a1b41794 Mobile: Resolves #9377: Don't attach empty drawings when a user exits without saving (#9386) 2023-11-27 20:14:04 +01:00
Laurent Cozic
02982464a6 iOS 12.13.8 2023-11-26 13:55:26 +01:00
Laurent Cozic
62e317db05 lock file 2023-11-26 13:49:00 +01:00
Laurent Cozic
e0795748a9 Android 2.13.8 2023-11-26 13:40:59 +01:00
Laurent Cozic
67070ed3d5 Desktop release v2.13.7 2023-11-26 12:38:28 +01:00
Laurent Cozic
fec8c6131c Mobile: Fixes #9376: Sidebar is not dismissed when creating a note 2023-11-26 12:37:45 +01:00
pedr
24ed5bda63 Mobile: #9361: Fix to-dos options toggle don't toggle a rerender in (#9364) 2023-11-24 14:48:41 +01:00
Henry Heino
dbb354ad10 Mobile: Fixes #9328: Fix new note/to-do buttons not visible on app startup in some cases (#9329) 2023-11-19 10:43:57 +00:00
Laurent Cozic
92dccbe98d Merge branch 'release-2.13' into dev 2023-11-16 15:11:39 +00:00
Laurent Cozic
9b775d77f6 iOS 12.13.7 2023-11-16 13:37:15 +00:00
Laurent Cozic
4fd6937d05 lock file 2023-11-16 13:36:45 +00:00
Laurent Cozic
7230f0e698 iOS 12.13.6 2023-11-16 13:28:27 +00:00
Laurent Cozic
ada82538ee Android 2.13.7 2023-11-16 13:26:58 +00:00
Laurent Cozic
e7dd981db6 Desktop release v2.13.6 2023-11-16 13:02:07 +00:00
Laurent Cozic
767bf9f002 Server: Increase number of items that are returned during sync 2023-11-16 12:20:07 +00:00
Laurent Cozic
f698068587 Server: Fix severe performance issue for certain delta calls 2023-11-16 12:20:06 +00:00
Henry Heino
d0955b4ca2 Mobile: Fixes #9321: Restore scroll position when returning to the note viewer from the editor or camera (#9324) 2023-11-16 12:19:48 +00:00
Henry Heino
18e86a7ba3 Mobile: Resolves #9294: Implement settings search (#9320) 2023-11-16 12:17:03 +00:00
renovate[bot]
f9a1ab4d40 Update dependency react-native-vector-icons to v10.0.1 (#9316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-16 01:02:28 +00:00
renovate[bot]
062d0898a0 Update dependency sass to v1.69.5 (#9317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 23:23:22 +00:00
pedr
3b51b4fd72 Tools: Resolves #9265: Add no-constant-binary-expression to eslint rules (#9319) 2023-11-15 20:18:31 +00:00
Helmut K. C. Tessarek
1a78ff4398 All: Translation: Update it_IT.po (thanks Pietro Campanella) 2023-11-15 18:56:00 +01:00
Laurent Cozic
544af8d118 Server v2.13.4 2023-11-15 15:31:09 +00:00
pedr
2616c377a9 Tools: Create vscode launch option for debugging lib project (#9318) 2023-11-15 15:29:45 +00:00
Henry Heino
4a63331306 Plugin Repo: Resolves #9280: Allow marking specific NPM packages as superseded (#9302) 2023-11-15 13:44:09 +00:00
renovate[bot]
48621443ec Update dependency sass to v1.69.0 (#9310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 13:38:48 +00:00
pedr
79fd66b94c All: Fixes #9151: Import of inter-linked md files has incorrect notebook structure (#9269) 2023-11-15 13:33:20 +00:00
Henry Heino
6a6c8c1d83 Mobile: Fixes #9312: Fix settings save confirmation not shown when navigating to encryption/profile/log screens (#9313) 2023-11-15 13:31:36 +00:00
Henry Heino
cf19dacbaf Mobile: Fixes #9308: Disable notebook list side menu in config screen (#9311) 2023-11-15 13:31:26 +00:00
renovate[bot]
50925abc40 Update dependency @types/react to v18.2.33 (#9306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 10:54:33 +00:00
renovate[bot]
c80cbaa32f Update dependency react-native-safe-area-context to v4.7.4 (#9307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 00:45:41 +00:00
Henry Heino
f7cb1aef4b iOS: Fixes #9271: Allow showing dropdowns in landscape mode (#9309) 2023-11-15 00:42:27 +00:00
Joplin Bot
96d5d1dfab Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-11-15 00:35:50 +00:00
renovate[bot]
98d608fec5 Update dependency @types/node to v18.18.7 (#9305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 22:21:00 +00:00
Laurent Cozic
1af46b0246 Update translations 2023-11-14 19:00:52 +00:00
Henry Heino
1e530b74d4 Chore: Plugins: Allow absolute paths and URLs in screenshot srcs (#9254) 2023-11-14 18:49:45 +00:00
Henry Heino
e61c4acce5 Desktop: Resolves #9136: Install script: Work around unprivlidged user namespace restrictions by adding the --no-sandbox flag to the launcher (#9137) 2023-11-14 18:49:25 +00:00
Mohammad Ashouri
184499711d Full translation support for Farsi/Persian (#9244) 2023-11-14 18:48:32 +00:00
Henry Heino
2c0181d097 Desktop, Cli: Fixes #8788: Work around WebDAV sync issues over ipv6 (#9286) 2023-11-14 18:47:52 +00:00
Laurent Cozic
06ea12adb3 Doc: Update sponsors 2023-11-14 18:06:56 +00:00
Laurent Cozic
9923e5c821 Desktop: Resolves #9293: Preserve nested tables in RTE 2023-11-14 11:45:38 +00:00
renovate[bot]
9a06e59cfe Update dependency @testing-library/react-native to v12.3.1 (#9298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 21:54:42 +00:00
renovate[bot]
80a2cd91f4 Update dependency @types/markdown-it to v13.0.5 (#9290)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 18:36:02 +00:00
Laurent Cozic
df9ed3e487 Update BUG_REPORT.yml 2023-11-13 14:01:49 +00:00
Laurent Cozic
368d0130f6 Update BUG_REPORT.yml 2023-11-13 14:00:44 +00:00
Laurent Cozic
824e1b44dd Update BUG_REPORT.yml 2023-11-13 13:56:45 +00:00
Laurent Cozic
ccf1c8ee31 Desktop: Improve toolbar button wrapping on RTE 2023-11-13 13:52:37 +00:00
Laurent Cozic
d5f6d83f6d Update BUG_REPORT.yml 2023-11-13 11:58:52 +00:00
Laurent Cozic
5d422f85c8 lock file 2023-11-12 18:12:53 +00:00
renovate[bot]
78aeb46d56 Update dependency nodemailer to v6.9.7 (#9277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 17:28:00 +00:00
renovate[bot]
091bf45149 Update postgres Docker tag to v16 (#9289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 16:16:04 +00:00
Laurent Cozic
8d9d24740b Doc: Suggest installing yo@4.3.1 2023-11-12 15:52:01 +00:00
Laurent Cozic
21e5f88cb2 Plugin Generator release v2.13.2 2023-11-12 15:40:37 +00:00
Laurent Cozic
5d4259d064 Lock file 2023-11-12 15:39:36 +00:00
Laurent Cozic
9ac03ec33a Releasing sub-packages 2023-11-12 15:38:06 +00:00
Laurent Cozic
e760276341 Chore: Fixes #9282: Plugin generator fails when updating plugin 2023-11-12 15:32:39 +00:00
Henry Heino
206f35ffe5 Mobile: Resolves #9258: Add more space between settings title and description (#9270) 2023-11-12 15:08:52 +00:00
Henry Heino
ddf716479d Chore: Partially resolves #9262: Mobile build: Convert bundle tasks to proper gulp tasks and increase output verbosity (#9266) 2023-11-12 15:07:30 +00:00
Henry Heino
ec7f94df25 Chore: Resolves #9274: Desktop: Fix end-to-end tests when the first window is the devtools window (#9275) 2023-11-12 15:06:32 +00:00
Henry Heino
bcbba0973f Mobile: Improve image editor load performance (#9281) 2023-11-12 15:06:16 +00:00
Henry Heino
bd1ddb8522 Mobile: Resolves #9195: Update js-draw to version 1.11.2 (#9120) 2023-11-12 15:04:55 +00:00
Laurent Cozic
fb47398554 lock files 2023-11-12 15:02:44 +00:00
Henry Heino
10356f4009 Chore: Fixes #9284: Desktop: Fix warning on opening settings screen (#9287) 2023-11-12 15:01:14 +00:00
renovate[bot]
ba83fca47a Update dependency mermaid to v10.5.1 (#9279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 12:57:00 +00:00
Laurent Cozic
b01295f0fd Update BUG_REPORT.yml 2023-11-12 12:51:49 +00:00
renovate[bot]
b928e614cc Update dependency mermaid to v10.5.0 (#9278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 18:22:46 +00:00
Laurent Cozic
973b9c354c Delete .github/ISSUE_TEMPLATE/bug_report.md 2023-11-11 18:20:48 +00:00
Wladimir Kirianov
c12444d6e8 Doc: Improved github issue template, added direct links to support and feature requests (#8974)
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-11-11 18:18:59 +00:00
Joplin Bot
1401d28f82 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-11-11 18:13:30 +00:00
Laurent Cozic
335269f92d Tools: Fail more cleanly when docker-compose randomly fails to load Postgres 2023-11-11 17:50:15 +00:00
Laurent Cozic
6211606a22 Desktop: Fixed import error report 2023-11-11 17:41:08 +00:00
Laurent Cozic
5f7d438ac1 Doc: Update sponsors 2023-11-11 17:41:07 +00:00
Daeraxa
ee2df96cfb Docs: Fix BUILD.md in contributing instructions (#9273) 2023-11-11 17:17:46 +00:00
Henry Heino
692e925997 Mobile: Fixes #9259: Config screen: Fix section list scroll (#9267) 2023-11-11 17:09:34 +00:00
Henry Heino
39803f53a0 Mobile: Resolves #9260: Fade settings screen icons (#9268) 2023-11-11 17:08:58 +00:00
renovate[bot]
b3591808b7 Update dependency @types/zxcvbn to v4.4.3 (#9247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 17:01:00 +00:00
github-actions[bot]
2427677fd5 @Daeraxa has signed the CLA in laurent22/joplin#9273 2023-11-11 16:18:11 +00:00
Laurent Cozic
9a051effcd Update bug_report.md 2023-11-11 13:24:00 +00:00
Henry Heino
e6e9f92e01 Mobile: Fixes #9123: Fix encryption when a resource doesn't have an associated file (#9222)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-11-10 14:22:26 +00:00
Laurent Cozic
ca6762c891 iOS 12.13.5 2023-11-10 14:13:43 +00:00
Laurent Cozic
76d07beb27 lock file 2023-11-10 14:13:42 +00:00
Henry Heino
f3daa7f0e4 Desktop: Resolves #9250: Make settings tabs focusable by keyboard (#9253) 2023-11-10 14:00:59 +00:00
Laurent Cozic
041ad22443 Update bug_report.md 2023-11-10 13:42:06 +00:00
Laurent Cozic
6cd0938ee4 iOS 12.13.5 2023-11-10 13:21:02 +00:00
Laurent Cozic
c3dc30ee5d lock file 2023-11-10 13:19:52 +00:00
renovate[bot]
37c925dcf2 Update dependency react-native-safe-area-context to v4.7.3 (#9248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-10 12:05:54 +00:00
renovate[bot]
05bd51f85c Update dependency @types/yargs to v17.0.29 (#9246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-10 12:05:41 +00:00
Joplin Bot
cfbc37df8d Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-11-10 00:41:46 +00:00
Laurent Cozic
36635c452c CLI v2.13.1 2023-11-09 20:09:25 +00:00
Laurent Cozic
d08a16c381 Lock file 2023-11-09 20:06:19 +00:00
Laurent Cozic
1cee10ce12 Releasing sub-packages 2023-11-09 20:04:28 +00:00
Laurent Cozic
b06974e104 Exclude files 2023-11-09 20:03:04 +00:00
Laurent Cozic
bfafe7a70c Publish packages 2023-11-09 20:01:49 +00:00
Laurent Cozic
05a29b4509 Releasing sub-packages 2023-11-09 19:52:38 +00:00
Laurent Cozic
54f7a83789 Android 2.13.6 2023-11-09 19:45:45 +00:00
Laurent Cozic
1d04ec6b64 Desktop release v2.13.5 2023-11-09 19:31:11 +00:00
Laurent Cozic
99d93f0a85 Merge branch 'dev' into release-2.13 2023-11-09 19:26:08 +00:00
Laurent Cozic
b78101ef90 iOS 12.13.4 2023-11-09 19:25:36 +00:00
Laurent Cozic
6261d30574 Upgrade to Electron 26.5.0 2023-11-09 19:24:02 +00:00
Henry Heino
672d028d29 Mobile: Settings screen: Create separate pages for each screen (#8567) 2023-11-09 19:19:08 +00:00
renovate[bot]
0340c7f65c Update dependency @types/react-redux to v7.1.28 (#9242)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-09 18:44:33 +00:00
pedr
632802e58b Clipper: Avoid errors when downloading media files without protocol (#9241) 2023-11-09 15:02:52 +00:00
Laurent Cozic
c3510bf26b Desktop: Fixes #9149: Toolbar icons in view mode are partly not grayed out and can be used 2023-11-09 14:58:38 +00:00
Laurent Cozic
e22aa4f6e9 Desktop: Fixes #8961: Fix rare crash when developing a plugin 2023-11-09 12:26:06 +00:00
renovate[bot]
76a8ae3a83 Update dependency @types/node-rsa to v1.1.3 (#9238)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-09 11:09:16 +00:00
renovate[bot]
1c104d2e83 Update dependency @types/uuid to v9.0.6 (#9245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 23:34:41 +00:00
renovate[bot]
7c38c2c8f2 Update dependency @types/nodemailer to v6.4.13 (#9239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 22:21:00 +00:00
renovate[bot]
188d9ac159 Update dependency @types/styled-components to v5.1.29 (#9243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 20:32:10 +00:00
github-actions[bot]
91751d5fa3 @mimeyn has signed the CLA in laurent22/joplin#9244 2023-11-08 19:40:10 +00:00
renovate[bot]
5124cbfd9b Update dependency @types/node-fetch to v2.6.7 (#9237)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 19:08:06 +00:00
renovate[bot]
cb21a61d7c Update dependency @types/node to v18.18.6 (#9233)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 18:33:00 +00:00
Marco Rombach
f50019d098 Server: Added LDAP authentication (#9150) 2023-11-08 15:38:01 +00:00
renovate[bot]
ab9a1776c8 Update dependency @types/markdown-it to v13.0.4 (#9231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 04:43:20 +00:00
renovate[bot]
a357665c77 Update dependency @types/fs-extra to v11.0.3 (#9236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 00:36:05 +00:00
renovate[bot]
be097afd83 Update dependency @types/koa to v2.13.10 (#9230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 21:42:13 +00:00
renovate[bot]
b417299616 Update dependency @types/mustache to v4.2.4 (#9232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 19:17:44 +00:00
renovate[bot]
582b963570 Update dependency @types/jsdom to v21.1.4 (#9229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 17:06:59 +00:00
Henry Heino
1405def25d Desktop: Fixes #7547: Fix inserting resources into TinyMCE from plugins (insertText command) (#9225) 2023-11-07 12:07:42 +00:00
Henry Heino
e9c598cf46 Chore: Mobile: Remove duplicate bundle minification (#9221) 2023-11-07 12:04:33 +00:00
Henry Heino
02361e37f0 Desktop: Fixes #9036: Fix note list scroll (#9211)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-11-07 12:04:03 +00:00
gitbreaker222
041414b11e Doc: Fixes broken faq links (#9220) 2023-11-07 12:01:00 +00:00
Henry Heino
b4ca00ebf5 Mobile: Fixes #9207: Fix search highlighting (#9206) 2023-11-07 12:00:36 +00:00
Henry Heino
9d96866531 Mobile, Desktop: Fixes #9201: Disable selection match highlighting (#9202) 2023-11-07 12:00:13 +00:00
Henry Heino
6593025051 Desktop: Fixes #8978: Rich text editor: Fix repeated newline characters discarded on save to markdown (#9199) 2023-11-07 11:59:35 +00:00
Henry Heino
a38fe11bbe Desktop: Fixes #9122: Fix underscores escaped within some text-only URLs (#9198) 2023-11-07 11:58:52 +00:00
Roman Orlowski
b030ca914d Desktop: Fixes #9130: Allow Electron --disable-gpu flag (#9179) 2023-11-07 11:58:39 +00:00
Henry Heino
88b44a0f74 All: Fixes #8561: Fix OneDrive sync crash on throttle (#9143) 2023-11-07 11:55:38 +00:00
renovate[bot]
7b2cf0e483 Update dependency @types/js-yaml to v4.0.8 (#9227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 11:43:28 +00:00
renovate[bot]
5d3e920370 Update dependency @types/formidable to v3.4.4 (#9226)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 06:07:13 +00:00
github-actions[bot]
7cec62fc71 @gitbreaker222 has signed the CLA in laurent22/joplin#9220 2023-11-06 14:32:53 +00:00
renovate[bot]
698d16e970 Update dependency @types/node to v18.18.5 (#9218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-05 21:58:35 +00:00
renovate[bot]
d88af474d2 Update dependency @types/node to v18.18.0 (#9214)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-05 19:35:06 +00:00
Laurent Cozic
7488129517 Doc: Fixes #9216: Fixed several broken links 2023-11-05 17:59:36 +00:00
Laurent Cozic
9772389fd9 Doc: Fixed favicon on translated websites 2023-11-05 17:59:35 +00:00
Laurent Cozic
c2a1ea8cba Tools: Add plugin API doc to linkchecker 2023-11-05 17:59:33 +00:00
Laurent Cozic
9afc94fb3b Tools: Pin Python version to 3.11 to fix desktop build on CI (#9212) 2023-11-05 13:33:38 +00:00
github-actions[bot]
390b28c3f5 @brechsteiner has signed the CLA in laurent22/joplin#9210 2023-11-03 22:08:52 +00:00
Laurent Cozic
8be22ed910 Plugins: Add support for getting plugin settings from a Markdown renderer 2023-11-03 19:45:21 +00:00
Laurent Cozic
b097ab29ee Desktop, Mobile: Resolves #9158: Add a "Retry all" button when multiple resources could not be downloaded 2023-11-03 16:01:51 +00:00
Laurent Cozic
d9bf0b7d82 Doc: Add image source 2023-11-03 16:01:50 +00:00
renovate[bot]
0f5533af55 Update dependency electron to v26 (#9203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-03 12:40:32 +00:00
Laurent Cozic
1c47db70a0 Doc: Automatically detect Apple silicon on Download page 2023-11-03 12:36:43 +00:00
renovate[bot]
9723ab0ba6 Update dependency lint-staged to v14.0.1 (#9192)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-02 14:36:56 +00:00
Laurent Cozic
2b9d519f4b Tools: Do not run "pod install" by default 2023-11-02 10:23:00 +00:00
renovate[bot]
4478ce118a Update dependency lint-staged to v14 (#9189)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-02 09:54:05 +00:00
Henry Heino
7b56311729 Mobile: Fixes #9188: Image editor resets on theme change (#9190) 2023-11-02 09:53:38 +00:00
Henry Heino
09b52237f2 Mobile: Fixes #9159: Fix fast search (#9191) 2023-11-02 09:50:38 +00:00
renovate[bot]
c7c86c2b52 Update dependency deprecated-react-native-prop-types to v4.2.3 (#9186)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-01 18:24:56 +00:00
Laurent Cozic
dce8bced15 Server v2.13.3 2023-11-01 16:30:12 +00:00
Laurent Cozic
a9719307af lock files 2023-11-01 16:29:04 +00:00
Laurent Cozic
0c8b475736 Server: Automatically restarts the server when it crashes, with exponentiel back-off 2023-11-01 16:28:45 +00:00
Laurent Cozic
dbd3db873f Fix ts error 2023-11-01 16:25:46 +00:00
Laurent Cozic
073781da92 Chore: Fixed attachFile method 2023-11-01 14:58:21 +00:00
Laurent Cozic
7d87d0b394 Chore: Remove postinstall step from editor package 2023-11-01 14:54:48 +00:00
renovate[bot]
0cb2a3a385 Update eslint (#9184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-01 10:48:09 +00:00
renovate[bot]
c1e970a703 Update dependency @react-native-community/datetimepicker to v7.6.1 (#9181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-01 07:16:07 +00:00
renovate[bot]
17831bf87a Update electron (#9183)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-01 03:32:57 +00:00
Joplin Bot
a93c558479 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-11-01 00:41:29 +00:00
Laurent Cozic
13b09aa9a4 Doc: Set Data API link position 2023-10-31 20:39:52 +00:00
298 changed files with 30539 additions and 23591 deletions

View File

@@ -260,6 +260,7 @@ 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.test.js
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
packages/app-desktop/gui/NoteEditor/styles/index.js
packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js
@@ -382,6 +383,8 @@ packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
@@ -426,6 +429,7 @@ packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
@@ -466,17 +470,29 @@ packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/app-nav.js
packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreenButton.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.test.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/exportAllFolders.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportAllFolders.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportDebugReport.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportProfile.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Notes.js
@@ -494,7 +510,10 @@ packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
packages/app-mobile/tools/buildInjectedJs/copyJs.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/utils/ShareExtension.js
packages/app-mobile/utils/ShareUtils.test.js
packages/app-mobile/utils/ShareUtils.js
@@ -599,6 +618,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/config/config-shared.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js
packages/lib/components/shared/note-screen-shared.js
@@ -825,6 +845,7 @@ packages/lib/services/rest/routes/events.test.js
packages/lib/services/rest/routes/events.js
packages/lib/services/rest/routes/folders.js
packages/lib/services/rest/routes/master_keys.js
packages/lib/services/rest/routes/notes.test.js
packages/lib/services/rest/routes/notes.js
packages/lib/services/rest/routes/ping.js
packages/lib/services/rest/routes/resources.js

View File

@@ -157,6 +157,8 @@ module.exports = {
// In user-facing text, it should be "notebook".
'id-denylist': ['error', 'err', 'notebook', 'notebooks'],
'prefer-arrow-callback': ['error'],
'no-constant-binary-expression': ['error'],
},
'plugins': [
'react',

62
.github/ISSUE_TEMPLATE/BUG_REPORT.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Bug Report
description: Report a reproducible bug or regression in Joplin.
labels: ['bug']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: dropdown
id: os
attributes:
label: "Operating system"
multiple: false
options:
- "Windows"
- "macOS"
- "Linux"
- "Android"
- "iOS"
validations:
required: true
- type: input
id: version
attributes:
label: "Joplin version"
placeholder: "For example 2.3.6"
description:
validations:
required: true
- type: textarea
id: desktop-about-content
attributes:
label: "Desktop version info"
description: "If this issue is about the **desktop app**, please open the \"About\" dialog under the \"Help\" or \"Joplin\" menu and copy its content here."
placeholder: "Joplin 2.13.5 (dev, darwin)\n\nClient ID: ..."
- type: textarea
id: current
attributes:
label: Current behaviour
description: What did Joplin do? Include screenshots and video recordings for UI problems if needed. If you are reporting a clipper bug, please include an example URL that shows the issue.
placeholder: |
1. This
2. Then that
3. Then this
4. Etc.
- type: textarea
id: expected
attributes:
label: Expected behaviour
description: What did you expect Joplin to do?
- type: textarea
id: logs
attributes:
label: Logs
description: "If relevant, please provide a log file as described here: https://joplinapp.org/help/apps/debugging"

View File

@@ -1,52 +0,0 @@
---
name: "\U0001F41B Bug Report"
about: Report a reproducible bug or regression in Joplin.
title: ''
labels: bug
assignees: ''
---
<!--
Please provide a clear and concise description of what the bug is. (In the section Steps To Reproduce.)
Include screenshots for UI problems if needed.
DO NOT create screenshots of text !!! Copy and paste the text into a code block.
Please test using the latest Joplin release to make sure your issue has not already been fixed.
-->
<!--
IMPORTANT: If you are reporting a clipper bug, please include an example URL that shows the issue.
Without the URL the issue is likely to be closed.
-->
## Environment
Joplin version:
Platform:
OS specifics:
<!--
Platform can be one of: macOS, Linux, Windows, Android, iOS, terminal (or a combination)
OS specifics: e.g. OS version, Linux distribution, Android/iOS version...
-->
## Steps to reproduce
1.
2.
3.
<!--
Issues without reproduction steps are likely to stall.
-->
## Describe what you expected to happen
## Logfile
<!--
Please attach a debug log. Issues without a debug log are likely to stall.
For information on how to collect a log file: https://joplinapp.org/help/apps/debugging/
-->

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: "\U0001F914 Feature requests and support"
url: https://discourse.joplinapp.org/
about: I have a question or feature request …
- name: Feature Requests
url: https://discourse.joplinapp.org/c/features/
about: Discuss ideas for new features or changes
- name: Support
url: https://discourse.joplinapp.org/c/support/
about: Please ask for help here

View File

@@ -75,6 +75,10 @@ if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
if [ "$IS_LINUX" == "1" ]; then
echo "Running Joplin Server tests using PostgreSQL..."
sudo docker-compose --file docker-compose.db-dev.yml up -d
cmdResult=$?
if [ $cmdResult -ne 0 ]; then
exit $cmdResult
fi
export JOPLIN_TESTS_SERVER_DB=pg
else
echo "Running Joplin Server tests using SQLite..."

View File

@@ -33,6 +33,11 @@ jobs:
# https://yarnpkg.com/getting-started/install
corepack enable
# See github-action-main.yml for explanation
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Build macOS M1 app
env:
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}

View File

@@ -98,6 +98,15 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# macos-latest ships with Python 3.12 by default, but this removes a
# utility that's used by electron-builder (distutils) so we need to pin
# Python to an earlier version.
# Fixes error `ModuleNotFoundError: No module named 'distutils'`
# Ref: https://github.com/nodejs/node-gyp/issues/2869
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Run tests, build and publish Linux and macOS apps
if: runner.os == 'Linux' || runner.os == 'macOs'
env:

27
.gitignore vendored
View File

@@ -242,6 +242,7 @@ 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.test.js
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
packages/app-desktop/gui/NoteEditor/styles/index.js
packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js
@@ -364,6 +365,8 @@ packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
@@ -408,6 +411,7 @@ packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
@@ -448,17 +452,29 @@ packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/app-nav.js
packages/app-mobile/components/base-screen.js
packages/app-mobile/components/biometrics/BiometricPopup.js
packages/app-mobile/components/biometrics/biometricAuthenticate.js
packages/app-mobile/components/biometrics/sensorInfo.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen/ConfigScreenButton.js
packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.test.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/exportAllFolders.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportAllFolders.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportDebugReport.js
packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportProfile.js
packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js
packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js
packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js
packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Notes.js
@@ -476,7 +492,10 @@ packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
packages/app-mobile/tools/buildInjectedJs/copyJs.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/utils/ShareExtension.js
packages/app-mobile/utils/ShareUtils.test.js
packages/app-mobile/utils/ShareUtils.js
@@ -581,6 +600,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/config/config-shared.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js
packages/lib/components/shared/note-screen-shared.js
@@ -807,6 +827,7 @@ packages/lib/services/rest/routes/events.test.js
packages/lib/services/rest/routes/events.js
packages/lib/services/rest/routes/folders.js
packages/lib/services/rest/routes/master_keys.js
packages/lib/services/rest/routes/notes.test.js
packages/lib/services/rest/routes/notes.js
packages/lib/services/rest/routes/ping.js
packages/lib/services/rest/routes/resources.js

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.6.3.cjs
yarnPath: .yarn/releases/yarn-3.6.4.cjs
logFilters:

BIN
Assets/BadgeMacOSM1.psd Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,6 +1,31 @@
function getOs() {
async function getOs() {
// The macOS release is available for Intel and Apple silicon processors,
// and the only way to get that info is through this new
// `getHighEntropyValues` function (which is not available on all browsers).
// So here we either return "macOs" for Intel or "macOsM1" for Apple
// Silicon. If we don't know which it is, we return "macOsUndefined".
// https://stackoverflow.com/a/75177111/561309
if (navigator.appVersion.indexOf("Mac")!=-1) {
let platformInfo = null;
try {
platformInfo = await navigator.userAgentData.getHighEntropyValues(['architecture'])
} catch (error) {
console.warn('Failed getting Mac architecture:', error);
return 'macOsUndefined';
}
console.info('Got platform info:', platformInfo);
if (platformInfo.architecture === 'arm') {
return "macOsM1";
} else {
return "macOs";
}
}
if (navigator.appVersion.indexOf("Win")!=-1) return "windows";
if (navigator.appVersion.indexOf("Mac")!=-1) return "macOs";
if (navigator.appVersion.indexOf("X11")!=-1) return "linux";
if (navigator.appVersion.indexOf("Linux")!=-1) return "linux";
return null;
@@ -45,7 +70,7 @@ function setupMobileMenu() {
});
}
function setupDownloadPage() {
async function setupDownloadPage() {
if (!$('.page-download').length) return;
const downloadLinks = {};
@@ -55,6 +80,7 @@ function setupDownloadPage() {
if (href.indexOf('-Setup') > 0) downloadLinks['windows'] = href;
if (href.indexOf('.dmg') > 0) downloadLinks['macOs'] = href;
if (href.endsWith('arm64.DMG')) downloadLinks['macOsM1'] = href;
if (href.indexOf('.AppImage') > 0) downloadLinks['linux'] = href;
});
@@ -70,8 +96,17 @@ function setupDownloadPage() {
if (mobileOs) {
$('.page-download .intro').hide();
} else {
const os = getOs();
if (!os || !downloadLinks[os]) {
const os = await getOs();
if (os === 'macOsUndefined') {
// If we don't know which macOS version it is, we let the user choose.
$('.main-content .intro').html('<p class="macos-m1-info">The macOS release is available for Intel processors or for Apple Silicon (M1) processors. Please select your version:</p>');
const macOsLink = $('.download-link-macOs');
const macOsM1Link = $('.download-link-macOsM1');
$('.macos-m1-info').after('<p style="font-style: italic; font-size: .8em;">To find out what processor you have, click on the <b>Apple logo</b> in the macOS menu bar, choose <b>About This Mac</b> from the dropdown menu. If you have an Apple silicon it should say"Apple M1" under "Chip". Otherwise you have an Intel processor.</p>');
$('.macos-m1-info').after(macOsM1Link);
$('.macos-m1-info').after(macOsLink);
} else if (!os || !downloadLinks[os]) {
// If we don't know, display the section to manually download the app
$('.page-download .get-it-desktop').show();
} else if (os === 'linux') {
@@ -89,5 +124,5 @@ function setupDownloadPage() {
$(function () {
setupMobileMenu();
setupDownloadPage();
void setupDownloadPage();
});

View File

@@ -71,7 +71,7 @@ EXPOSE ${APP_PORT}
# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals
WORKDIR /home/$user/packages/server
ENTRYPOINT ["tini", "--"]
CMD ["node", "dist/app.js"]
CMD ["yarn", "start-prod"]
# Build-time metadata
# https://github.com/opencontainers/image-spec/blob/master/annotations.md

View File

@@ -124,7 +124,7 @@ else
fi
if [[ $LIBFUSE == "" ]] ; then
print "${COLOR_RED}Error: Can't get libfuse2 on system, please install libfuse2${COLOR_RESET}"
print "See https://joplinapp.org/faq/#desktop-application-will-not-launch-on-linux and https://github.com/AppImage/AppImageKit/wiki/FUSE for more information"
print "See https://joplinapp.org/help/faq/#desktop-application-will-not-launch-on-linux and https://github.com/AppImage/AppImageKit/wiki/FUSE for more information"
exit 1
fi
@@ -205,9 +205,16 @@ if command -v lsb_release &> /dev/null; then
# Check for "The SUID sandbox helper binary was found, but is not configured correctly" problem.
# It is present in Debian 1X. A (temporary) patch will be applied at .desktop file
# Linux Mint 4 Debbie is based on Debian 10 and requires the same param handling.
if [[ $DISTVER =~ Debian1. ]] || [ "$DISTVER" = "Linuxmint4" ] && [ "$DISTCODENAME" = "debbie" ] || [ "$DISTVER" = "CentOS" ] && [[ "$DISTMAJOR" =~ 6|7 ]]
#
# This also works around Ubuntu 23.10+'s restrictions on unprivileged user namespaces. Electron
# uses these to sandbox processes. Unfortunately, it doesn't look like we can get around this
# without writing the AppImage to a non-user-writable location (without invalidating other security
# controls). See https://discourse.joplinapp.org/t/possible-future-requirement-for-no-sandbox-flag-for-ubuntu-23-10/.
if [[ $DISTVER = "Ubuntu23.10" || $DISTVER =~ Debian1. || ( "$DISTVER" = "Linuxmint4" && "$DISTCODENAME" = "debbie" ) || ( "$DISTVER" = "CentOS" && "$DISTMAJOR" =~ 6|7 ) ]]
then
SANDBOXPARAM="--no-sandbox"
print "${COLOR_YELLOW}WARNING${COLOR_RESET} Electron sandboxing disabled."
print " See https://discourse.joplinapp.org/t/32160/5 for details."
fi
fi

View File

@@ -42,8 +42,8 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) | <img width="50" src="https://avatars2.githubusercontent.com/u/2793530?s=96&v=4"/></br>[CyberXZT](https://github.com/CyberXZT) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/14873877?s=96&v=4"/></br>[dchecks](https://github.com/dchecks) | <img width="50" src="https://avatars2.githubusercontent.com/u/56287?s=96&v=4"/></br>[fats](https://github.com/fats) | <img width="50" src="https://avatars2.githubusercontent.com/u/8030470?s=96&v=4"/></br>[Galliver7](https://github.com/Galliver7) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/126279083?s=96&v=4"/></br>[matmoly](https://github.com/matmoly) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/333944?s=96&v=4"/></br>[tateisu](https://github.com/tateisu) | | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/126279083?s=96&v=4"/></br>[matmoly](https://github.com/matmoly) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/4560672?s=96&v=4"/></br>[mu88](https://github.com/mu88) | <img width="50" src="https://avatars2.githubusercontent.com/u/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/333944?s=96&v=4"/></br>[tateisu](https://github.com/tateisu) | |
<!-- SPONSORS-GITHUB -->
# Community

View File

@@ -6,7 +6,7 @@ version: '3'
services:
db:
image: postgres:15
image: postgres:16
command: postgres -c work_mem=100000
ports:
- "5432:5432"

View File

@@ -18,7 +18,7 @@ services:
- POSTGRES_PORT=5432
- POSTGRES_HOST=localhost
db:
image: postgres:15
image: postgres:16
ports:
- "5432:5432"
environment:

View File

@@ -19,7 +19,7 @@ version: '3'
services:
db:
image: postgres:15
image: postgres:16
volumes:
- ./data/postgres:/var/lib/postgresql/data
ports:

View File

@@ -367,6 +367,12 @@
"type": "shell",
"command": "cd ${workspaceFolder}/packages/server && yarn tsc",
"group": "build",
},
{
"label": "transpile-lib",
"type": "shell",
"command": "cd ${workspaceFolder}/packages/lib && yarn tsc",
"group": "build",
}
]
},
@@ -395,6 +401,19 @@
"APP_BASE_URL": "http://joplincloud.local:22300",
"API_BASE_URL": "http://api.joplincloud.local:22300",
}
},
{
"type": "node",
"request": "launch",
"name": "lib: debug test file",
"preLaunchTask": "transpile-lib",
"program": "${workspaceFolder}/packages/lib/node_modules/.bin/jest",
"args": [
"${fileBasenameNoExtension}",
"--config",
"packages/lib/jest.config.js",
],
"console": "integratedTerminal",
}
]
}

View File

@@ -32,7 +32,7 @@
"cspell": "cspell",
"dependencyTree": "madge",
"generateDatabaseTypes": "node packages/tools/generate-database-types",
"linkChecker": "linkchecker https://joplinapp.org",
"linkChecker": "linkchecker https://joplinapp.org/ && linkchecker --check-extern https://joplinapp.org/api/references/plugin_api/classes/joplin.html",
"linter-ci": "eslint --resolve-plugins-relative-to . --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"linter-interactive": "eslint-interactive --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"linter-precommit": "eslint --resolve-plugins-relative-to . --fix --ext .js --ext .jsx --ext .ts --ext .tsx",
@@ -72,13 +72,13 @@
"@crowdin/cli": "3",
"@joplin/utils": "~2.12",
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "6.0.0",
"@typescript-eslint/parser": "6.0.0",
"@typescript-eslint/eslint-plugin": "6.7.2",
"@typescript-eslint/parser": "6.7.2",
"cspell": "5.21.2",
"eslint": "8.47.0",
"eslint": "8.49.0",
"eslint-interactive": "10.8.0",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-jest": "27.2.3",
"eslint-plugin-jest": "27.4.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.33.2",
"execa": "5.1.1",
@@ -87,23 +87,23 @@
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "13.3.0",
"lint-staged": "14.0.1",
"madge": "6.1.0",
"npm-package-json-lint": "7.0.0",
"typescript": "5.1.6"
"typescript": "5.2.2"
},
"dependencies": {
"@types/fs-extra": "11.0.2",
"eslint-plugin-github": "4.9.2",
"@types/fs-extra": "11.0.3",
"eslint-plugin-github": "4.10.0",
"http-server": "14.1.1",
"node-gyp": "9.4.0",
"nodemon": "3.0.1"
},
"packageManager": "yarn@3.6.3",
"packageManager": "yarn@3.6.4",
"resolutions": {
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
"eslint": "patch:eslint@8.47.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"eslint": "patch:eslint@8.49.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
"react-native@0.71.10": "patch:react-native@npm%3A0.71.10#./.yarn/patches/react-native-animation-fix/react-native-npm-0.71.10-f9c32562d8.patch"
}

View File

@@ -57,6 +57,10 @@ class Command extends BaseCommand {
const lines = [];
lines.push('---');
lines.push('sidebar_position: 2');
lines.push('---');
lines.push('');
lines.push('# Joplin Data API');
lines.push('');
lines.push('This API is available when the clipper server is running. It provides access to the notes, notebooks, tags and other Joplin object via a REST API. Plugins can also access this API even when the clipper server is not running.');

View File

@@ -52,7 +52,7 @@ export default class PluginRunner extends BasePluginRunner {
this.activeSandboxCalls_[callId] = true;
const promise = executeSandboxCall(pluginId, sandbox, `joplin.${path}`, mapEventHandlersToIds(args, this.eventHandlers_), this.eventHandler);
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
promise.finally(() => {
void promise.finally(() => {
delete this.activeSandboxCalls_[callId];
});
return promise;

View File

@@ -35,7 +35,7 @@
],
"owner": "Laurent Cozic"
},
"version": "2.13.0",
"version": "2.13.2",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
@@ -71,13 +71,13 @@
},
"devDependencies": {
"@joplin/tools": "~2.13",
"@types/fs-extra": "11.0.2",
"@types/jest": "29.5.4",
"@types/node": "18.17.19",
"@types/fs-extra": "11.0.3",
"@types/jest": "29.5.5",
"@types/node": "18.18.7",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.6.4",
"jest": "29.7.0",
"temp": "0.9.4",
"typescript": "5.1.6"
"typescript": "5.2.2"
}
}

View File

@@ -36,6 +36,10 @@ describe('HtmlToMd', () => {
htmlToMdOptions.preserveImageTagsWithSize = true;
}
if (htmlFilename.indexOf('preserve_nested_tables') === 0) {
htmlToMdOptions.preserveNestedTables = true;
}
const html = await readFile(htmlPath, 'utf8');
let expectedMd = await readFile(mdPath, 'utf8');
@@ -83,8 +87,8 @@ describe('HtmlToMd', () => {
it('should allow disabling escape', async () => {
const htmlToMd = new HtmlToMd();
expect(htmlToMd.parse('https://test.com/1_2_3.pdf', { disableEscapeContent: true })).toBe('https://test.com/1_2_3.pdf');
expect(htmlToMd.parse('https://test.com/1_2_3.pdf', { disableEscapeContent: false })).toBe('https://test.com/1\\_2\\_3.pdf');
expect(htmlToMd.parse('> 1 _2_ 3.pdf', { disableEscapeContent: true })).toBe('> 1 _2_ 3.pdf');
expect(htmlToMd.parse('> 1 _2_ 3.pdf', { disableEscapeContent: false })).toBe('\\> 1 \\_2_ 3.pdf');
});
});

View File

@@ -15,7 +15,4 @@ however.
Because it isn't
necessary.
...
<br/><br/><br/>...

View File

@@ -0,0 +1,19 @@
<body>
<table border="5px" bordercolor="#8707B0">
<tr>
<td>Left side of the main table</td>
<td>
<table border="5px" bordercolor="#F35557">
<h4 align="center">Nested Table</h4>
<tr>
<td>nested table C1</td>
<td>nested table C2</td>
</tr>
<tr>
<td>nested table</td>
<td>nested table</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1 @@
<table border="5px" bordercolor="#8707B0"><tbody><tr><td>Left side of the main table</td><td><h4 align="center">Nested Table</h4><table border="5px" bordercolor="#F35557"><tbody><tr><td>nested table C1</td><td>nested table C2</td></tr><tr><td>nested table</td><td>nested table</td></tr></tbody></table></td></tr></tbody></table>

View File

@@ -0,0 +1,4 @@
A<br/><br/><br/>test.<br/>
A single &lt;br/&gt;<br/>can use two spaces at the end of the line,
but<br/><br/>the markdown renderer discards these if the line is otherwise empty.

View File

@@ -0,0 +1,5 @@
A
<br/><br/>test.
A single &lt;br/&gt;
can use two spaces at the end of the line, but
<br/>the markdown renderer discards these if the line is otherwise empty.

View File

@@ -0,0 +1,14 @@
<p>
Some URLs in the Rich_Text_Editor contain <code>_</code> characters, but haven't been converted
to links yet. For example, https://www.example.com/a_test_of_links.
</p>
<p>We should preserve the underscores _without escaping them_ to prevent the links from breaking.</p>
<p>
This should also correctly handle unicode characters. For example, punctuation❯_requires escapes_,
but 𝔏𝔈𝔗𝔗𝔈𝕽_𝔠𝔥𝔞𝔯𝔞𝔠𝔱𝔢𝔯𝔰_and_897_numbers_𝒟on_'t.
</p>
<p>
_Note_ that what [_causes_] a `_` to create italics_ seems to depend only on the character before
and an escape at the _beginning_ seems to be sufficient.
</p>
<p>_s also don't need escapes if _ followed _ by a _space.</p>

View File

@@ -0,0 +1,9 @@
Some URLs in the Rich_Text_Editor contain `_` characters, but haven't been converted to links yet. For example, https://www.example.com/a_test_of_links.
We should preserve the underscores \_without escaping them_ to prevent the links from breaking.
This should also correctly handle unicode characters. For example, punctuation❯\_requires escapes_, but 𝔏𝔈𝔗𝔗𝔈𝕽_𝔠𝔥𝔞𝔯𝔞𝔠𝔱𝔢𝔯𝔰_and_897_numbers_𝒟on_'t.
\_Note_ that what \[\_causes_\] a \`\_\` to create italics_ seems to depend only on the character before and an escape at the \_beginning_ seems to be sufficient.
\_s also don't need escapes if _ followed _ by a \_space.

View File

@@ -206,7 +206,7 @@ describe('services_PluginService', () => {
const mdToHtml = new MdToHtml();
const module = require(contentScript.path).default;
mdToHtml.loadExtraRendererRule(contentScript.id, tempDir, module({}));
mdToHtml.loadExtraRendererRule(contentScript.id, tempDir, module({}), '');
const result = await mdToHtml.render([
'```justtesting',

View File

@@ -74,7 +74,7 @@ To get such an external script file to compile, you need to add it to the `extra
## More information
- [Joplin Plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html)
- [Joplin Data API](https://joplinapp.org/api/references/rest_api/)
- [Joplin Data API](https://joplinapp.org/help/api/references/rest_api)
- [Joplin Plugin Manifest](https://joplinapp.org/api/references/plugin_manifest/)
- Ask for help on the [forum](https://discourse.joplinapp.org/) or our [Discord channel](https://discord.gg/VSj7AFHvpq)

View File

@@ -26,7 +26,7 @@ const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
import Folder from '@joplin/lib/models/Folder';
import Tag from '@joplin/lib/models/Tag';
import { reg } from '@joplin/lib/registry';
const packageInfo = require('./packageInfo.js');
const packageInfo: PackageInfo = require('./packageInfo.js');
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
import ClipperServer from '@joplin/lib/ClipperServer';
const { webFrame } = require('electron');
@@ -68,6 +68,7 @@ import path = require('path');
import { checkPreInstalledDefaultPlugins, installDefaultPlugins, setSettingsForDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
import userFetcher, { initializeUserFetcher } from '@joplin/lib/utils/userFetcher';
import { parseNotesParent } from '@joplin/lib/reducer';
import { PackageInfo } from '@joplin/lib/versionInfo';
const pluginClasses = [
require('./plugins/GotoAnything').default,

View File

@@ -5,8 +5,9 @@ import bridge from './services/bridge';
import KvStore from '@joplin/lib/services/KvStore';
import * as ArrayUtils from '@joplin/lib/ArrayUtils';
import { CheckForUpdateOptions, extractVersionInfo, GitHubRelease } from './utils/checkForUpdatesUtils';
const packageInfo = require('./packageInfo.js');
import { PackageInfo } from '@joplin/lib/versionInfo';
import { compareVersions } from 'compare-versions';
const packageInfo: PackageInfo = require('./packageInfo.js');
const logger = Logger.create('checkForUpdates');

View File

@@ -12,7 +12,7 @@ const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const pathUtils = require('@joplin/lib/path-utils');
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const shared = require('@joplin/lib/components/shared/config/config-shared.js');
import * as shared from '@joplin/lib/components/shared/config/config-shared.js';
import ClipperConfigScreen from '../ClipperConfigScreen';
import restart from '../../services/restart';
import PluginService from '@joplin/lib/services/plugins/PluginService';
@@ -35,9 +35,10 @@ class ConfigScreenComponent extends React.Component<any, any> {
public constructor(props: any) {
super(props);
shared.init(this, reg);
shared.init(reg);
this.state = {
...shared.defaultScreenState,
selectedSectionName: 'general',
screenName: '',
changedSettingKeys: [],
@@ -98,7 +99,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
}
public sectionByName(name: string) {
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
const sections = shared.settingsSections({ device: AppType.Desktop, settings: this.state.settings });
for (const section of sections) {
if (section.name === name) return section;
}
@@ -699,7 +700,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
const hasChanges = this.hasChanges();
const settingComps = shared.settingsToComponents2(this, 'desktop', settings, this.state.selectedSectionName);
const settingComps = shared.settingsToComponents2(this, AppType.Desktop, settings, this.state.selectedSectionName);
// screenComp is a custom config screen, such as the encryption config screen or keymap config screen.
// These screens handle their own loading/saving of settings and have bespoke rendering.
@@ -708,7 +709,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
if (screenComp) containerStyle.display = 'none';
const sections = shared.settingsSections({ device: 'desktop', settings });
const sections = shared.settingsSections({ device: AppType.Desktop, settings });
const needRestartComp: any = this.state.needRestart ? (
<div style={{ ...theme.textStyle, padding: 10, paddingLeft: 24, backgroundColor: theme.warningBackgroundColor, color: theme.color }}>

View File

@@ -1,4 +1,4 @@
import { SettingSectionSource } from '@joplin/lib/models/Setting';
import { AppType, SettingSectionSource } from '@joplin/lib/models/Setting';
import * as React from 'react';
import { useMemo } from 'react';
import Setting from '@joplin/lib/models/Setting';
@@ -92,8 +92,18 @@ export default function Sidebar(props: Props) {
function renderButton(section: any) {
const selected = props.selection === section.name;
return (
<StyledListItem key={section.name} isSubSection={Setting.isSubSection(section.name)} selected={selected} onClick={() => { props.onSelectionChange({ section: section }); }}>
<StyledListItemIcon className={Setting.sectionNameToIcon(section.name)} />
<StyledListItem
key={section.name}
href='#'
role='tab'
aria-selected={selected}
isSubSection={Setting.isSubSection(section.name)}
selected={selected}
onClick={() => { props.onSelectionChange({ section: section }); }}
>
<StyledListItemIcon
className={Setting.sectionNameToIcon(section.name, AppType.Desktop)}
/>
<StyledListItemLabel>
{Setting.sectionNameToLabel(section.name)}
</StyledListItemLabel>
@@ -121,7 +131,7 @@ export default function Sidebar(props: Props) {
}
return (
<StyledRoot>
<StyledRoot role='tablist'>
{buttons}
</StyledRoot>
);

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import versionInfo from '@joplin/lib/versionInfo';
import versionInfo, { PackageInfo } from '@joplin/lib/versionInfo';
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');
const packageInfo: PackageInfo = require('../packageInfo.js');
const ipcRenderer = require('electron').ipcRenderer;
interface ErrorInfo {

View File

@@ -8,7 +8,7 @@ import KeymapService from '@joplin/lib/services/KeymapService';
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import shim from '@joplin/lib/shim';
import Setting from '@joplin/lib/models/Setting';
import versionInfo from '@joplin/lib/versionInfo';
import versionInfo, { PackageInfo } from '@joplin/lib/versionInfo';
import makeDiscourseDebugUrl from '@joplin/lib/makeDiscourseDebugUrl';
import { ImportModule } from '@joplin/lib/services/interop/Module';
import InteropServiceHelper from '../InteropServiceHelper';
@@ -25,7 +25,7 @@ import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { getListRendererById, getListRendererIds } from '@joplin/lib/services/noteList/renderers';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
const packageInfo = require('../packageInfo.js');
const packageInfo: PackageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;

View File

@@ -203,7 +203,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
let commandProcessed = true;
if (cmd.name === 'insertText') {
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value, { bodyOnly: true });
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value, markupRenderOptions({ bodyOnly: true }));
editor.insertContent(result.html);
} else if (cmd.name === 'editor.focus') {
editor.focus();
@@ -559,11 +559,21 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const toolbarPluginButtons = pluginCommandNames.length ? ` | ${pluginCommandNames.join(' ')}` : '';
// The toolbar is going to wrap based on groups of buttons
// (delimited by |). It means that if we leave large groups of
// buttons towards the end of the toolbar it's going to needlessly
// hide many buttons even when there is space. So this is why below,
// we create small groups of just one button towards the end.
const toolbar = [
'bold', 'italic', 'joplinHighlight', 'joplinStrikethrough', 'formattingExtras', '|',
'link', 'joplinInlineCode', 'joplinCodeBlock', 'joplinAttach', '|',
'bullist', 'numlist', 'joplinChecklist', '|',
'h1', 'h2', 'h3', 'hr', 'blockquote', 'table', `joplinInsertDateTime${toolbarPluginButtons}`,
'h1', 'h2', 'h3', '|',
'hr', '|',
'blockquote', '|',
'table', '|',
`joplinInsertDateTime${toolbarPluginButtons}`,
];
const editors = await (window as any).tinymce.init({

View File

@@ -48,6 +48,7 @@ import ItemChange from '@joplin/lib/models/ItemChange';
import PlainEditor from './NoteBody/PlainEditor/PlainEditor';
import CodeMirror6 from './NoteBody/CodeMirror/v6/CodeMirror';
import CodeMirror5 from './NoteBody/CodeMirror/v5/CodeMirror';
import { namespacedKey } from '@joplin/lib/services/plugins/api/JoplinSettings';
const commands = [
require('./commands/showRevisions'),
@@ -159,10 +160,15 @@ function NoteEditor(props: NoteEditorProps) {
return formNote.saveActionQueue.waitForAllDone();
}
const settingValue = useCallback((pluginId: string, key: string) => {
return Setting.value(namespacedKey(pluginId, key));
}, []);
const markupToHtml = useMarkupToHtml({
themeId: props.themeId,
customCss: props.customCss,
plugins: props.plugins,
settingValue,
});
const allAssets = useCallback(async (markupLanguage: number, options: AllAssetsOptions = null): Promise<any[]> => {

View File

@@ -0,0 +1,64 @@
import WhenClause from '@joplin/lib/services/WhenClause';
import { enabledCondition } from './editorCommandDeclarations';
const baseContext: Record<string, any> = {
modalDialogVisible: false,
gotoAnythingVisible: false,
markdownEditorPaneVisible: true,
oneNoteSelected: true,
noteIsMarkdown: true,
noteIsReadOnly: false,
richTextEditorVisible: false,
};
describe('editorCommandDeclarations', () => {
test.each([
[
{},
true,
],
[
{
markdownEditorPaneVisible: false,
},
false,
],
[
{
noteIsReadOnly: true,
},
false,
],
[
// In the Markdown editor, but only the viewer is visible
{
markdownEditorPaneVisible: false,
richTextEditorVisible: false,
},
false,
],
[
// In the Markdown editor, and the viewer is visible
{
markdownEditorPaneVisible: true,
richTextEditorVisible: false,
},
true,
],
[
// In the RT editor
{
markdownEditorPaneVisible: false,
richTextEditorVisible: true,
},
true,
],
])('should create the enabledCondition', (context: Record<string, any>, expected: boolean) => {
const condition = enabledCondition('textBold');
const wc = new WhenClause(condition);
const actual = wc.evaluate({ ...baseContext, ...context });
expect(actual).toBe(expected);
});
});

View File

@@ -9,7 +9,17 @@ const workWithHtmlNotes = [
export const enabledCondition = (commandName: string) => {
const markdownEditorOnly = !Object.keys(joplinCommandToTinyMceCommands).includes(commandName);
const noteMustBeMarkdown = !workWithHtmlNotes.includes(commandName);
return `(!modalDialogVisible || gotoAnythingVisible) ${markdownEditorOnly ? '&& markdownEditorPaneVisible' : ''} && oneNoteSelected ${noteMustBeMarkdown ? '&& noteIsMarkdown' : ''} && !noteIsReadOnly`;
const output = [
'!modalDialogVisible',
'!gotoAnythingVisible',
markdownEditorOnly ? 'markdownEditorPaneVisible' : '(markdownEditorPaneVisible || richTextEditorVisible)',
'oneNoteSelected',
noteMustBeMarkdown ? 'noteIsMarkdown' : '',
'!noteIsReadOnly',
];
return output.filter(c => !!c).join(' && ');
};
const declarations: CommandDeclaration[] = [

View File

@@ -9,7 +9,10 @@ export async function htmlToMarkdown(markupLanguage: number, html: string, origi
if (markupLanguage === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN) {
const htmlToMd = new HtmlToMd();
newBody = htmlToMd.parse(html, { preserveImageTagsWithSize: true });
newBody = htmlToMd.parse(html, {
preserveImageTagsWithSize: true,
preserveNestedTables: true,
});
newBody = await Note.replaceResourceExternalToInternalLinks(newBody, { useAbsolutePaths: true });
} else {
newBody = await Note.replaceResourceExternalToInternalLinks(html, { useAbsolutePaths: true });

View File

@@ -12,6 +12,7 @@ interface HookDependencies {
themeId: number;
customCss: string;
plugins: PluginStates;
settingValue: (pluginId: string, key: string)=> any;
}
export interface MarkupToHtmlOptions {
@@ -59,12 +60,16 @@ export default function useMarkupToHtml(deps: HookDependencies) {
delete options.replaceResourceInternalToExternalLinks;
const result = await markupToHtml.render(markupLanguage, md, theme, { codeTheme: theme.codeThemeCss,
const result = await markupToHtml.render(markupLanguage, md, theme, {
codeTheme: theme.codeThemeCss,
resources: resources,
postMessageSyntax: 'ipcProxySendToHost',
splitted: true,
externalAssetsOnly: true,
codeHighlightCacheKey: 'useMarkupToHtml', ...options });
codeHighlightCacheKey: 'useMarkupToHtml',
settingValue: deps.settingValue,
...options,
});
return result;
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied

View File

@@ -145,6 +145,7 @@ const NoteListItem = (props: NoteItemProps, ref: LegacyRef<HTMLDivElement>) => {
tabIndex={0}
className={className}
data-id={noteId}
style={{ height: props.itemSize.height }}
onContextMenu={props.onContextMenu}
onDragStart={props.onDragStart}
onDragOver={props.onDragOver}

View File

@@ -2,18 +2,18 @@ import * as React from 'react';
import { useState, useEffect } from 'react';
import ButtonBar from '../ConfigScreen/ButtonBar';
import { _ } from '@joplin/lib/locale';
const { connect } = require('react-redux');
import Setting from '@joplin/lib/models/Setting';
const { themeStyle } = require('@joplin/lib/theme');
import ReportService from '@joplin/lib/services/ReportService';
import { themeStyle } from '@joplin/lib/theme';
import ReportService, { ReportItem, ReportSection, RetryAllHandler } from '@joplin/lib/services/ReportService';
import Button, { ButtonLevel } from '../Button/Button';
import bridge from '../../services/bridge';
const fs = require('fs-extra');
import styled from 'styled-components';
import { AppState } from '../../app.reducer';
import { writeFileSync } from 'fs';
interface Props {
themeId: string;
themeId: number;
style: any;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
dispatch: Function;
@@ -34,12 +34,12 @@ async function exportDebugReportClick() {
if (!filePath) return;
const service = new ReportService();
const csv = await service.basicItemList({ format: 'csv' });
await fs.writeFileSync(filePath, csv);
const csv = (await service.basicItemList({ format: 'csv' })) as string;
await writeFileSync(filePath, csv);
}
function StatusScreen(props: Props) {
const [report, setReport] = useState<any[]>([]);
const [report, setReport] = useState<ReportSection[]>([]);
async function resfreshScreen() {
const service = new ReportService();
@@ -65,7 +65,7 @@ function StatusScreen(props: Props) {
const containerStyle = { ...theme.containerStyle, padding: containerPadding,
flex: 1 };
function renderSectionTitleHtml(key: string, title: string) {
function renderSectionTitle(key: string, title: string) {
return (
<h2 key={`section_${key}`} style={theme.h2Style}>
{title}
@@ -73,7 +73,7 @@ function StatusScreen(props: Props) {
);
}
function renderSectionRetryAllHtml(key: string, retryAllHandler: any) {
function renderSectionRetryAll(key: string, retryAllHandler: RetryAllHandler) {
return (
<a key={`retry_all_${key}`} href="#" onClick={retryAllHandler} style={retryAllStyle}>
{_('Retry All')}
@@ -81,13 +81,26 @@ function StatusScreen(props: Props) {
);
}
const renderSectionHtml = (key: string, section: any) => {
const itemsHtml = [];
const renderRetryAll = (section: ReportSection) => {
const items: React.JSX.Element[] = [];
if (section.canRetryAll) {
items.push(renderSectionRetryAll(section.title, async () => {
await section.retryAllHandler();
void resfreshScreen();
}));
}
return items;
};
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
const renderSection = (key: string, section: ReportSection) => {
let items = [];
items.push(renderSectionTitle(section.title, section.title));
items = items.concat(renderRetryAll(section));
let currentListKey = '';
let listItems: any[] = [];
let listItems: React.JSX.Element[] = [];
for (const n in section.body) {
if (!section.body.hasOwnProperty(n)) continue;
const item = section.body[n];
@@ -115,12 +128,12 @@ function StatusScreen(props: Props) {
}
if (itemType === 'openList') {
currentListKey = item.key;
currentListKey = (item as ReportItem).key;
continue;
}
if (itemType === 'closeList') {
itemsHtml.push(<ul key={currentListKey}>{listItems}</ul>);
items.push(<ul key={currentListKey}>{listItems}</ul>);
currentListKey = '';
listItems = [];
continue;
@@ -136,7 +149,7 @@ function StatusScreen(props: Props) {
</li>,
);
} else {
itemsHtml.push(
items.push(
<div style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
@@ -145,26 +158,21 @@ function StatusScreen(props: Props) {
}
}
if (section.canRetryAll) {
itemsHtml.push(renderSectionRetryAllHtml(section.title, async () => {
await section.retryAllHandler();
void resfreshScreen();
}));
}
items = items.concat(renderRetryAll(section));
return <div key={key}>{itemsHtml}</div>;
return <div key={key}>{items}</div>;
};
function renderBodyHtml(report: any) {
const sectionsHtml = [];
function renderBody(report: ReportSection[]) {
const sections = [];
for (let i = 0; i < report.length; i++) {
const section = report[i];
if (!section.body.length) continue;
sectionsHtml.push(renderSectionHtml(`${i}`, section));
sections.push(renderSection(`${i}`, section));
}
return <div>{sectionsHtml}</div>;
return <div>{sections}</div>;
}
function renderTools() {
@@ -180,7 +188,7 @@ function StatusScreen(props: Props) {
);
}
const body = renderBodyHtml(report);
const body = renderBody(report);
return (
<div style={style}>
@@ -195,7 +203,7 @@ function StatusScreen(props: Props) {
);
}
const mapStateToProps = (state: any) => {
const mapStateToProps = (state: AppState) => {
return {
themeId: state.settings.theme,
settings: state.settings,

View File

@@ -5,6 +5,8 @@ import SettingsScreen from './models/SettingsScreen';
import { _electron as electron } from '@playwright/test';
import { writeFile } from 'fs-extra';
import { join } from 'path';
import createStartupArgs from './util/createStartupArgs';
import firstNonDevToolsWindow from './util/firstNonDevToolsWindow';
test.describe('main', () => {
@@ -130,11 +132,9 @@ test.describe('main', () => {
// We need to write to the force-safe-mode file before opening the Electron app.
// Open the app ourselves:
const startupArgs = [
'main.js', '--env', 'dev', '--profile', profileDirectory,
];
const startupArgs = createStartupArgs(profileDirectory);
const electronApp = await electron.launch({ args: startupArgs });
const mainWindow = await electronApp.firstWindow();
const mainWindow = await firstNonDevToolsWindow(electronApp);
const safeModeDisableLink = mainWindow.getByText('Disable safe mode and restart');
await safeModeDisableLink.waitFor();

View File

@@ -0,0 +1,9 @@
const createStartupArgs = (profileDirectory: string) => {
// We need to run with --env dev to disable the single instance check.
return [
'main.js', '--env', 'dev', '--profile', profileDirectory,
];
};
export default createStartupArgs;

View File

@@ -0,0 +1,43 @@
import { ElectronApplication, Page } from '@playwright/test';
const isDevTools = async (page: Page) => {
// It seems that the developer tools window can have titles in different
// formats (e.g. DevTools, Developer Tools).
return (await page.title()).match(/Dev(eloper)?\s*Tools/i);
};
const firstNonDevToolsWindow = async (electronApp: ElectronApplication) => {
// Wait for the window event as soon as possible -- it's possible that
// the window we want will be shown while doing other async checks.
const nextNonDevToolsPage = electronApp.waitForEvent('window', {
predicate: async page => {
return !(await isDevTools(page));
},
});
// First use firstWindow -- it's possible that the first window
// has already been shown.
let mainWindow = await electronApp.firstWindow();
if (await isDevTools(mainWindow)) {
for (const window of electronApp.windows()) {
if (!(await isDevTools(window))) {
mainWindow = window;
break;
}
}
if (await isDevTools(mainWindow)) {
mainWindow = await nextNonDevToolsPage;
}
}
// waitForEvent will throw if no additional windows are created.
// Ignore.
// eslint-disable-next-line promise/prefer-await-to-then
nextNonDevToolsPage.catch(_error => {});
return mainWindow;
};
export default firstNonDevToolsWindow;

View File

@@ -2,6 +2,8 @@ import { resolve, join, dirname } from 'path';
import { remove, mkdirp } from 'fs-extra';
import { _electron as electron, Page, ElectronApplication, test as base } from '@playwright/test';
import uuid from '@joplin/lib/uuid';
import createStartupArgs from './createStartupArgs';
import firstNonDevToolsWindow from './firstNonDevToolsWindow';
@@ -32,9 +34,7 @@ export const test = base.extend<JoplinFixtures>({
},
electronApp: async ({ profileDirectory }, use) => {
const startupArgs = [
'main.js', '--env', 'dev', '--profile', profileDirectory,
];
const startupArgs = createStartupArgs(profileDirectory);
const electronApp = await electron.launch({ args: startupArgs });
await use(electronApp);
@@ -44,8 +44,8 @@ export const test = base.extend<JoplinFixtures>({
},
mainWindow: async ({ electronApp }, use) => {
const window = await electronApp.firstWindow();
await use(window);
const mainWindow = await firstNonDevToolsWindow(electronApp);
await use(mainWindow);
},
});

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.13.4",
"version": "2.13.7",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -119,21 +119,21 @@
"@joplin/tools": "~2.13",
"@playwright/test": "1.38.1",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.4",
"@types/node": "18.17.19",
"@types/react": "18.2.31",
"@types/react-redux": "7.1.27",
"@types/styled-components": "5.1.28",
"electron": "25.9.0",
"electron-builder": "24.4.0",
"@types/jest": "29.5.5",
"@types/node": "18.18.7",
"@types/react": "18.2.33",
"@types/react-redux": "7.1.28",
"@types/styled-components": "5.1.29",
"electron": "26.5.0",
"electron-builder": "24.6.4",
"glob": "10.3.10",
"gulp": "4.0.2",
"jest": "29.6.4",
"jest-environment-jsdom": "29.6.4",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"js-sha512": "0.8.0",
"nan": "2.18.0",
"react-test-renderer": "18.2.0",
"typescript": "5.1.6"
"typescript": "5.2.2"
},
"optionalDependencies": {
"7zip-bin-linux": "^1.0.1",
@@ -142,14 +142,14 @@
},
"dependencies": {
"@electron/notarize": "2.1.0",
"@electron/remote": "2.0.11",
"@electron/remote": "2.0.12",
"@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4",
"@joplin/editor": "~2.13",
"@joplin/lib": "~2.13",
"@joplin/renderer": "~2.13",
"@joplin/utils": "~2.13",
"@types/mustache": "4.2.3",
"@types/mustache": "4.2.4",
"async-mutex": "0.4.0",
"codemirror": "5.65.9",
"color": "3.2.1",

View File

@@ -32,7 +32,7 @@ export default function(frameWindow: any, isReady: boolean, postMessage: Functio
frameWindow.addEventListener('message', onMessage);
return () => {
frameWindow.removeEventListener('message', onMessage);
if (frameWindow.removeEventListener) frameWindow.removeEventListener('message', onMessage);
};
}, [frameWindow, htmlHash]);

View File

@@ -5,8 +5,8 @@
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
TEMP_PATH=~/src/plugin-tests
NEED_COMPILING=0
PLUGIN_PATH=~/src/joplin/packages/app-cli/tests/support/plugins/simple
NEED_COMPILING=1
PLUGIN_PATH=~/src/plugin-abc
if [[ $NEED_COMPILING == 1 ]]; then
mkdir -p "$TEMP_PATH"

View File

@@ -110,8 +110,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097725
versionName "2.13.5"
versionCode 2097730
versionName "2.13.10"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -1,10 +1,12 @@
const React = require('react');
import { useState, useCallback, useMemo } from 'react';
const Icon = require('react-native-vector-icons/Ionicons').default;
import { FAB, Portal } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';
import { Dispatch } from 'redux';
const Icon = require('react-native-vector-icons/Ionicons').default;
// eslint-disable-next-line no-undef -- Don't know why it says React is undefined when it's defined above
type FABGroupProps = React.ComponentProps<typeof FAB.Group>;
type OnButtonPress = ()=> void;
interface ButtonSpec {
@@ -19,6 +21,7 @@ interface ActionButtonProps {
// If not given, an "add" button will be used.
mainButton?: ButtonSpec;
dispatch: Dispatch;
}
const defaultOnPress = () => {};
@@ -36,10 +39,12 @@ const useIcon = (iconName: string) => {
const ActionButton = (props: ActionButtonProps) => {
const [open, setOpen] = useState(false);
const onMenuToggled = useCallback(
(state: { open: boolean }) => setOpen(state.open)
, [setOpen]);
const onMenuToggled: FABGroupProps['onStateChange'] = useCallback(state => {
props.dispatch({
type: 'SIDE_MENU_CLOSE',
});
setOpen(state.open);
}, [setOpen, props.dispatch]);
const actions = useMemo(() => (props.buttons ?? []).map(button => {
return {

View File

@@ -178,6 +178,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
onRequestClose={() => {
closeList();
}}
supportedOrientations={['landscape', 'portrait']}
>
<TouchableWithoutFeedback
accessibilityElementsHidden={true}

View File

@@ -48,6 +48,8 @@ interface Props {
// See react-native-webview's prop with the same name.
mixedContentMode?: 'never' | 'always';
allowFileAccessFromJs?: boolean;
// Initial javascript. Must evaluate to true.
injectedJavaScript: string;
@@ -143,6 +145,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
originWhitelist={['file://*', './*', 'http://*', 'https://*']}
mixedContentMode={props.mixedContentMode}
allowFileAccess={true}
allowFileAccessFromFileURLs={props.allowFileAccessFromJs}
injectedJavaScript={props.injectedJavaScript}
onMessage={props.onMessage}
onError={props.onError}

View File

@@ -0,0 +1,44 @@
import * as React from 'react';
import { TextStyle } from 'react-native';
const FontAwesomeIcon = require('react-native-vector-icons/FontAwesome5').default;
interface Props {
name: string;
style: TextStyle;
// If `null` is given, the content must be labeled elsewhere.
accessibilityLabel: string|null;
}
const Icon: React.FC<Props> = props => {
// Matches:
// 1. A prefix of word characters (\w+)
// 2. A suffix of non-spaces (\S+)
// An "fa-" at the beginning of the suffix is ignored.
const nameMatch = props.name.match(/^(\w+)\s+(?:fa-)?(\S+)$/);
const namePrefix = nameMatch ? nameMatch[1] : '';
const nameSuffix = nameMatch ? nameMatch[2] : props.name;
// If there's no label, make sure that the screen reader doesn't try
// to read the characters from the icon font (they don't make sense
// without the icon font applied).
const accessibilityHidden = props.accessibilityLabel === null;
return (
<FontAwesomeIcon
brand={namePrefix.startsWith('fab')}
solid={namePrefix.startsWith('fas')}
accessibilityLabel={props.accessibilityLabel}
aria-hidden={accessibilityHidden}
importantForAccessibility={
accessibilityHidden ? 'no-hide-descendants' : 'yes'
}
name={nameSuffix}
style={props.style}
/>
);
};
export default Icon;

View File

@@ -1,14 +1,14 @@
import { useRef, useCallback } from 'react';
import useSource from './hooks/useSource';
import useOnMessage, { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
import useOnMessage, { HandleMessageCallback, HandleScrollCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
import useOnResourceLongPress from './hooks/useOnResourceLongPress';
const React = require('react');
import { View } from 'react-native';
import BackButtonDialogBox from '../BackButtonDialogBox';
import { reg } from '@joplin/lib/registry';
import ExtendedWebView from '../ExtendedWebView';
import ExtendedWebView, { WebViewControl } from '../ExtendedWebView';
interface Props {
themeId: number;
@@ -18,11 +18,13 @@ interface Props {
highlightedKeywords: string[];
noteResources: any;
paddingBottom: number;
initialScroll: number|null;
noteHash: string;
onJoplinLinkClick: HandleMessageCallback;
onCheckboxChange?: HandleMessageCallback;
onRequestEditResource?: HandleMessageCallback;
onMarkForDownload?: OnMarkForDownloadCallback;
onScroll: HandleScrollCallback;
onLoadEnd?: ()=> void;
}
@@ -32,6 +34,7 @@ const webViewStyle = {
export default function NoteBodyViewer(props: Props) {
const dialogBoxRef = useRef(null);
const webviewRef = useRef<WebViewControl>(null);
const { html, injectedJs } = useSource(
props.noteBody,
@@ -41,6 +44,7 @@ export default function NoteBodyViewer(props: Props) {
props.noteResources,
props.paddingBottom,
props.noteHash,
props.initialScroll,
);
const onResourceLongPress = useOnResourceLongPress(
@@ -59,6 +63,7 @@ export default function NoteBodyViewer(props: Props) {
onJoplinLinkClick: props.onJoplinLinkClick,
onRequestEditResource: props.onRequestEditResource,
onResourceLongPress,
onMainContainerScroll: props.onScroll,
},
);
@@ -96,6 +101,7 @@ export default function NoteBodyViewer(props: Props) {
return (
<View style={props.style}>
<ExtendedWebView
ref={webviewRef}
webviewInstanceId='NoteBodyViewer'
themeId={props.themeId}
style={webViewStyle}

View File

@@ -3,6 +3,7 @@ import shared from '@joplin/lib/components/shared/note-screen-shared';
export type HandleMessageCallback = (message: string)=> void;
export type OnMarkForDownloadCallback = (resource: { resourceId: string })=> void;
export type HandleScrollCallback = (scrollTop: number)=> void;
interface MessageCallbacks {
onMarkForDownload?: OnMarkForDownloadCallback;
@@ -10,6 +11,7 @@ interface MessageCallbacks {
onResourceLongPress: HandleMessageCallback;
onRequestEditResource?: HandleMessageCallback;
onCheckboxChange: HandleMessageCallback;
onMainContainerScroll: HandleScrollCallback;
}
export default function useOnMessage(
@@ -24,6 +26,7 @@ export default function useOnMessage(
// Thus, useCallback should depend on each callback individually.
const {
onMarkForDownload, onResourceLongPress, onCheckboxChange, onRequestEditResource, onJoplinLinkClick,
onMainContainerScroll,
} = callbacks;
return useCallback((event: any) => {
@@ -35,10 +38,23 @@ export default function useOnMessage(
// https://github.com/laurent22/joplin/issues/4494
const msg = event.nativeEvent.data;
// eslint-disable-next-line no-console
console.info('Got IPC message: ', msg);
const isScrollMessage = msg.startsWith('onscroll:');
if (msg.indexOf('checkboxclick:') === 0) {
// Scroll messages are very frequent so we avoid logging them.
if (!isScrollMessage) {
// eslint-disable-next-line no-console
console.info('Got IPC message: ', msg);
}
if (isScrollMessage) {
const eventData = JSON.parse(msg.substring(msg.indexOf(':') + 1));
if (typeof eventData.scrollTop !== 'number') {
throw new Error(`Invalid scroll message, ${msg}`);
}
onMainContainerScroll?.(eventData.scrollTop);
} else if (msg.indexOf('checkboxclick:') === 0) {
const newBody = shared.toggleCheckbox(msg, noteBody);
onCheckboxChange?.(newBody);
} else if (msg.indexOf('markForDownload:') === 0) {
@@ -63,5 +79,6 @@ export default function useOnMessage(
onJoplinLinkClick,
onResourceLongPress,
onRequestEditResource,
onMainContainerScroll,
]);
}

View File

@@ -40,7 +40,16 @@ const onlyCheckboxHasChangedHack = (previousBody: string, newBody: string) => {
return true;
};
export default function useSource(noteBody: string, noteMarkupLanguage: number, themeId: number, highlightedKeywords: string[], noteResources: any, paddingBottom: number, noteHash: string): UseSourceResult {
export default function useSource(
noteBody: string,
noteMarkupLanguage: number,
themeId: number,
highlightedKeywords: string[],
noteResources: any,
paddingBottom: number,
noteHash: string,
initialScroll: number|null,
): UseSourceResult {
const [html, setHtml] = useState<string>('');
const [injectedJs, setInjectedJs] = useState<string[]>([]);
const [resourceLoadedTime, setResourceLoadedTime] = useState(0);
@@ -142,6 +151,12 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
const resourceDownloadMode = Setting.value('sync.resourceDownloadMode');
// On iOS, the root container has slow inertial scroll, which feels very different from
// the native scroll in other apps. This is not the case, however, when a child (e.g. a div)
// scrolls the content instead.
// Use a div to scroll on iOS instead of the main container:
const scrollRenderedMdContainer = shim.mobilePlatform() === 'ios';
const js = [];
js.push('try {');
js.push(shim.injectedJs('webviewLib'));
@@ -149,15 +164,46 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
// the ReactNativeWebView actually supports only one, so the second arg is ignored (and currently not needed for the mobile app).
js.push('window.joplinPostMessage_ = (msg, args) => { return window.ReactNativeWebView.postMessage(msg); };');
js.push('webviewLib.initialize({ postMessage: msg => { return window.ReactNativeWebView.postMessage(msg); } });');
js.push(`
const scrollingElement =
${scrollRenderedMdContainer ? 'document.querySelector("#rendered-md")' : 'document.scrollingElement'};
let lastScrollTop;
const onMainContentScroll = () => {
const newScrollTop = scrollingElement.scrollTop;
if (lastScrollTop !== newScrollTop) {
const eventData = { scrollTop: newScrollTop };
window.ReactNativeWebView.postMessage('onscroll:' + JSON.stringify(eventData));
}
};
// Listen for events on both scrollingElement and window
// - On Android, scrollingElement.addEventListener('scroll', callback) doesn't call callback on
// scroll. However, window.addEventListener('scroll', callback) does.
// - iOS needs a listener to be added to scrollingElement -- events aren't received when
// the listener is added to window with window.addEventListener('scroll', ...).
scrollingElement.addEventListener('scroll', onMainContentScroll);
window.addEventListener('scroll', onMainContentScroll);
const scrollContentToPosition = (position) => {
scrollingElement.scrollTop = position;
};
`);
js.push(`
const readyStateCheckInterval = setInterval(function() {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
if ("${resourceDownloadMode}" === "manual") webviewLib.setupResourceManualDownload();
const hash = "${noteHash}";
// Gives it a bit of time before scrolling to the anchor
// so that images are loaded.
if (hash) {
const initialScroll = ${JSON.stringify(initialScroll)};
// Don't scroll to a hash if we're given initial scroll (initial scroll
// overrides scrolling to a hash).
if ((initialScroll ?? null) !== null) {
scrollContentToPosition(initialScroll);
} else if (hash) {
// Gives it a bit of time before scrolling to the anchor
// so that images are loaded.
setTimeout(() => {
const e = document.getElementById(hash);
if (!e) {
@@ -171,6 +217,7 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
}, 10);
`);
js.push('} catch (e) {');
js.push(' console.error(e);');
js.push(' window.ReactNativeWebView.postMessage("error:" + e.message + ": " + JSON.stringify(e))');
js.push(' true;');
js.push('}');
@@ -186,10 +233,11 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
}
}
/*
iOS seems to increase inertial scrolling friction when the WebView body/root elements
scroll. Scroll the main container instead.
*/
:root > body {
padding: 0;
}
`;
const scrollRenderedMdContainerCss = `
body > #rendered-md {
width: 100vw;
overflow: auto;
@@ -197,10 +245,6 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
padding-bottom: ${paddingBottom}px;
padding-top: ${paddingTop};
}
:root > body {
padding: 0;
}
`;
const defaultCss = `
code {
@@ -219,6 +263,7 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
<style>
${defaultCss}
${shim.mobilePlatform() === 'ios' ? iOSSpecificCss : ''}
${scrollRenderedMdContainer ? scrollRenderedMdContainerCss : ''}
${editPopupCss}
</style>
${assetsToHeaders(result.pluginAssets, { asHtml: true })}

View File

@@ -11,18 +11,17 @@ import { WebViewMessageEvent } from 'react-native-webview';
import ExtendedWebView, { WebViewControl } from '../../ExtendedWebView';
import { clearAutosave, writeAutosave } from './autosave';
import { LocalizedStrings } from './js-draw/types';
import VersionInfo from 'react-native-version-info';
const logger = Logger.create('ImageEditor');
type OnSaveCallback = (svgData: string)=> void;
type OnCancelCallback = ()=> void;
// Returns the empty string to load from a template.
type LoadInitialSVGCallback = ()=> Promise<string>;
interface Props {
themeId: number;
loadInitialSVGData: LoadInitialSVGCallback;
resourceFilename: string|null;
onSave: OnSaveCallback;
onExit: OnCancelCallback;
}
@@ -130,20 +129,31 @@ const ImageEditor = (props: Props) => {
}, [onRequestCloseEditor]);
const css = useCss(editorTheme);
const html = useMemo(() => `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
const [html, setHtml] = useState('');
<style>
${css}
</style>
</head>
<body></body>
</html>
`, [css]);
useEffect(() => {
setHtml(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
<style id='main-style'>
${css}
</style>
</head>
<body></body>
</html>
`);
// Only set HTML initially (and don't reset). Changing the HTML reloads
// the page.
//
// We need the HTML to initially have the correct CSS to prevent color
// changes on load.
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps
}, []);
// A set of localization overrides (Joplin is better localized than js-draw).
// All localizable strings (some unused?) can be found at
@@ -155,10 +165,23 @@ const ImageEditor = (props: Props) => {
redo: _('Redo'),
}), []);
const appInfo = useMemo(() => {
return {
name: 'Joplin',
description: `v${VersionInfo.appVersion}`,
};
}, []);
const injectedJavaScript = useMemo(() => `
window.onerror = (message, source, lineno) => {
window.ReactNativeWebView.postMessage(
"error: " + message + " in file://" + source + ", line " + lineno
"error: " + message + " in file://" + source + ", line " + lineno,
);
};
window.onunhandledrejection = (error) => {
window.ReactNativeWebView.postMessage(
"error: " + error.reason,
);
};
@@ -218,6 +241,7 @@ const ImageEditor = (props: Props) => {
${JSON.stringify(Setting.value('imageeditor.jsdrawToolbar'))},
${JSON.stringify(Setting.value('locale'))},
${JSON.stringify(localizedStrings)},
${JSON.stringify({ appInfo })},
);
// Start loading the SVG file (if present) after loading the editor.
@@ -231,30 +255,30 @@ const ImageEditor = (props: Props) => {
);
}
true;
`, [localizedStrings]);
`, [localizedStrings, appInfo]);
useEffect(() => {
webviewRef.current?.injectJS(`
document.querySelector('#main-style').innerText = ${JSON.stringify(css)};
if (window.editorControl) {
window.editorControl.onThemeUpdate();
}
`);
}, [editorTheme]);
}, [css]);
const onReadyToLoadData = useCallback(async () => {
const initialSVGData = await props.loadInitialSVGData?.() ?? '';
// It can take some time for initialSVGData to be transferred to the WebView.
// Thus, do so after the main content has been loaded.
webviewRef.current.injectJS(`(async () => {
if (window.editorControl) {
const initialSVGData = ${JSON.stringify(initialSVGData)};
const initialSVGPath = ${JSON.stringify(props.resourceFilename)};
const initialTemplateData = ${JSON.stringify(Setting.value('imageeditor.imageTemplate'))};
editorControl.loadImageOrTemplate(initialSVGData, initialTemplateData);
editorControl.loadImageOrTemplate(initialSVGPath, initialTemplateData);
}
})();`);
}, [webviewRef, props.loadInitialSVGData]);
}, [webviewRef, props.resourceFilename]);
const onMessage = useCallback(async (event: WebViewMessageEvent) => {
const data = event.nativeEvent.data;
@@ -293,6 +317,7 @@ const ImageEditor = (props: Props) => {
themeId={props.themeId}
html={html}
injectedJavaScript={injectedJavaScript}
allowFileAccessFromJs={true}
onMessage={onMessage}
onError={onError}
ref={webviewRef}

View File

@@ -57,7 +57,7 @@ describe('createJsDrawEditor', () => {
});
// Load no image and an empty template so that autosave can start
await editorControl.loadImageOrTemplate(undefined, '{}');
await editorControl.loadImageOrTemplate('', '{}');
expect(calledAutosaveCount).toBe(0);

View File

@@ -120,20 +120,44 @@ export const createJsDrawEditor = (
editor.showLoadingWarning(0);
editor.setReadOnly(true);
const fetchInitialSvgData = (resourceUrl: string) => {
return new Promise<string>((resolve, reject) => {
if (!resourceUrl) {
resolve('');
}
// fetch seems to be unable to request file:// URLs.
// https://github.com/react-native-webview/react-native-webview/issues/1560#issuecomment-1783611805
const request = new XMLHttpRequest();
const onError = () => {
reject(`Failed to load initial SVG data: ${request.status}, ${request.statusText}, ${request.responseText}`);
};
request.addEventListener('load', _ => {
resolve(request.responseText);
});
request.addEventListener('error', onError);
request.addEventListener('abort', onError);
request.open('GET', resourceUrl);
request.send();
});
};
const editorControl = {
editor,
loadImageOrTemplate: async (svgData: string|undefined, templateData: string) => {
loadImageOrTemplate: async (resourceUrl: string, templateData: string) => {
// loadFromSVG shows its own loading message. Hide the original.
editor.hideLoadingWarning();
if (svgData && svgData.length > 0) {
await editor.loadFromSVG(svgData);
} else {
await applyTemplateToEditor(editor, templateData);
const svgData = await fetchInitialSvgData(resourceUrl);
// The editor expects to be saved initially (without
// unsaved changes). Save now.
saveNow();
// Load from a template if no initial data
if (svgData === '') {
await applyTemplateToEditor(editor, templateData);
} else {
await editor.loadFromSVG(svgData);
}
// We can now edit and save safely (without data loss).

View File

@@ -6,8 +6,8 @@ import { defaultSearchState, SearchPanel } from './SearchPanel';
import ExtendedWebView from '../ExtendedWebView';
import * as React from 'react';
import { forwardRef, RefObject, useImperativeHandle } from 'react';
import { useEffect, useMemo, useState, useCallback, useRef } from 'react';
import { forwardRef, useImperativeHandle } from 'react';
import { useMemo, useState, useCallback, useRef } from 'react';
import { LayoutChangeEvent, View, ViewStyle } from 'react-native';
const { editorFont } = require('../global-style');
@@ -126,7 +126,6 @@ type OnSearchStateChangeCallback = (state: SearchState)=> void;
const useEditorControl = (
injectJS: OnInjectJSCallback, setLinkDialogVisible: OnSetVisibleCallback,
setSearchState: OnSearchStateChangeCallback,
searchStateRef: RefObject<SearchState>,
): EditorControl => {
return useMemo(() => {
const execCommand = (command: EditorCommandType) => {
@@ -252,16 +251,10 @@ const useEditorControl = (
},
showSearch() {
setSearchState({
...searchStateRef.current,
dialogVisible: true,
});
execCommand(EditorCommandType.ShowSearch);
},
hideSearch() {
setSearchState({
...searchStateRef.current,
dialogVisible: false,
});
execCommand(EditorCommandType.HideSearch);
},
setSearchState: setSearchStateCallback,
@@ -269,7 +262,7 @@ const useEditorControl = (
};
return control;
}, [injectJS, searchStateRef, setLinkDialogVisible, setSearchState]);
}, [injectJS, setLinkDialogVisible, setSearchState]);
};
function NoteEditor(props: Props, ref: any) {
@@ -356,22 +349,13 @@ function NoteEditor(props: Props, ref: any) {
const [linkDialogVisible, setLinkDialogVisible] = useState(false);
const [searchState, setSearchState] = useState(defaultSearchState);
// Having a [searchStateRef] allows [editorControl] to not be re-created
// whenever [searchState] changes.
const searchStateRef = useRef(defaultSearchState);
// Keep the reference and the [searchState] in sync
useEffect(() => {
searchStateRef.current = searchState;
}, [searchState]);
// Runs [js] in the context of the CodeMirror frame.
const injectJS = (js: string) => {
webviewRef.current.injectJS(js);
};
const editorControl = useEditorControl(
injectJS, setLinkDialogVisible, setSearchState, searchStateRef,
injectJS, setLinkDialogVisible, setSearchState,
);
useImperativeHandle(ref, () => {

View File

@@ -40,9 +40,7 @@ interface ActionButtonProps {
onPress: Callback;
}
const ActionButton = (
props: ActionButtonProps,
) => {
const ActionButton = (props: ActionButtonProps) => {
return (
<CustomButton
themeId={props.themeId}

View File

@@ -70,6 +70,7 @@ interface ScreenHeaderProps {
onRedoButtonPress: OnPressCallback;
onSaveButtonPress: OnPressCallback;
sortButton_press?: OnPressCallback;
onSearchButtonPress?: OnPressCallback;
showSideMenuButton?: boolean;
showSearchButton?: boolean;
@@ -242,7 +243,11 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
}
private searchButton_press() {
void NavService.go('Search');
if (this.props.onSearchButtonPress) {
this.props.onSearchButtonPress();
} else {
void NavService.go('Search');
}
}
private async duplicateButton_press() {

View File

@@ -1,12 +1,12 @@
const React = require('react');
const { StyleSheet } = require('react-native');
import * as React from 'react';
import { StyleSheet } from 'react-native';
const { themeStyle } = require('./global-style.js');
const rootStyles_ = {};
const rootStyles_: Record<number, any> = {};
class BaseScreenComponent extends React.Component {
class BaseScreenComponent<Props, State> extends React.Component<Props, State> {
rootStyle(themeId) {
protected rootStyle(themeId: number) {
const theme = themeStyle(themeId);
if (rootStyles_[themeId]) return rootStyles_[themeId];
rootStyles_[themeId] = StyleSheet.create({
@@ -19,4 +19,5 @@ class BaseScreenComponent extends React.Component {
}
}
module.exports = { BaseScreenComponent };
export { BaseScreenComponent };
export default BaseScreenComponent;

View File

@@ -0,0 +1,65 @@
import * as React from 'react';
import shim from '@joplin/lib/shim';
import { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { ConfigScreenStyles } from './configScreenStyles';
import { TouchableNativeFeedback, View, Text } from 'react-native';
import Setting, { SettingItem } from '@joplin/lib/models/Setting';
import { openDocumentTree } from '@joplin/react-native-saf-x';
import { UpdateSettingValueCallback } from './types';
import { reg } from '@joplin/lib/registry';
interface Props {
styles: ConfigScreenStyles;
settingMetadata: SettingItem;
updateSettingValue: UpdateSettingValueCallback;
}
const FileSystemPathSelector: FunctionComponent<Props> = props => {
const [fileSystemPath, setFileSystemPath] = useState<string>('');
const settingId = props.settingMetadata.key;
useEffect(() => {
setFileSystemPath(Setting.value(settingId));
}, [settingId]);
const selectDirectoryButtonPress = useCallback(async () => {
try {
const doc = await openDocumentTree(true);
if (doc?.uri) {
setFileSystemPath(doc.uri);
await props.updateSettingValue(settingId, doc.uri);
} else {
throw new Error('User cancelled operation');
}
} catch (e) {
reg.logger().info('Didn\'t pick sync dir: ', e);
}
}, [props.updateSettingValue, settingId]);
// Unsupported on non-Android platforms.
if (!shim.fsDriver().isUsingAndroidSAF()) {
return null;
}
const styleSheet = props.styles.styleSheet;
return (
<TouchableNativeFeedback
onPress={selectDirectoryButtonPress}
style={styleSheet.settingContainer}
>
<View style={styleSheet.settingContainer}>
<Text key="label" style={styleSheet.settingText}>
{props.settingMetadata.label()}
</Text>
<Text style={styleSheet.settingControl}>
{fileSystemPath}
</Text>
</View>
</TouchableNativeFeedback>
);
};
export default FileSystemPathSelector;

View File

@@ -0,0 +1,44 @@
import * as React from 'react';
import { useCallback, useState } from 'react';
import { _ } from '@joplin/lib/locale';
import exportDebugReport from './utils/exportDebugReport';
import shim from '@joplin/lib/shim';
import SettingsButton from '../SettingsButton';
import { ConfigScreenStyles } from '../configScreenStyles';
interface Props {
styles: ConfigScreenStyles;
}
export const exportDebugReportTitle = () => _('Export Debug Report');
const ExportDebugReportButton = (props: Props) => {
const [creatingReport, setCreatingReport] = useState(false);
const exportDebugButtonPress = useCallback(async () => {
setCreatingReport(true);
await exportDebugReport();
setCreatingReport(false);
}, [setCreatingReport]);
const exportDebugReportButton = (
<SettingsButton
title={creatingReport ? _('Creating report...') : exportDebugReportTitle()}
clickHandler={exportDebugButtonPress}
styles={props.styles}
disabled={creatingReport}
/>
);
// The debug functionality is only supported on Android.
if (shim.mobilePlatform() !== 'android') {
return null;
}
return exportDebugReportButton;
};
export default ExportDebugReportButton;

View File

@@ -0,0 +1,81 @@
import * as React from 'react';
import { useCallback, useState } from 'react';
import { View, Button } from 'react-native';
import { TextInput } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';
import shim from '@joplin/lib/shim';
import exportProfile from './utils/exportProfile';
import { ConfigScreenStyles } from '../configScreenStyles';
import SettingsButton from '../SettingsButton';
interface Props {
styles: ConfigScreenStyles;
}
export const exportProfileButtonTitle = () => _('Export profile');
const ExportProfileButton = (props: Props) => {
const [profileExportStatus, setProfileExportStatus] = useState<'idle'|'prompt'|'exporting'>('idle');
const [profileExportPath, setProfileExportPath] = useState<string>('');
const exportProfileButtonPress = useCallback(async () => {
const externalDir = await shim.fsDriver().getExternalDirectoryPath();
if (!externalDir) {
return;
}
const p = profileExportPath ? profileExportPath : `${externalDir}/JoplinProfileExport`;
setProfileExportStatus('prompt');
setProfileExportPath(p);
}, [profileExportPath]);
const exportProfileButton = (
<SettingsButton
styles={props.styles}
title={profileExportStatus === 'exporting' ? _('Exporting profile...') : exportProfileButtonTitle()}
clickHandler={exportProfileButtonPress}
description={_('For debugging purpose only: export your profile to an external SD card.')}
disabled={profileExportStatus === 'exporting'}
/>
);
const exportProfileButtonPress2 = useCallback(async () => {
setProfileExportStatus('exporting');
await exportProfile(profileExportPath);
setProfileExportStatus('idle');
}, [profileExportPath]);
const profileExportPrompt = (
<View>
<TextInput
label={_('Path:')}
onChangeText={text => setProfileExportPath(text)}
value={profileExportPath}
placeholder="/path/to/sdcard"
keyboardAppearance={props.styles.keyboardAppearance} />
<Button
onPress={exportProfileButtonPress2}
title={_('OK')}
/>
</View>
);
const mainContent = (
<>
{exportProfileButton}
{profileExportStatus === 'prompt' ? profileExportPrompt : null}
</>
);
// The debug functionality is only supported on Android.
if (shim.mobilePlatform() !== 'android') {
return null;
}
return mainContent;
};
export default ExportProfileButton;

View File

@@ -7,10 +7,10 @@ import { FunctionComponent, useCallback, useState } from 'react';
import shim from '@joplin/lib/shim';
import { join } from 'path';
import Share from 'react-native-share';
import exportAllFolders, { makeExportCacheDirectory } from './exportAllFolders';
import exportAllFolders, { makeExportCacheDirectory } from './utils/exportAllFolders';
import { ExportProgressState } from '@joplin/lib/services/interop/types';
import { ConfigScreenStyles } from '../configScreenStyles';
import ConfigScreenButton from '../ConfigScreenButton';
import SettingsButton from '../SettingsButton';
const logger = Logger.create('NoteExportButton');
@@ -24,6 +24,9 @@ enum ExportStatus {
Exported,
}
export const exportButtonTitle = () => _('Export all notes as JEX');
export const exportButtonDescription = () => _('Share a copy of all notes in a file format that can be imported by Joplin on a computer.');
const NoteExportButton: FunctionComponent<Props> = props => {
const [exportStatus, setExportStatus] = useState<ExportStatus>(ExportStatus.NotStarted);
const [exportProgress, setExportProgress] = useState<number|undefined>(0);
@@ -80,13 +83,12 @@ const NoteExportButton: FunctionComponent<Props> = props => {
indeterminate={exportProgress === undefined}
progress={exportProgress}/>
);
const descriptionText = _('Share a copy of all notes in a file format that can be imported by Joplin on a computer.');
const startOrCancelExportButton = (
<ConfigScreenButton
title={exportStatus === ExportStatus.Exporting ? _('Exporting...') : _('Export all notes as JEX')}
<SettingsButton
title={exportStatus === ExportStatus.Exporting ? _('Exporting...') : exportButtonTitle()}
disabled={exportStatus === ExportStatus.Exporting}
description={exportStatus === ExportStatus.NotStarted ? descriptionText : null}
description={exportStatus === ExportStatus.NotStarted ? exportButtonDescription() : null}
statusComponent={progressComponent}
clickHandler={startExport}
styles={props.styles}
@@ -96,14 +98,14 @@ const NoteExportButton: FunctionComponent<Props> = props => {
return startOrCancelExportButton;
} else {
const warningComponent = (
<Text style={props.styles.warningText}>
<Text style={props.styles.styleSheet.warningText}>
{_('Warnings:\n%s', warnings)}
</Text>
);
const exportSummary = (
<View style={props.styles.settingContainer}>
<Text style={props.styles.descriptionText}>{_('Exported successfully!')}</Text>
<View style={props.styles.styleSheet.settingContainer}>
<Text style={props.styles.styleSheet.descriptionText}>{_('Exported successfully!')}</Text>
{warnings.length > 0 ? warningComponent : null}
</View>
);

View File

@@ -0,0 +1,32 @@
import { reg } from '@joplin/lib/registry';
import ReportService from '@joplin/lib/services/ReportService';
import shim from '@joplin/lib/shim';
import time from '@joplin/lib/time';
const exportDebugReport = async () => {
const service = new ReportService();
const logItems = await reg.logger().lastEntries(null);
const logItemRows = [['Date', 'Level', 'Message']];
for (let i = 0; i < logItems.length; i++) {
const item = logItems[i];
logItemRows.push([time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss'), item.level, item.message]);
}
const logItemCsv = service.csvCreate(logItemRows);
const itemListCsv = await service.basicItemList({ format: 'csv' });
const externalDir = await shim.fsDriver().getExternalDirectoryPath();
if (!externalDir) {
return;
}
const filePath = `${externalDir}/syncReport-${new Date().getTime()}.txt`;
const finalText = [logItemCsv, itemListCsv].join('\n================================================================================\n');
await shim.fsDriver().writeFile(filePath, finalText, 'utf8');
alert(`Debug report exported to ${filePath}`);
};
export default exportDebugReport;

View File

@@ -0,0 +1,35 @@
import shim from '@joplin/lib/shim';
import { reg } from '@joplin/lib/registry';
import Setting from '@joplin/lib/models/Setting';
const exportProfile = async (profileExportPath: string) => {
const dbPath = '/data/data/net.cozic.joplin/databases';
const exportPath = profileExportPath;
const resourcePath = `${exportPath}/resources`;
try {
const copyFiles = async (source: string, dest: string) => {
await shim.fsDriver().mkdir(dest);
const files = await shim.fsDriver().readDirStats(source);
for (const file of files) {
const source_ = `${source}/${file.path}`;
const dest_ = `${dest}/${file.path}`;
if (!file.isDirectory()) {
reg.logger().info(`Copying profile: ${source_} => ${dest_}`);
await shim.fsDriver().copy(source_, dest_);
} else {
await copyFiles(source_, dest_);
}
}
};
await copyFiles(dbPath, exportPath);
await copyFiles(Setting.value('resourceDir'), resourcePath);
alert('Profile has been exported!');
} catch (error) {
alert(`Could not export files: ${error.message}`);
}
};
export default exportProfile;

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import { ConfigScreenStyleSheet } from './configScreenStyles';
import { View, Text, LayoutChangeEvent } from 'react-native';
interface Props {
styles: ConfigScreenStyleSheet;
title: string;
onLayout?: (event: LayoutChangeEvent)=> void;
}
const SectionHeader: React.FunctionComponent<Props> = props => {
return (
<View
style={props.styles.headerWrapperStyle}
onLayout={props.onLayout}
>
<Text style={props.styles.headerTextStyle}>
{props.title}
</Text>
</View>
);
};
export default SectionHeader;

View File

@@ -0,0 +1,108 @@
import * as React from 'react';
import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { ConfigScreenStyles } from './configScreenStyles';
import { FlatList, Text, Pressable, View, ViewStyle } from 'react-native';
import { settingsSections } from '@joplin/lib/components/shared/config/config-shared';
import Icon from '../../Icon';
interface Props {
styles: ConfigScreenStyles;
width: number|undefined;
settings: any;
selectedSectionName: string|null;
openSection: (sectionName: string)=> void;
}
const SectionSelector: FunctionComponent<Props> = props => {
const sections = useMemo(() => {
return settingsSections({ device: AppType.Mobile, settings: props.settings });
}, [props.settings]);
const styles = props.styles.styleSheet;
const itemHeight = styles.sidebarButton.height;
const onRenderButton = ({ item }: { item: SettingMetadataSection }) => {
const section = item;
const selected = props.selectedSectionName === section.name;
const icon = Setting.sectionNameToIcon(section.name, AppType.Mobile);
const label = Setting.sectionNameToLabel(section.name);
const shortDescription = Setting.sectionMetadataToSummary(section);
return (
<Pressable
key={section.name}
role='tab'
aria-selected={selected}
onPress={() => props.openSection(section.name)}
style={selected ? styles.selectedSidebarButton : styles.sidebarButton}
>
<Icon
name={icon}
accessibilityLabel={null}
style={styles.sidebarIcon}
/>
<View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<Text
style={selected ? styles.sidebarSelectedButtonText : styles.sidebarButtonMainText}
>
{label}
</Text>
<Text
style={styles.sidebarButtonDescriptionText}
numberOfLines={1}
ellipsizeMode='tail'
>
{shortDescription ?? ''}
</Text>
</View>
</Pressable>
);
};
const [flatListRef, setFlatListRef] = useState<FlatList|null>(null);
useEffect(() => {
if (flatListRef && props.selectedSectionName) {
let selectedIndex = 0;
for (const section of sections) {
if (section.name === props.selectedSectionName) {
break;
}
selectedIndex ++;
}
flatListRef.scrollToIndex({
index: selectedIndex,
viewPosition: 0.5,
});
}
}, [props.selectedSectionName, flatListRef, sections]);
const containerStyle: ViewStyle = useMemo(() => ({
width: props.width,
maxWidth: props.width,
minWidth: props.width,
flex: 1,
}), [props.width]);
return (
<View style={containerStyle}>
<FlatList
role='tablist'
ref={setFlatListRef}
data={sections}
renderItem={onRenderButton}
keyExtractor={item => item.name}
getItemLayout={(_data, index) => ({
length: itemHeight, offset: itemHeight * index, index,
})}
/>
</View>
);
};
export default SectionSelector;

View File

@@ -0,0 +1,156 @@
import * as React from 'react';
import { UpdateSettingValueCallback } from './types';
import { View, Text, TextInput } from 'react-native';
import Setting from '@joplin/lib/models/Setting';
import Dropdown from '../../Dropdown';
import { ConfigScreenStyles } from './configScreenStyles';
import Slider from '@react-native-community/slider';
import SettingsToggle from './SettingsToggle';
import FileSystemPathSelector from './FileSystemPathSelector';
import shim from '@joplin/lib/shim';
const { themeStyle } = require('../../global-style.js');
interface Props {
settingId: string;
// The value associated with the given settings key
value: any;
styles: ConfigScreenStyles;
themeId: number;
updateSettingValue: UpdateSettingValueCallback;
}
const SettingComponent: React.FunctionComponent<Props> = props => {
const themeId = props.themeId;
const theme = themeStyle(themeId);
const output: any = null;
const md = Setting.settingMetadata(props.settingId);
const settingDescription = md.description ? md.description() : '';
const styleSheet = props.styles.styleSheet;
const descriptionComp = !settingDescription ? null : <Text style={styleSheet.settingDescriptionText}>{settingDescription}</Text>;
const containerStyle = props.styles.getContainerStyle(!!settingDescription);
if (md.isEnum) {
const value = props.value.toString();
const items = Setting.enumOptionsToValueLabels(md.options(), md.optionsOrder ? md.optionsOrder() : []);
return (
<View key={props.settingId} style={{ flexDirection: 'column', borderBottomWidth: 1, borderBottomColor: theme.dividerColor }}>
<View style={containerStyle}>
<Text key="label" style={styleSheet.settingText}>
{md.label()}
</Text>
<Dropdown
key="control"
items={items as any}
selectedValue={value}
itemListStyle={{
backgroundColor: theme.backgroundColor,
}}
headerStyle={{
color: theme.color,
fontSize: theme.fontSize,
}}
itemStyle={{
color: theme.color,
fontSize: theme.fontSize,
}}
onValueChange={(itemValue: string) => {
void props.updateSettingValue(props.settingId, itemValue);
}}
/>
</View>
{descriptionComp}
</View>
);
} else if (md.type === Setting.TYPE_BOOL) {
return (
<SettingsToggle
settingId={props.settingId}
value={props.value}
themeId={props.themeId}
styles={props.styles}
label={md.label()}
updateSettingValue={props.updateSettingValue}
description={descriptionComp}
/>
);
} else if (md.type === Setting.TYPE_INT) {
const unitLabel = md.unitLabel ? md.unitLabel(props.value) : props.value;
const minimum = 'minimum' in md ? md.minimum : 0;
const maximum = 'maximum' in md ? md.maximum : 10;
// Note: Do NOT add the minimumTrackTintColor and maximumTrackTintColor props
// on the Slider as they are buggy and can crash the app on certain devices.
// https://github.com/laurent22/joplin/issues/2733
// https://github.com/react-native-community/react-native-slider/issues/161
return (
<View key={props.settingId} style={styleSheet.settingContainer}>
<Text key="label" style={styleSheet.settingText}>
{md.label()}
</Text>
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', flex: 1 }}>
<Text style={styleSheet.sliderUnits}>{unitLabel}</Text>
<Slider
key="control"
style={{ flex: 1 }}
step={md.step}
minimumValue={minimum}
maximumValue={maximum}
value={props.value}
onValueChange={newValue => void props.updateSettingValue(props.settingId, newValue)}
/>
</View>
</View>
);
} else if (md.type === Setting.TYPE_STRING) {
if (md.key === 'sync.2.path' && shim.fsDriver().isUsingAndroidSAF()) {
return (
<FileSystemPathSelector
styles={props.styles}
settingMetadata={md}
updateSettingValue={props.updateSettingValue}
/>
);
}
return (
<View key={props.settingId} style={{ flexDirection: 'column', borderBottomWidth: 1, borderBottomColor: theme.dividerColor }}>
<View key={props.settingId} style={containerStyle}>
<Text key="label" style={styleSheet.settingText}>
{md.label()}
</Text>
<TextInput
autoCorrect={false}
autoComplete="off"
selectionColor={theme.textSelectionColor}
keyboardAppearance={theme.settingKeyboardAppearance}
autoCapitalize="none"
key="control"
style={styleSheet.settingControl}
value={props.value}
onChangeText={(newValue: string) => void props.updateSettingValue(props.settingId, newValue)}
secureTextEntry={!!md.secure}
/>
</View>
{descriptionComp}
</View>
);
} else if (md.type === Setting.TYPE_BUTTON) {
// TODO: Not yet supported
} else if (Setting.value('env') === 'dev') {
throw new Error(`Unsupported setting type: ${md.type}`);
}
return output;
};
export default SettingComponent;

View File

@@ -6,25 +6,27 @@ import { ConfigScreenStyles } from './configScreenStyles';
interface Props {
title: string;
description: string;
description?: string;
clickHandler: ()=> void;
styles: ConfigScreenStyles;
disabled?: boolean;
statusComponent?: ReactNode;
}
const ConfigScreenButton: FunctionComponent<Props> = props => {
const SettingsButton: FunctionComponent<Props> = props => {
const styles = props.styles.styleSheet;
let descriptionComp = null;
if (props.description) {
descriptionComp = (
<View style={{ flex: 1, marginTop: 10 }}>
<Text style={props.styles.descriptionText}>{props.description}</Text>
<Text style={styles.descriptionText}>{props.description}</Text>
</View>
);
}
return (
<View style={props.styles.settingContainer}>
<View style={styles.settingContainer}>
<View style={{ flex: 1, flexDirection: 'column' }}>
<View style={{ flex: 1 }}>
<Button title={props.title} onPress={props.clickHandler} disabled={!!props.disabled} />
@@ -35,4 +37,4 @@ const ConfigScreenButton: FunctionComponent<Props> = props => {
</View>
);
};
export default ConfigScreenButton;
export default SettingsButton;

View File

@@ -0,0 +1,45 @@
import * as React from 'react';
import { FunctionComponent, ReactNode } from 'react';
import { View, Text, Switch } from 'react-native';
import { UpdateSettingValueCallback } from './types';
import { themeStyle } from '@joplin/lib/theme';
import { ConfigScreenStyles } from './configScreenStyles';
interface Props {
settingId: string;
value: any;
themeId: number;
styles: ConfigScreenStyles;
label: string;
updateSettingValue: UpdateSettingValueCallback;
description?: ReactNode;
}
const SettingsToggle: FunctionComponent<Props> = props => {
const theme = themeStyle(props.themeId);
const styleSheet = props.styles.styleSheet;
return (
<View>
<View style={props.styles.getContainerStyle(false)}>
<Text key="label" style={styleSheet.switchSettingText}>
{props.label}
</Text>
<Switch
key="control"
style={styleSheet.switchSettingControl}
trackColor={{ false: theme.dividerColor }}
value={props.value}
onValueChange={(value: boolean) => void props.updateSettingValue(props.settingId, value)}
/>
</View>
{props.description}
</View>
);
};
export default SettingsToggle;

View File

@@ -1,13 +1,16 @@
import { TextStyle, ViewStyle, StyleSheet } from 'react-native';
const { themeStyle } = require('../../global-style.js');
export interface ConfigScreenStyles {
type SidebarButtonStyle = ViewStyle & { height: number };
export interface ConfigScreenStyleSheet {
body: ViewStyle;
settingContainer: ViewStyle;
settingContainerNoBottomBorder: ViewStyle;
headerWrapperStyle: ViewStyle;
headerTextStyle: TextStyle;
settingText: TextStyle;
linkText: TextStyle;
descriptionText: TextStyle;
@@ -22,9 +25,24 @@ export interface ConfigScreenStyles {
switchSettingContainer: ViewStyle;
switchSettingControl: TextStyle;
sidebarButton: SidebarButtonStyle;
sidebarIcon: TextStyle;
selectedSidebarButton: SidebarButtonStyle;
sidebarButtonMainText: TextStyle;
sidebarSelectedButtonText: TextStyle;
sidebarButtonDescriptionText: TextStyle;
settingControl: TextStyle;
}
export interface ConfigScreenStyles {
styleSheet: ConfigScreenStyleSheet;
selectedSectionButtonColor: string;
keyboardAppearance: 'default'|'light'|'dark';
getContainerStyle(hasDescription: boolean): ViewStyle;
}
const configScreenStyles = (themeId: number): ConfigScreenStyles => {
const theme = themeStyle(themeId);
@@ -54,7 +72,31 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => {
borderBottomColor: theme.dividerColor,
};
const styles: ConfigScreenStyles = {
const sidebarButtonHeight = theme.fontSize * 4 + 5;
const sidebarButton: SidebarButtonStyle = {
height: sidebarButtonHeight,
flex: 1,
flexDirection: 'row',
alignItems: 'center',
paddingEnd: theme.marginRight,
};
const sidebarButtonMainText: TextStyle = {
color: theme.color,
fontSize: theme.fontSize,
};
const fadedOpacity = 0.75;
const sidebarButtonDescriptionText: TextStyle = {
...sidebarButtonMainText,
fontSize: theme.fontSizeSmaller,
color: theme.color,
opacity: fadedOpacity,
paddingTop: 3,
};
const styles: ConfigScreenStyleSheet = {
body: {
flex: 1,
justifyContent: 'flex-start',
@@ -119,6 +161,8 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => {
justifyContent: 'space-between',
},
headerTextStyle: theme.headerStyle,
headerWrapperStyle: {
...settingContainerStyle,
...theme.headerWrapperStyle,
@@ -129,9 +173,40 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => {
color: undefined,
flex: 0,
},
sidebarButton,
selectedSidebarButton: {
...sidebarButton,
backgroundColor: theme.selectedColor,
},
sidebarButtonMainText: sidebarButtonMainText,
sidebarIcon: {
...sidebarButtonMainText,
textAlign: 'center',
fontSize: 18,
width: sidebarButtonHeight * 0.8,
opacity: fadedOpacity,
},
sidebarSelectedButtonText: {
...sidebarButtonMainText,
fontWeight: 'bold',
},
sidebarButtonDescriptionText,
};
return StyleSheet.create(styles);
const styleSheet = StyleSheet.create(styles);
return {
styleSheet,
selectedSectionButtonColor: theme.selectedColor,
keyboardAppearance: theme.keyboardAppearance,
getContainerStyle: (hasDescription) => {
return !hasDescription ? styleSheet.settingContainer : styleSheet.settingContainerNoBottomBorder;
},
};
};
export default configScreenStyles;

View File

@@ -0,0 +1,10 @@
import { ReactElement } from 'react';
export interface CustomSettingSection {
component: ReactElement;
icon: string;
title: string;
keywords: string[];
}
export type UpdateSettingValueCallback = (key: string, value: any)=> Promise<void>;

View File

@@ -32,7 +32,7 @@ const { Checkbox } = require('../checkbox.js');
import { _, currentLocale } from '@joplin/lib/locale';
import { reg } from '@joplin/lib/registry';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const { themeStyle, editorFont } = require('../global-style.js');
const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
@@ -61,6 +61,9 @@ const emptyArray: any[] = [];
const logger = Logger.create('screens/Note');
class NoteScreenComponent extends BaseScreenComponent {
// This isn't in this.state because we don't want changing scroll to trigger
// a re-render.
private lastBodyScroll: number|undefined = undefined;
public static navigationOptions(): any {
return { header: null };
@@ -81,7 +84,6 @@ class NoteScreenComponent extends BaseScreenComponent {
fromShare: false,
showCamera: false,
showImageEditor: false,
loadImageEditorData: null,
imageEditorResource: null,
noteResources: {},
@@ -747,7 +749,11 @@ class NoteScreenComponent extends BaseScreenComponent {
if (this.useEditorBeta()) {
// The beta editor needs to be explicitly informed of changes
// to the note's body
this.editorRef.current.insertText(newText);
if (this.editorRef.current) {
this.editorRef.current.insertText(newText);
} else {
logger.error(`Tried to attach resource ${resource.id} to the note when the editor is not visible!`);
}
}
} else {
newNote.body += `\n${resourceTag}`;
@@ -812,31 +818,34 @@ class NoteScreenComponent extends BaseScreenComponent {
}, 'image');
}
private drawPicture_onPress = async () => {
// Create a new empty drawing and attach it now.
const resource = await this.attachNewDrawing('');
await this.editDrawing(resource);
};
private async updateDrawing(svgData: string) {
let resource: ResourceEntity|null = this.state.imageEditorResource;
if (!resource) {
throw new Error('No resource is loaded in the editor');
resource = await this.attachNewDrawing(svgData);
// Set resouce and file path to allow
// 1. subsequent saves to update the resource
// 2. the editor to load from the resource's filepath (can happen
// if the webview is reloaded).
this.setState({
imageEditorResourceFilepath: Resource.fullPath(resource),
imageEditorResource: resource,
});
} else {
logger.info('Saving drawing to resource', resource.id);
const tempFilePath = join(Setting.value('tempDir'), uuid.createNano());
await shim.fsDriver().writeFile(tempFilePath, svgData, 'utf8');
resource = await Resource.updateResourceBlobContent(
resource.id,
tempFilePath,
);
await shim.fsDriver().remove(tempFilePath);
await this.refreshResource(resource);
}
logger.info('Saving drawing to resource', resource.id);
const tempFilePath = join(Setting.value('tempDir'), uuid.createNano());
await shim.fsDriver().writeFile(tempFilePath, svgData, 'utf8');
resource = await Resource.updateResourceBlobContent(
resource.id,
tempFilePath,
);
await shim.fsDriver().remove(tempFilePath);
await this.refreshResource(resource);
}
private onSaveDrawing = async (svgData: string) => {
@@ -847,13 +856,28 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ showImageEditor: false });
};
private drawPicture_onPress = async () => {
if (this.state.mode === 'edit') {
// Create a new empty drawing and attach it now, before the image editor is opened.
// With the present structure of Note.tsx, the we can't use this.editorRef while
// the image editor is open, and thus can't attach drawings at the cursor locaiton.
const resource = await this.attachNewDrawing('');
await this.editDrawing(resource);
} else {
logger.info('Showing image editor...');
this.setState({
showImageEditor: true,
imageEditorResourceFilepath: null,
imageEditorResource: null,
});
}
};
private async editDrawing(item: BaseItem) {
const filePath = Resource.fullPath(item);
this.setState({
showImageEditor: true,
loadImageEditorData: async () => {
return await shim.fsDriver().readFile(filePath);
},
imageEditorResourceFilepath: filePath,
imageEditorResource: item,
});
}
@@ -1260,6 +1284,10 @@ class NoteScreenComponent extends BaseScreenComponent {
}, 5);
}
private onBodyViewerScroll = (scrollTop: number) => {
this.lastBodyScroll = scrollTop;
};
public onBodyViewerCheckboxChange(newBody: string) {
void this.saveOneProperty('body', newBody);
}
@@ -1302,7 +1330,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return <CameraView themeId={this.props.themeId} style={{ flex: 1 }} onPhoto={this.cameraView_onPhoto} onCancel={this.cameraView_onCancel} />;
} else if (this.state.showImageEditor) {
return <ImageEditor
loadInitialSVGData={this.state.loadImageEditorData}
resourceFilename={this.state.imageEditorResourceFilepath}
themeId={this.props.themeId}
onSave={this.onSaveDrawing}
onExit={this.onCloseDrawing}
@@ -1334,6 +1362,8 @@ class NoteScreenComponent extends BaseScreenComponent {
onMarkForDownload={this.onMarkForDownload}
onRequestEditResource={this.onEditResource}
onLoadEnd={this.onBodyViewerLoadEnd}
onScroll={this.onBodyViewerScroll}
initialScroll={this.lastBodyScroll}
/>
);
} else {
@@ -1416,7 +1446,7 @@ class NoteScreenComponent extends BaseScreenComponent {
if (this.state.mode === 'edit') return null;
return <ActionButton mainButton={editButton} />;
return <ActionButton mainButton={editButton} dispatch={this.props.dispatch} />;
};
// Save button is not really needed anymore with the improved save logic

View File

@@ -13,9 +13,10 @@ import { _ } from '@joplin/lib/locale';
import ActionButton from '../ActionButton';
const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const { BackButtonService } = require('../../services/back-button.js');
import { AppState } from '../../utils/types';
const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids.js');
class NotesScreenComponent extends BaseScreenComponent<any> {
@@ -108,7 +109,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
}
public async componentDidUpdate(prevProps: any) {
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType) {
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType || prevProps.uncompletedTodosOnTop !== this.props.uncompletedTodosOnTop || prevProps.showCompletedTodos !== this.props.showCompletedTodos) {
await this.refreshNotes(this.props);
}
}
@@ -223,17 +224,32 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
let buttonFolderId = this.props.selectedFolderId !== Folder.conflictFolderId() ? this.props.selectedFolderId : null;
if (!buttonFolderId) buttonFolderId = this.props.activeFolderId;
const addFolderNoteButtons = !!buttonFolderId;
const isAllNotes =
this.props.notesParentType === 'SmartFilter'
&& this.props.selectedSmartFilterId === ALL_NOTES_FILTER_ID;
// Usually, when showing all notes, activeFolderId/selectedFolderId is set to the last
// active folder.
// If the app starts showing all notes, activeFolderId/selectedFolderId are
// empty or null. As such, we need a special case to show the buttons:
const addFolderNoteButtons = !!buttonFolderId || isAllNotes;
const thisComp = this;
const makeActionButtonComp = () => {
const getTargetFolderId = async () => {
if (!buttonFolderId && isAllNotes) {
return (await Folder.defaultFolder()).id;
}
return buttonFolderId;
};
if (addFolderNoteButtons && this.props.folders.length > 0) {
const buttons = [];
buttons.push({
label: _('New to-do'),
onPress: () => {
onPress: async () => {
const folderId = await getTargetFolderId();
const isTodo = true;
void this.newNoteNavigate(buttonFolderId, isTodo);
void this.newNoteNavigate(folderId, isTodo);
},
color: '#9b59b6',
icon: 'checkbox-outline',
@@ -241,14 +257,15 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
buttons.push({
label: _('New note'),
onPress: () => {
onPress: async () => {
const folderId = await getTargetFolderId();
const isTodo = false;
void this.newNoteNavigate(buttonFolderId, isTodo);
void this.newNoteNavigate(folderId, isTodo);
},
color: '#9b59b6',
icon: 'document',
});
return <ActionButton buttons={buttons}/>;
return <ActionButton buttons={buttons} dispatch={this.props.dispatch}/>;
}
return null;
};

View File

@@ -4,7 +4,7 @@ const { View, Button, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView
const { connect } = require('react-redux');
const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const DialogBox = require('react-native-dialogbox').default;
const { dialogs } = require('../../utils/dialogs.js');
const Shared = require('@joplin/lib/components/shared/dropbox-login-shared');

View File

@@ -5,7 +5,7 @@ const { connect } = require('react-redux');
const Folder = require('@joplin/lib/models/Folder').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const { ScreenHeader } = require('../ScreenHeader');
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const { dialogs } = require('../../utils/dialogs.js');
const { _ } = require('@joplin/lib/locale');
const { default: FolderPicker } = require('../FolderPicker');

View File

@@ -7,7 +7,7 @@ const { connect } = require('react-redux');
const { ScreenHeader } = require('../ScreenHeader');
const { reg } = require('@joplin/lib/registry.js');
const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const parseUri = require('@joplin/lib/parseUri');
const { themeStyle } = require('../global-style.js');
const shim = require('@joplin/lib/shim').default;

View File

@@ -7,7 +7,7 @@ const Icon = require('react-native-vector-icons/Ionicons').default;
import { _ } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
const { NoteItem } = require('../note-item.js');
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const { themeStyle } = require('../global-style.js');
const DialogBox = require('react-native-dialogbox').default;
import SearchEngineUtils from '@joplin/lib/services/searchengine/SearchEngineUtils';
@@ -98,7 +98,7 @@ class SearchScreenComponent extends BaseScreenComponent {
if (query) {
if (this.props.settings['db.ftsEnabled']) {
notes = await SearchEngineUtils.notesForQuery(query, true);
notes = await SearchEngineUtils.notesForQuery(query, true, { appendWildCards: true });
} else {
const p = query.split(' ');
const temp = [];

View File

@@ -6,7 +6,7 @@ const { connect } = require('react-redux');
const { ScreenHeader } = require('../ScreenHeader');
const ReportService = require('@joplin/lib/services/ReportService').default;
const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
const { themeStyle } = require('../global-style.js');
class StatusScreenComponent extends BaseScreenComponent {

View File

@@ -6,7 +6,7 @@ const Tag = require('@joplin/lib/models/Tag').default;
const { themeStyle } = require('../global-style.js');
const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js');
const { BaseScreenComponent } = require('../base-screen');
class TagsScreenComponent extends BaseScreenComponent {
static navigationOptions() {

View File

@@ -1,17 +1,13 @@
const gulp = require('gulp');
const utils = require('@joplin/tools/gulp/utils');
import { buildInjectedJS, watchInjectedJS } from './tools/buildInjectedJs';
import gulpTasks from './tools/buildInjectedJs/gulpTasks';
const tasks = {
encodeAssets: {
fn: require('./tools/encodeAssets'),
},
buildInjectedJs: {
fn: buildInjectedJS,
},
watchInjectedJs: {
fn: watchInjectedJS,
},
...gulpTasks,
podInstall: {
fn: require('./tools/podInstall'),
},
@@ -19,6 +15,22 @@ const tasks = {
utils.registerGulpTasks(gulp, tasks);
gulp.task('buildInjectedJs', gulp.series(
'beforeBundle',
'buildCodeMirrorEditor',
'buildJsDrawEditor',
'copyWebviewLib',
));
gulp.task('watchInjectedJs', gulp.series(
'beforeBundle',
'copyWebviewLib',
gulp.parallel(
'watchCodeMirrorEditor',
'watchJsDrawEditor',
),
));
gulp.task('build', gulp.series(
'buildInjectedJs',
'encodeAssets',

View File

@@ -381,10 +381,16 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Joplin/Pods-Joplin-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -517,13 +523,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 101;
CURRENT_PROJECT_VERSION = 106;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.13.4;
MARKETING_VERSION = 12.13.9;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -546,12 +552,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 101;
CURRENT_PROJECT_VERSION = 106;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.13.4;
MARKETING_VERSION = 12.13.9;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -698,14 +704,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 101;
CURRENT_PROJECT_VERSION = 106;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.13.4;
MARKETING_VERSION = 12.13.9;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -729,14 +735,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 101;
CURRENT_PROJECT_VERSION = 106;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.13.4;
MARKETING_VERSION = 12.13.9;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,5 +1,6 @@
PODS:
- boost (1.76.0)
- CocoaAsyncSocket (7.6.5)
- DoubleConversion (1.1.6)
- FBLazyVector (0.71.10)
- FBReactNativeSpec (0.71.10):
@@ -9,6 +10,67 @@ PODS:
- React-Core (= 0.71.10)
- React-jsi (= 0.71.10)
- ReactCommon/turbomodule/core (= 0.71.10)
- Flipper (0.125.0):
- Flipper-Folly (~> 2.6)
- Flipper-RSocket (~> 1.4)
- Flipper-Boost-iOSX (1.76.0.1.11)
- Flipper-DoubleConversion (3.2.0.1)
- Flipper-Fmt (7.1.7)
- Flipper-Folly (2.6.10):
- Flipper-Boost-iOSX
- Flipper-DoubleConversion
- Flipper-Fmt (= 7.1.7)
- Flipper-Glog
- libevent (~> 2.1.12)
- OpenSSL-Universal (= 1.1.1100)
- Flipper-Glog (0.5.0.5)
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.4.3):
- Flipper-Folly (~> 2.6)
- FlipperKit (0.125.0):
- FlipperKit/Core (= 0.125.0)
- FlipperKit/Core (0.125.0):
- Flipper (~> 0.125.0)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- SocketRocket (~> 0.6.0)
- FlipperKit/CppBridge (0.125.0):
- Flipper (~> 0.125.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.125.0):
- Flipper-Folly (~> 2.6)
- FlipperKit/FBDefines (0.125.0)
- FlipperKit/FKPortForwarding (0.125.0):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.125.0)
- FlipperKit/FlipperKitLayoutHelpers (0.125.0):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.125.0):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutHelpers
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutPlugin (0.125.0):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutHelpers
- FlipperKit/FlipperKitLayoutIOSDescriptors
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.125.0)
- FlipperKit/FlipperKitNetworkPlugin (0.125.0):
- FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.125.0):
- FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.125.0):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.125.0):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- fmt (6.2.1)
- glog (0.3.5)
- hermes-engine (0.71.10):
@@ -19,6 +81,7 @@ PODS:
- JoplinCommonShareExtension
- React
- libevent (2.1.12)
- OpenSSL-Universal (1.1.1100)
- RCT-Folly (2021.07.22.00):
- boost
- DoubleConversion
@@ -296,9 +359,9 @@ PODS:
- React-Core
- react-native-rsa-native (2.0.5):
- React
- react-native-saf-x (2.13.0):
- react-native-saf-x (2.13.3):
- React-Core
- react-native-safe-area-context (4.7.2):
- react-native-safe-area-context (4.7.4):
- React-Core
- react-native-slider (4.4.3):
- React-Core
@@ -398,7 +461,7 @@ PODS:
- React-Core
- RNCPushNotificationIOS (1.11.0):
- React-Core
- RNDateTimePicker (7.6.0):
- RNDateTimePicker (7.6.1):
- React-Core
- RNDeviceInfo (10.11.0):
- React-Core
@@ -416,7 +479,8 @@ PODS:
- React
- RNShare (9.4.1):
- React-Core
- RNVectorIcons (10.0.0):
- RNVectorIcons (10.0.1):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- RNZipArchive (6.1.0):
- React-Core
@@ -425,19 +489,44 @@ PODS:
- RNZipArchive/Core (6.1.0):
- React-Core
- SSZipArchive (~> 2.2)
- SocketRocket (0.6.0)
- SSZipArchive (2.4.3)
- Yoga (1.14.0)
- YogaKit (1.18.1):
- Yoga (~> 1.14)
DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- Flipper (= 0.125.0)
- Flipper-Boost-iOSX (= 1.76.0.1.11)
- Flipper-DoubleConversion (= 3.2.0.1)
- Flipper-Fmt (= 7.1.7)
- Flipper-Folly (= 2.6.10)
- Flipper-Glog (= 0.5.0.5)
- Flipper-PeerTalk (= 0.0.4)
- Flipper-RSocket (= 1.4.3)
- FlipperKit (= 0.125.0)
- FlipperKit/Core (= 0.125.0)
- FlipperKit/CppBridge (= 0.125.0)
- FlipperKit/FBCxxFollyDynamicConvert (= 0.125.0)
- FlipperKit/FBDefines (= 0.125.0)
- FlipperKit/FKPortForwarding (= 0.125.0)
- FlipperKit/FlipperKitHighlightOverlay (= 0.125.0)
- FlipperKit/FlipperKitLayoutPlugin (= 0.125.0)
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.125.0)
- FlipperKit/FlipperKitNetworkPlugin (= 0.125.0)
- FlipperKit/FlipperKitReactPlugin (= 0.125.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.125.0)
- FlipperKit/SKIOSNetworkPlugin (= 0.125.0)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- JoplinCommonShareExtension (from `ShareExtension`)
- JoplinRNShareExtension (from `ShareExtension`)
- libevent (~> 2.1.12)
- OpenSSL-Universal (= 1.1.1100)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
@@ -445,6 +534,7 @@ DEPENDENCIES:
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Codegen (from `build/generated/ios`)
- React-Core (from `../node_modules/react-native/`)
- React-Core/DevSupport (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
@@ -500,9 +590,22 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- CocoaAsyncSocket
- Flipper
- Flipper-Boost-iOSX
- Flipper-DoubleConversion
- Flipper-Fmt
- Flipper-Folly
- Flipper-Glog
- Flipper-PeerTalk
- Flipper-RSocket
- FlipperKit
- fmt
- libevent
- OpenSSL-Universal
- SocketRocket
- SSZipArchive
- YogaKit
EXTERNAL SOURCES:
boost:
@@ -640,15 +743,26 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: ddb55c55295ea51ed98aa7e2e08add2f826309d5
FBReactNativeSpec: 90fc1a90b4b7a171e0a7c20ea426c1bf6ce4399c
Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3
Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: d27603b55a48402501ad1928c05411dae9cd6b85
JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7
JoplinRNShareExtension: 485f3e6dad83b7b77f1572eabc249f869ee55c02
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: 8ef706f91e2b643cd32c26a57700b5f24fab0585
RCTTypeSafety: 5fbddd8eb9242b91ac0d901c01da3673f358b1b7
@@ -673,8 +787,8 @@ SPEC CHECKSUMS:
react-native-image-resizer: 681f7607418b97c084ba2d0999b153b103040d8a
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
react-native-saf-x: b758d1b38a96a96b8179a16e96ed966a507ca5c2
react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284
react-native-saf-x: 0f7531c9f8bdbb62bbd55ceb7433de7bb756cd73
react-native-safe-area-context: 2cd91d532de12acdb0a9cbc8d43ac72a8e4c897c
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
@@ -695,7 +809,7 @@ SPEC CHECKSUMS:
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
RNDateTimePicker: ccd988deb223cbb2e669e157ec576c2c6217128c
RNDateTimePicker: 8fb39263b721223e095248acaf6f406d5b7f6713
RNDeviceInfo: bf8a32acbcb875f568217285d1793b0e8588c974
RNExitApp: 00036cabe7bacbb413d276d5520bf74ba39afa6a
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
@@ -704,10 +818,12 @@ SPEC CHECKSUMS:
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
RNShare: 32e97adc8d8c97d4a26bcdd3c45516882184f8b6
RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9
RNVectorIcons: ace237de89f1574ef3c963ae9d5da3bd6fbeb02a
RNZipArchive: ef9451b849c45a29509bf44e65b788829ab07801
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
Yoga: e7ea9e590e27460d28911403b894722354d73479
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 3b2cace838120977b5b54871752c9dddf5a11cea

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