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

Compare commits

...

298 Commits

Author SHA1 Message Date
Laurent Cozic
362bc75c49 team => teams 2022-04-05 13:07:39 +01:00
Laurent Cozic
860838967c update price 2022-04-05 11:47:22 +01:00
Laurent Cozic
17896eda81 team feature 2022-04-04 18:18:23 +01:00
Laurent Cozic
3bcff9b602 Doc: Move info to Joplin Cloud FAQ 2022-04-04 15:23:37 +01:00
Bernard Tyers
3153d3a1b6 Doc: Improve debugging.md (#6342) 2022-03-29 19:25:27 +01:00
Joplin Bot
df8c265ee4 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-28 18:20:22 +00:00
Laurent Cozic
3725b14e04 Revert "Desktop: Fixes #5686: Fixed Tags Order (#6136)"
This reverts commit 07f128ae95.

Due to regression: https://github.com/laurent22/joplin/issues/6301
2022-03-28 17:40:51 +01:00
Retrove
a73d822998 Desktop, Cli: Fixes #6197: Fixed creation of empty notebooks when importing directory of files (#6274) 2022-03-28 17:13:13 +01:00
asrient
a62e1fba96 Desktop: Resolves #6100: Allow saving a Mermaid graph as a PNG or SVG via context menu (#6126) 2022-03-28 17:10:29 +01:00
Laurent Cozic
37d51c3b58 Plugins: Allow updating a resource via the data API 2022-03-28 16:35:41 +01:00
Laurent Cozic
d5dfecc19f Server: Automatically delete expired sessions 2022-03-28 15:51:44 +01:00
Laurent Cozic
8f8cc12d79 Server: Fixed removal of user deletion tasks 2022-03-28 15:51:43 +01:00
Laurent Cozic
8e1802409f Server: Cannot sort user deletions by email 2022-03-28 15:51:43 +01:00
reportxx
42b2f2146c Update Swedish translation (#6331)
* Update Swedish translation

Please merge to update the Swedish translation.

* Update sv.po
2022-03-27 14:19:10 +01:00
mrkaato0
2ba1563d92 Update fi_FI.po (#6323) 2022-03-27 14:19:02 +01:00
Milo Ivir
a679b21119 Update Crotian translation (#6319) 2022-03-27 14:18:53 +01:00
Xavi Ivars
dd10b6ac65 Update ca.po (#6308) 2022-03-23 19:54:22 +00:00
Laurent
32600df7ce Update pull_request_guidelines.md 2022-03-23 18:24:58 +00:00
Laurent
8a1cfabfc6 Doc: Allow plugins for GSoC 2022-03-23 18:17:43 +00:00
Joplin Bot
1b2046f2fa Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-20 00:37:39 +00:00
Laurent Cozic
a1d5168918 Doc: Updated PR guideline for GSoC 2022-03-18 11:44:49 +00:00
Bishoy
047c1fb1a5 Desktop: Fixes right click menu on Markdown Editor (#6132) 2022-03-18 11:07:59 +00:00
Andrew
e83d662555 Doc: Update docker-compose.server.yml (#6283) 2022-03-18 11:06:40 +00:00
Joplin Bot
5c3b2671bf Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-17 18:17:04 +00:00
Laurent
02a2dce605 Update pull_request_guidelines.md 2022-03-16 12:07:35 +00:00
Laurent
c4e158d0fb Doc: Slightly relaxed rule 5 of GSoC guidelines 2022-03-16 12:04:40 +00:00
Daniel Aleksandersen
fa8a1c2122 Desktop: Resolves #4155: Don’t unpin app from taskbar on update (#6271) 2022-03-15 10:06:00 +00:00
JackGruber
3f732939d0 All: Resolves #6266: Make search engine filter keywords case insensitive (#6267) 2022-03-15 10:03:56 +00:00
Ayush Srivastava
fb8886db4b Mobile: Color of Date-Time text changed to match theme (#6279) 2022-03-15 10:00:17 +00:00
Laurent Cozic
eb86e9c896 Doc: Fixed GSoC links 2022-03-14 11:02:03 +00:00
PackElend
85e3a44276 Doc: fixed link togeneral Summer of Code introduction (#6269) 2022-03-13 14:44:31 +00:00
Laurent Cozic
addcfb0129 All: Fixes #6261: Ensure that note revision markup type is set correctly 2022-03-12 16:24:07 +00:00
Laurent Cozic
f4ec73ab0e Desktop release v2.7.14 2022-03-12 15:59:09 +00:00
Laurent Cozic
19ba939f0f Doc: Added doc about desktop application styling 2022-03-12 13:38:52 +00:00
Laurent Cozic
780b58ada4 Doc: Remove option to create a plugin as first GSoC contribution 2022-03-12 10:33:03 +00:00
Joplin Bot
b3aed81bea Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-11 12:19:26 +00:00
Laurent Cozic
74dd2d1194 Doc: Fixed Twitter OpenGraph tags 2022-03-10 17:49:50 +00:00
Laurent Cozic
3c11445db0 Doc: Fixed news dates 2022-03-10 17:43:06 +00:00
Laurent Cozic
50890a7b2b Tools: Added tool to get the list of package licenses 2022-03-10 17:29:28 +00:00
Laurent
dc60da219a Update CONTRIBUTING.md 2022-03-09 14:56:55 +00:00
Laurent Cozic
bef93e1375 Doc: Update test unit documentation 2022-03-09 14:51:05 +00:00
Daeraxa
45e02a6b3f Doc: Update theme of index and a typo correction (#6245) 2022-03-08 22:52:33 +00:00
Laurent
0c5f64207f Update technical_spec.md 2022-03-08 22:50:21 +00:00
Laurent
8e88686bb1 Doc: Make it clear that test units are not optional for GSoC 2022-03-08 22:49:10 +00:00
Joplin Bot
9594508c73 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-08 14:38:05 +00:00
Laurent Cozic
de7d3b6b8a Doc: Update for GSoC 2022 2022-03-08 14:27:00 +00:00
Laurent Cozic
f5b6398d07 Doc: Add GSoC 2O22 annoucement 2022-03-08 14:27:00 +00:00
Joplin Bot
62a5cf520a Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-08 14:10:52 +00:00
Laurent Cozic
90bfffed0f Doc: Add GSoC 2O22 annoucement 2022-03-08 13:58:41 +00:00
Laurent Cozic
a5b0d594c3 Doc: Updated GSoC 2022 mentors 2022-03-07 12:02:00 +00:00
Laurent Cozic
1547256af4 Doc: Update GSoC 2022 themes 2022-03-07 11:44:36 +00:00
Shing Lyu
c3e7fcb471 Mobile: Fix: the camera button remains clickable after taking a photo bug (#6222) 2022-03-07 10:25:08 +00:00
Roman Musin
261302d5c4 Doc: Update BUILD.md (#6241)
Add a note that the path may not contain spaces
2022-03-07 10:23:06 +00:00
Laurent
38e65253ad Tools: Add fastlane structure for F-Droid support (#6224) 2022-03-07 10:22:27 +00:00
Laurent Cozic
eb8d0daa3d Fixed tests 2022-03-07 10:19:47 +00:00
Joplin Bot
5a6c03fbac Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-06 18:15:01 +00:00
Laurent Cozic
21f9dab908 Doc: add sponsor 2022-03-06 12:37:05 +00:00
Harris Arvanitis
6dba4730b0 Translation: Update el_GR.po (#6229) 2022-03-03 18:22:40 +00:00
Laurent Cozic
9599d4ef7e Doc: Fixed news 2022-03-03 14:09:03 +00:00
Laurent Cozic
252f08e828 Doc: Improved rendering of news items 2022-03-03 14:06:37 +00:00
Laurent Cozic
94dc216add Desktop: Fixes #6214: Undo and redo on note title did not work in some cases 2022-03-03 13:53:11 +00:00
Laurent Cozic
32de63fad3 Desktop: Fixes #5584: Clicking on folder button was no longer jumping to the right folder
Regression introduced by https://github.com/laurent22/joplin/issues/4697
2022-03-03 13:33:52 +00:00
Laurent Cozic
299a14755a All: Resolves #6209: Handle invalid revision patches 2022-03-03 13:20:29 +00:00
Laurent Cozic
053dbabf74 Server: Add support for sidebar in user pages 2022-03-03 11:33:46 +00:00
OmGole
07f128ae95 Desktop: Fixes #5686: Fixed Tags Order (#6136) 2022-03-02 18:11:14 +00:00
Shing Lyu
98f9ed641c Clipper: Add a button to open the newly clipped note (#6204) 2022-03-02 17:42:29 +00:00
Shing Lyu
d814fd6965 Mobile: Allow filtering tags in tag dialog (#6221) 2022-03-02 17:41:07 +00:00
Shunya Ueta
d44aade075 Doc: update pre-release docs (#6225) 2022-03-02 17:34:34 +00:00
Joplin Bot
4884c9ef87 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-03-01 18:14:23 +00:00
Daeraxa
3e518ff2c7 Doc: Replaced Privacy welcome note with website privacy md (#6208) 2022-03-01 15:07:29 +00:00
Alexander Hagen Huse
195c84ff59 Update translation [nb_NO] (#6220)
Update nb_NO.po
2022-03-01 14:51:06 +00:00
asahiocean
6881259501 Update ru_RU.po (#6215) 2022-03-01 14:50:43 +00:00
reportxx
6d7f6e29bd Update the Swedish translation (#6207) 2022-03-01 14:50:14 +00:00
Joplin Bot
dc991d4c0f Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-27 12:17:53 +00:00
Laurent Cozic
88ac664e37 Deskop, Cli: Fixes #6203: Note export could fail in some cases (regression) 2022-02-27 10:49:18 +00:00
Laurent Cozic
20784b0e99 Desktop: Resolves #5531: Prevent certain errors from stopping the revision service 2022-02-27 10:30:40 +00:00
Laurent Cozic
a325bf6dc6 All: Improve error message when revision metadata cannot be decoded, to improve debugging 2022-02-26 18:20:23 +00:00
Laurent Cozic
ff79745d28 Doc: Added missing hours on GSoC idea list 2022-02-26 10:03:06 +00:00
Laurent Cozic
82109a3132 Doc: Add mentors and complete expected outcomes and project sizes for GSoC 2022 ideas 2022-02-26 09:45:18 +00:00
Helmut K. C. Tessarek
4e901436cc All: Translation: Update da_DK.po (thanks ERYpTION) 2022-02-24 21:03:14 -05:00
Laurent Cozic
f0113c0673 Tools: Add Open Graph tags to website 2022-02-24 19:37:34 +00:00
Joplin Bot
0a1947a712 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-24 18:16:25 +00:00
Laurent Cozic
39aee65eee Doc: Add release 2.7 news item 2022-02-24 17:25:49 +00:00
Laurent Cozic
d37b820bd3 Desktop release v2.7.13 2022-02-24 17:11:14 +00:00
Laurent Cozic
06b95bb718 Update translations 2022-02-24 15:10:03 +00:00
Laurent Cozic
8e87e64dea Tools: Fixed changelog tool 2022-02-23 14:20:38 +00:00
Laurent Cozic
422a5bfa91 Server: Fixed sidebar menu selection 2022-02-23 14:03:02 +00:00
Laurent Cozic
bfe5ee8ba3 Server: Fixed user deletion schedule 2022-02-23 14:03:01 +00:00
Daeraxa
00e0f6b97c Docs: Updated GSoC to replace student refs with contributor (#6180) 2022-02-23 13:44:43 +00:00
Laurent Cozic
46490113a2 Tools: Make server build more configurable 2022-02-23 09:02:13 +00:00
Laurent Cozic
cc69cabf9b Doc: Fixed minor website issues and added Twitter link in mobile view 2022-02-22 19:08:23 +00:00
Laurent Cozic
da88ddb6bd Doc: tweak 2022-02-22 09:35:40 +00:00
Miucci
554fd73054 All: Translation: Update fi_FI.po (#6178) 2022-02-20 14:29:02 -05:00
Laurent
c48a0c6e79 Update CONTRIBUTING.md 2022-02-20 11:21:20 +00:00
Erik Thomsen
f8b7db40c7 Server: Use apt to install tini to enable multi-platform support (#6097) 2022-02-20 11:14:42 +00:00
Laurent
051eac09ec Update ideas.md 2022-02-18 12:23:23 +00:00
Joplin Bot
7584b203fc Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-17 18:19:23 +00:00
Laurent Cozic
440618ef71 Desktop: Fixed search marker background color in Markdown editor 2022-02-16 15:52:23 +00:00
Laurent
166d8da81b Doc: Add news item 2022-02-15 14:21:18 +00:00
Joplin Bot
6b7fd24f9e Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-14 18:12:54 +00:00
Laurent Cozic
41dd93bc24 iOS 12.7.1 2022-02-14 14:11:23 +00:00
Laurent Cozic
4906087649 Desktop release v2.7.12 2022-02-14 14:08:29 +00:00
Daeraxa
fe95926fa2 Docs: Removed unsupported installation methods and added link to community wiki page (#6158) 2022-02-14 12:32:21 +00:00
Daeraxa
a0fcd240b0 Docs: Add settings sync idea for GSoC 2022 (#6159) 2022-02-14 12:27:13 +00:00
Daeraxa
ae3a4a3027 Mobile: Fixes #5482: Prevent multiline note titles (#6144) 2022-02-13 15:01:43 +00:00
Joplin Bot
5252fdc8ce Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-12 18:15:36 +00:00
Laurent Cozic
fe787d1257 Chore: clean up 2022-02-12 17:46:32 +00:00
Laurent Cozic
a70f9b1a13 Desktop: Fixes #6129: Exported JEX notebook should not contain share metadata 2022-02-12 17:44:07 +00:00
Laurent Cozic
ed20604ad2 Android 2.7.2 2022-02-12 12:58:28 +00:00
Laurent Cozic
105a61c1ee Desktop release v2.7.11 2022-02-12 12:26:51 +00:00
Laurent Cozic
38fbaa9acf Update translations 2022-02-12 12:21:40 +00:00
Joplin Bot
0d0231e82c Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-12 00:31:27 +00:00
Laurent Cozic
b79270990b Tools: Fixed tests 2022-02-11 19:53:42 +00:00
Laurent Cozic
064891d097 Desktop: Resize custom icon down to 256px when it is too large 2022-02-11 19:24:37 +00:00
Laurent Cozic
9036bc4fd1 Android 2.7.1 2022-02-11 18:24:59 +00:00
Laurent Cozic
0f41a2d00a Desktop release v2.7.10 2022-02-11 11:21:21 +00:00
Laurent Cozic
3ee0c7f440 Desktop release v2.7.9 2022-02-11 11:19:59 +00:00
Laurent Cozic
89ada6432b Desktop: Fixes #6075: Login field was sometimes disabled on Sync Wizard dialog 2022-02-11 11:04:10 +00:00
Laurent Cozic
445d691103 Doc: Update readme intro 2022-02-11 10:38:45 +00:00
Laurent Cozic
de757026d4 All: Fixes #6092: Shared resource was not encrypted with correct encryption key 2022-02-11 10:38:45 +00:00
Helmut K. C. Tessarek
5e8ed8bc24 Desktop: Fixes #6108: Add "Other applications" import menu item (#6118) 2022-02-10 10:54:23 +00:00
Laurent Cozic
0a739e099d Doc: Fixed website links 2022-02-09 15:24:08 +00:00
Laurent Cozic
f3b1f07a75 Doc: Added GSoC 2022 documents 2022-02-09 15:14:57 +00:00
Laurent Cozic
1b91646d92 Doc: Added GSoC 2022 documents 2022-02-09 15:14:00 +00:00
Laurent Cozic
885f0e1557 Desktop: Improved custom icon selection 2022-02-07 17:23:20 +00:00
Peng Ding
2933b7eb2a All: Translation: Update zh_CN.po 2022-02-06 16:32:22 -05:00
Laurent
9f252ea673 Desktop: Add support for custom notebook icons (#6110) 2022-02-06 16:42:00 +00:00
Laurent Cozic
db497ee0a5 Tools: Fixed fork-uslug issue for mobile app 2022-02-06 16:40:28 +00:00
Laurent Cozic
31fcd0ed1d Server: Only use Stripe "customer.subscription.created" event to provision subscriptions 2022-02-05 18:32:45 +00:00
Joplin Bot
5d7d597147 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-02-05 18:12:49 +00:00
Laurent
6ebec0cbf8 Update sponsors.json 2022-02-05 14:39:10 +00:00
Laurent
f4b39633ea Delete style.min.css 2022-02-05 14:38:30 +00:00
Mr-Kanister
4c8e2fba7d All: Translation: Update de_DE.po (#6102) 2022-02-04 18:26:08 -05:00
Vincent Jo
288ae1b463 All: Add additional time format HH.mm (#6086) 2022-02-03 18:52:24 +00:00
Laurent Cozic
e3c9bcbec6 Server: Improve admin email UI 2022-02-03 11:25:13 +00:00
Laurent Cozic
49180fbf88 Remove printing of version numbers in Docker image 2022-02-03 11:25:12 +00:00
Helmut K. C. Tessarek
486ac6db7b Update translations 2022-02-02 18:42:45 -05:00
Laurent Cozic
b3fc8fbc93 Server v2.7.4 2022-02-02 19:24:50 +00:00
Laurent Cozic
980190ec09 Merge branch 'dev' into release-2.7 2022-02-02 19:22:49 +00:00
Laurent Cozic
cebf043284 Desktop release v2.7.8 2022-02-02 19:21:07 +00:00
Laurent Cozic
4c11bbf0da Server: Disable session expiration logic for now
As we do not know how many people have version 2.5+
2022-02-02 19:17:50 +00:00
Laurent Cozic
69170dd362 Server: Enabled task to automatically delete sessions every 6 hours 2022-02-01 17:57:01 +00:00
Laurent Cozic
1afcb27601 Server: Add task to automate deletion of disabled accounts 2022-02-01 17:55:14 +00:00
Laurent Cozic
68469bc1a5 Server: Temporarily save user info before deleting account 2022-02-01 15:39:25 +00:00
Laurent Cozic
a4e279270b Chore: Fixed server db types 2022-02-01 14:40:02 +00:00
Laurent Cozic
2f7ab7e92e Server: Set a timestamp when disabling a user
So that it can be used later on to decide if user data
should be automatically deleted or not.
2022-01-31 19:52:30 +00:00
Daeraxa
46202beb9b Doc: Add rich text editor limitation - lists within tables (#6072) 2022-01-30 17:16:37 -05:00
Laurent Cozic
dd705680f1 Doc: Update help 2022-01-29 19:37:27 +00:00
Laurent Cozic
00163f58d0 Server: Always display Help link even when not logged in 2022-01-29 19:31:48 +00:00
Laurent Cozic
df9c460363 Server: Make page wider only when displaying admin pages 2022-01-29 19:21:37 +00:00
Laurent Cozic
603c8338c0 Doc: Fixed news image width on mobile 2022-01-29 16:07:25 +00:00
Laurent Cozic
fca5875c21 All: Make heading 4, 5 and 6 styling more consistent 2022-01-29 15:52:32 +00:00
Laurent Cozic
2941ee0590 Doc: Fixed link 2022-01-29 12:44:49 +00:00
Dirag Biswas
5d71787835 Doc: Fixes #5947: Incorrect build instruction in documentation (#6068) 2022-01-28 18:37:31 +00:00
Caleb John
2b2070aabe All: Resolves #5808: Fixed sync scroll issue (#6059)
Remove patch for multimd, and update package instead
2022-01-28 10:36:02 +00:00
Laurent Cozic
bb464d8a59 Doc: Added document on how to write a technical spec 2022-01-27 15:13:24 +00:00
Arda Kılıçdağı
00a3014131 All: Translation: Update tr_TR.po (#6053) 2022-01-25 14:03:59 -05:00
Laurent Cozic
f51b0c6d06 Doc: Added GSoC idea "Desktop application integration testing" 2022-01-24 19:08:33 +00:00
Laurent Cozic
01825e6d3e Tools: Fixed tests 2022-01-24 19:08:32 +00:00
Aaron
b7bb5acd41 Server: Fixes #5397: Use multi-stage builds and smaller base image (#6048) 2022-01-24 18:50:01 +00:00
Kenichi Kobayashi
638905e1b8 Desktop: Fixes #6042: Scroll position is not remembered (regression) (#6043) 2022-01-24 09:55:46 +00:00
Laurent
e05c89aa11 Tools: Don't auto-close issues with "enhancement" label 2022-01-24 09:24:52 +00:00
Laurent Cozic
f5f7981dba Server: View sent emails from admin dashboard 2022-01-22 17:29:09 +00:00
qcol
23d9ba7bf1 All: Translation: Update pl_PL.po (#6046)
typo
2022-01-21 13:20:37 -05:00
Laurent Cozic
095db5d0d2 Doc: Added trademark info 2022-01-21 15:12:45 +00:00
Helmut K. C. Tessarek
5718001a33 All: Translation: Update da_DK.po (thanks ERYpTION) 2022-01-20 13:55:07 -05:00
Helmut K. C. Tessarek
91907aa5dd All: Update Mermaid 8.13.5 -> 8.13.9 and Katex dependencies (#6039) 2022-01-20 11:54:01 +00:00
Caleb John
d70dca3139 Desktop: Fixes #6035: Global search focuses text in notes so that edits overwrite highlighted text (#6040)
Co-authored-by: Kenichi Kobayashi <ken1kob@users.noreply.github.com>
2022-01-20 11:52:32 +00:00
Helmut K. C. Tessarek
630584c80e Doc: remove redundant and obsolete privacy section (from README.md)
The privacy document can be found:

- https://joplinapp.org/privacy/
- https://github.com/laurent22/joplin/blob/dev/readme/privacy.md
2022-01-19 15:01:55 -05:00
Joplin Bot
b152879086 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-01-19 12:16:54 +00:00
Laurent Cozic
2238ef5fd0 Desktop release v2.7.8 2022-01-19 09:05:15 +00:00
Laurent Cozic
6bb0318c2f Desktop: Disable plugin throttling for now 2022-01-19 09:03:36 +00:00
Laurent Cozic
c668bb0370 Revert "Desktop: Fixes #5850: Editor loses cursor focus when Ctrl+F search is closed (#5919)"
This reverts commit b98e64c881.

Ref: https://github.com/laurent22/joplin/issues/6035
2022-01-19 09:03:35 +00:00
Helmut K. C. Tessarek
360293949c Doc: fixed a wrong term (on-time -> app) 2022-01-18 13:17:13 -05:00
Joplin Bot
8289ce3749 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-01-18 18:14:50 +00:00
Laurent Cozic
1bb4c38699 Desktop release v2.7.7 2022-01-18 11:12:47 +00:00
Mayank Bondre
bc977bf43f Desktop: Fixes #5803: Fixes alt text not appearing in html (#6017) 2022-01-18 11:11:21 +00:00
HoraceYoung
c98edf89fa Update zh_CN.po (#6032) 2022-01-18 11:10:20 +00:00
Laurent Cozic
c6b67126fb Desktop: Disable plugin throttling mechanism for now 2022-01-18 11:07:19 +00:00
Laurent Cozic
c1aa57a72c Doc: Update Joplin Cloud cancellation policy 2022-01-18 11:05:37 +00:00
Joplin Bot
fdba6c076e Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-01-17 18:17:39 +00:00
Laurent Cozic
f8d0bb472e Desktop release v2.7.6 2022-01-17 16:24:01 +00:00
Laurent Cozic
3a18da49f3 Tools: Fix Windows build and improve CI error handling 2022-01-17 14:09:02 +00:00
Kenichi Kobayashi
5143a81749 Desktop: Fixes #5981: Scroll positions are not preserved when layout changes (#6021) 2022-01-17 10:30:09 +00:00
Laurent
f283970186 Update privacy.md 2022-01-16 17:06:25 +00:00
Laurent Cozic
abf50565cb typo 2022-01-16 17:05:13 +00:00
Laurent Cozic
287057fea6 Doc: Also mention plugin repository request in privacy policy 2022-01-16 17:03:50 +00:00
Laurent Cozic
8ac6017c02 Doc: Mention spellchecker dictionary request in privacy policy 2022-01-16 16:57:31 +00:00
Laurent Cozic
8cdb73bd65 Doc: Mention that E2EE works with the notebook sharing feature 2022-01-16 16:57:31 +00:00
Laurent Cozic
35e6aaceb7 Merge branch 'dev' into release-2.7 2022-01-16 13:09:46 +00:00
Laurent Cozic
52287c30bf Desktop release v2.7.5 2022-01-16 13:09:02 +00:00
Laurent Cozic
a9daa4f772 Desktop release v2.7.4 2022-01-16 12:37:11 +00:00
Laurent Cozic
316e31f937 Desktop release v2.7.4 2022-01-16 12:36:25 +00:00
Laurent Cozic
94f4ba3053 Merge branch 'dev' into release-2.7 2022-01-16 12:36:07 +00:00
Kenichi Kobayashi
8825133675 Desktop: Fixes #5968: Default sort order lost on exit (#6022) 2022-01-16 11:49:16 +00:00
Benjamin Weis
2fd724911d All: Translation: Update de_DE.po (#6019) 2022-01-15 18:59:33 -05:00
Laurent Cozic
12704bfb4d Desktop release v2.7.3 2022-01-15 19:27:01 +00:00
Laurent Cozic
070fd0434b Desktop release v2.7.3 2022-01-15 19:22:36 +00:00
Joplin Bot
a105306dc7 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-01-15 18:13:48 +00:00
Laurent Cozic
70035f49d1 Desktop release v2.7.2 2022-01-15 17:22:50 +00:00
Laurent Cozic
066dcbc6e4 Merge branch 'dev' into release-2.7 2022-01-15 17:22:34 +00:00
Laurent Cozic
597454d39d Desktop release v2.7.1 2022-01-15 17:20:38 +00:00
Laurent Cozic
6bc70ed8dc Desktop: Fixed order of editor search buttons 2022-01-15 17:18:52 +00:00
Laurent Cozic
cfce397df8 Update translations 2022-01-15 17:16:16 +00:00
Laurent Cozic
0274fd1790 Tools: Ignore some files when building translations 2022-01-15 17:16:04 +00:00
JCardoen
47da47e12d Translation of nl_BE.po (#6013) 2022-01-15 17:06:02 +00:00
asrient
7088be01c8 Desktop: Fixes #5953: Note list buttons do not reappear after changing app layout (#5994) 2022-01-15 17:05:31 +00:00
Stephan Dev
6a1f3ace67 Doc: Clarify where to go to enable markdown plugins (#6007) 2022-01-15 17:03:11 +00:00
Krishna Kumar
4ae3bec7e4 Desktop: Fix white space in the bottom of Add Tag Prompt dialog (#5998) 2022-01-15 17:00:42 +00:00
Felipe Kinoshita
970c1fb423 Desktop: Prevent Desktop Environments to launch a new window (#5984) 2022-01-15 16:59:50 +00:00
Laurent Cozic
810018b41f Desktop: Security: Fixes #6004: Prevent XSS in Goto Anything 2022-01-15 16:53:24 +00:00
Laurent Cozic
e0bfa0dbe6 Doc: Add sponsor 2022-01-15 16:15:25 +00:00
Laurent
ca7e68ba4b Server: Move admin pages under /admin (#6006) 2022-01-14 10:14:43 +00:00
Laurent
3fcdeb08d9 Server: Simplify Docker image (#6010)
- Removed complicated optimisation steps that didn't seem to optimise anything
- Delete Yarn cache after installation

After this, it should be back to the previous pre-Yarn size.
2022-01-13 17:39:58 +00:00
Roman Musin
ebaddfa4a8 Doc: GSoC 22 - email plugin idea (#6009) 2022-01-13 10:47:19 +00:00
Hieu-Thi Luong
2ab049305e Doc: Use correct flag for Vietnam (#5991)
Use correct Vietnam flag when build translation
2022-01-12 12:21:25 -05:00
Laurent Cozic
98bfb65ead Server: Improved string localization in views 2022-01-11 15:11:37 +00:00
Laurent Cozic
09cbe3cb7c Server: Put admin pages under /admin 2022-01-11 15:09:53 +00:00
Laurent Cozic
fd322edaab Server: Improved env variable validation and support true-false as boolean values 2022-01-11 15:02:53 +00:00
Laurent
09ed92bc26 Tools: Run stale action once a day at 16:00 2022-01-10 15:20:50 +00:00
Laurent Cozic
fb6069de6d Doc: Add Twitter link in header and fixed donate links 2022-01-10 12:56:25 +00:00
Laurent
39056ae1aa Added idea: 11. Improve plugin search and discoverability 2022-01-09 16:15:38 +00:00
Laurent Cozic
d031a04a2c Desktop: Adjusted styling to make it more consistent across app
There was two types of styling - the new one done using SCSS, which had
a 16px font, and the old one done js-in-css, which had a 13px font. Both
now have a 13px font. Also fixed margins on certain config screens.
2022-01-09 15:42:27 +00:00
Laurent Cozic
ed0f0fae01 Server: Set NODE_ENV to "production" in Docker image 2022-01-09 15:36:41 +00:00
Laurent Cozic
c185f00006 Server: Call server start command directly, without going through npm 2022-01-09 15:36:21 +00:00
Laurent Cozic
3117133be2 Desktop: Focus notebook title when opening Notebook dialog 2022-01-09 15:30:21 +00:00
Laurent Cozic
b92c650f5d Server: Fixes #5958: Fixed sharing notebook with a user that does not have E2EE enabled 2022-01-09 15:20:23 +00:00
Laurent Cozic
9dbf5e02eb Server: Remove uneeded CSS file 2022-01-09 12:04:54 +00:00
reportxx
e1016b8af4 Update Swedish translation (#5985) 2022-01-09 11:35:40 +00:00
Thibault Jan Beyer
3ba4a1de72 Doc: Update editorCommandDeclarations link (#5976) 2022-01-09 11:32:53 +00:00
Kenichi Kobayashi
a683f12622 Desktop: Clickable tags in Tag Bar (#5956) 2022-01-09 11:32:21 +00:00
Nicholas Hobbs
89184a3f9f Doc: Add libvips dependency for Apple Silicon build instructions (#5966) 2022-01-09 11:28:13 +00:00
Kenichi Kobayashi
24dbede6c1 Desktop: Fixes #5890: Scroll jump when checkbox is toggled in Viewer (#5941) 2022-01-09 11:26:40 +00:00
Kenichi Kobayashi
70e623e741 Desktop: Fixes #5918: Scroll jumps when images are rendered in Markdown Editor (#5929) 2022-01-09 11:26:03 +00:00
Jonathan Heard
5c77317735 All: Show login prompt for OneDrive (#5933) 2022-01-09 11:25:24 +00:00
Shing Lyu
9684b38f7e Desktop: Fixes #5875: Show error on sync if S3 region is not set (#5923) 2022-01-09 11:24:24 +00:00
Helmut K. C. Tessarek
3dfe43204d Desktop: Fix wording "Check for updates" in settings (#5832) 2022-01-09 11:23:24 +00:00
Caleb John
16148b2255 Desktop: Fixes #5808: Scrolling was out of sync when a Multi Markdown Table was being used (#5815) 2022-01-09 11:22:22 +00:00
Laurent Cozic
df14ffdee2 Server: Resolves #5222: Fixed handling of mailer security settings, and changed env variable name MAILER_SECURE => MAILER_SECURITY, and default port 587 => 465 (Breaking change) 2022-01-09 11:14:16 +00:00
Laurent
f6ed5eb064 Update close-stale-issues.yml 2022-01-08 21:51:28 +00:00
Zander Hill
a14115bfd7 Generator: When publishing a plugin, only include files in the "publish" directory (#5971) 2022-01-08 19:18:49 +00:00
Krishna Kumar
dd68d6cf2c Desktop: Fixes #5916: Fixed search icon when note list is resized (#5974) 2022-01-08 19:12:18 +00:00
Laurent Cozic
7d3555d62c Doc: Fix email address 2022-01-08 17:19:21 +00:00
Laurent Cozic
af472528a2 Doc: Add CLA document for Joplin Server 2022-01-08 17:18:30 +00:00
Ilia Kondrashov
5c8b0ec1cb Doc: Remove unsupported AWS IAM policy statement (#5982) 2022-01-08 17:02:45 +00:00
Laurent
5e20e890d8 Tools: Use GitHub Actions to close stale issues (#5979)
Stale bot appears to be broken and discontinued.
2022-01-08 12:50:37 +00:00
Laurent Cozic
50dc656f65 Doc: Move faq entry to correct document 2022-01-07 18:12:47 +00:00
Laurent Cozic
b36cf46a06 Plugin Repo: Improved error message when plugin "publish" directory is missing 2022-01-07 18:12:47 +00:00
Roman Musin
1781334374 Doc: A few more GSoC ideas (#5959) 2022-01-06 16:42:13 -05:00
Laurent Cozic
71140a638f Minor clipper tweaks 2022-01-06 15:46:28 +00:00
Laurent Cozic
6ba0fce237 Doc: Update Joplin Cloud FAQ 2022-01-06 12:22:23 +00:00
Laurent Cozic
c033a343c1 Doc: Update Joplin Server license 2022-01-06 12:00:54 +00:00
Daeraxa
898c96a566 Tools: Fix desktop app build (#5960) 2022-01-06 10:54:10 +00:00
CandleCandle
b83fa133b2 Doc: Default S3 permissions require 'GetObject' (#5957) 2022-01-05 16:46:41 +00:00
reportxx
ec7fec8b59 All: Translation: Update sv.po (#5950) 2022-01-03 18:14:37 -05:00
Fusion future
b2fb4f2ea2 Desktop: Add "X-GNOME-SingleWindow=true" to the desktop file (#5948)
Since this commit (ebd2acd98e),
Plasma Desktop will check "X-GNOME-SingleWindow" property to determine
whether to show "Open New Window" action in the context menu of a task.

Joplin cannot launch a new instance or open a new window, so add the
property and set it to true to hide the action.
2022-01-02 21:15:08 -07:00
Laurent Cozic
c74e51a58e Tools: Apply "withRetry" technique to copyPluginAssets too to try to fix CI 2022-01-02 18:38:06 +01:00
Daeraxa
7cba4be498 Doc: update e2ee spec paths and links (#5938) 2022-01-02 12:03:07 -05:00
giorgi shengelaia
19b396f2ec Doc: fix grammar and typos (#5946) 2022-01-02 11:57:30 -05:00
Laurent
4ed7c340a0 Tools: Fixed Docker image building and make it more stable (#5942) 2022-01-02 12:48:03 +00:00
Laurent Cozic
fe770917fd Tools: Also build Docker image using sequential build 2022-01-01 15:28:33 +01:00
Laurent Cozic
f2168d3bca Tools: Also build Docker image using sequential build 2022-01-01 15:24:56 +01:00
Laurent Cozic
5d4ebc6e14 Tools: Build package sequentially on CI to try to prevent various file access errors 2022-01-01 15:06:57 +01:00
Laurent Cozic
3cf0841775 Desktop: Fixes #5707: Add back text editor commands to Command Palette 2021-12-31 09:20:29 +01:00
Laurent Cozic
e5b6ecc50b Chore: Added back accidentally ignored file (loadEmojiLib.js) 2021-12-31 08:40:54 +01:00
Laurent Cozic
f451633a51 Desktop: Fixes #5927: Update menu item labels when the language changes 2021-12-31 07:50:32 +01:00
Laurent Cozic
e813d15ef2 Desktop: Resolves #5934: Use same notebook dialog when creating a new notebook too 2021-12-31 07:26:06 +01:00
Laurent Cozic
b5b02d8d7b Chore: Removed unused method 2021-12-30 12:11:23 +01:00
Laurent Cozic
2660ff3af6 Plugins: Adds joplin.workspace.onResourceChange 2021-12-30 12:11:22 +01:00
Xavi Ivars
59cdcaf8d1 All: Translation: Update ca.po (#5928)
* Update ca.po

* Update ca.po
2021-12-29 18:30:22 -05:00
Laurent Cozic
d6f4ebfff5 Server v2.7.3 2021-12-29 14:35:39 +01:00
Laurent Cozic
e03655bc4d Merge branch 'dev' into release-2.7 2021-12-29 14:34:51 +01:00
Laurent Cozic
9426a2170c Server v2.7.2 2021-12-29 14:34:28 +01:00
Laurent Cozic
b47a541976 Server v2.7.1 2021-12-29 14:34:28 +01:00
Laurent Cozic
4df5ad5c7a Tools: Trying to fix Docker image building 2021-12-29 14:34:07 +01:00
Laurent Cozic
b214c8e42a Server v2.7.2 2021-12-29 12:21:20 +01:00
Laurent Cozic
360ac40f50 Merge branch 'dev' into release-2.7 2021-12-29 12:20:47 +01:00
Laurent Cozic
7c00bc5d0e Server v2.7.1 2021-12-29 12:20:41 +01:00
Laurent Cozic
6f0f3d586e Fixed starting Joplin Server 2021-12-29 12:20:16 +01:00
Laurent Cozic
863d894af1 Tools: Test migration upgrade and downgrade on Joplin Server 2021-12-29 11:54:27 +01:00
Laurent Cozic
2748df7c61 Server v2.7.1 2021-12-29 10:51:24 +01:00
Laurent Cozic
a595b8f250 Tools: Fixed git-changelog 2021-12-29 10:48:34 +01:00
Laurent Cozic
a6ff60aa99 Tools: Add doc for CI fix 2021-12-28 20:57:51 +01:00
Laurent Cozic
aaf5d74b94 Tools: Trying to fix CI 2021-12-28 14:52:39 +01:00
Laurent Cozic
c7d0d659a0 Tools: Fixed linter and tsc errors 2021-12-28 14:17:59 +01:00
Laurent Cozic
57e46711f6 Tools: Trying to fix CI 2021-12-28 12:19:09 +01:00
Laurent Cozic
297b992944 Desktop: Right click on image to copy it to clipboard 2021-12-28 12:00:40 +01:00
Laurent Cozic
3b05e7ec5f Tools: Trying to fix CI 2021-12-28 11:43:15 +01:00
Kenichi Kobayashi
b98e64c881 Desktop: Fixes #5850: Editor loses cursor focus when Ctrl+F search is closed (#5919) 2021-12-28 10:26:33 +00:00
Hieu-Thi Luong
46438a5888 Desktop: Better handling of bold text to simplify customisation (#5732) 2021-12-28 09:57:34 +00:00
Laurent
d1e02fd5f0 Server: Allow deleting complete user data (#5824) 2021-12-28 09:55:01 +00:00
Laurent Cozic
b41a3d7f8d Tools: Trying to fix CI 2021-12-28 10:46:58 +01:00
Shing Lyu
88b56a2abd Doc: Add yarn installation instruction (#5920)
Although it obvious to Node.js developers how to install yarn, people from other languages might need a little hint.
2021-12-27 17:42:40 -05:00
Laurent Cozic
c898214e7e Tools: Trying to fix CI 2021-12-27 19:47:17 +01:00
Kenichi Kobayashi
852e6c141b Desktop: Fixes #5549: cannot jump if local search count is one (#5894) 2021-12-27 18:13:09 +00:00
Laurent
fa868297a2 Plugins: Throttle plugins that make too many API calls (#5895) 2021-12-27 18:12:21 +00:00
Laurent Cozic
3744e08335 Plugin Generator release v2.7.3 2021-12-27 18:23:50 +01:00
Laurent Cozic
ea56d020e9 Tools: Improve plugin generator release script 2021-12-27 18:22:19 +01:00
447 changed files with 30189 additions and 18937 deletions

View File

@@ -1,12 +1,19 @@
_mydocs/
_releases/
.git/
.yarn/cache/
**/.DS_Store
**/node_modules
Assets/
.git/
_releases/
packages/app-desktop
packages/app-cli
packages/app-mobile
packages/app-clipper
packages/generator-joplin
packages/plugin-repo-cli
docs/
lerna-debug.log
packages/app-cli/
packages/app-clipper/
packages/app-desktop/
packages/app-mobile/
packages/generator-joplin/
packages/plugin-repo-cli/
packages/server/db-*.sqlite
packages/server/temp
packages/server/dist/
packages/server/logs/
packages/server/temp/

View File

@@ -235,6 +235,9 @@ packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
packages/app-desktop/gui/ErrorBoundary.d.ts
packages/app-desktop/gui/ErrorBoundary.js
packages/app-desktop/gui/ErrorBoundary.js.map
packages/app-desktop/gui/FolderIconBox.d.ts
packages/app-desktop/gui/FolderIconBox.js
packages/app-desktop/gui/FolderIconBox.js.map
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
@@ -487,6 +490,12 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js.map
packages/app-desktop/gui/NoteEditor/utils/index.d.ts
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/index.js.map
@@ -721,6 +730,9 @@ packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map
packages/app-desktop/gui/utils/loadScript.d.ts
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gui/utils/loadScript.js.map
packages/app-desktop/loadResources.testEnv.d.ts
packages/app-desktop/loadResources.testEnv.js
packages/app-desktop/loadResources.testEnv.js.map
packages/app-desktop/plugins/GotoAnything.d.ts
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/plugins/GotoAnything.js.map
@@ -733,6 +745,9 @@ packages/app-desktop/services/commands/stateToWhenClauseContext.js.map
packages/app-desktop/services/commands/types.d.ts
packages/app-desktop/services/commands/types.js
packages/app-desktop/services/commands/types.js.map
packages/app-desktop/services/plugins/BackOffHandler.d.ts
packages/app-desktop/services/plugins/BackOffHandler.js
packages/app-desktop/services/plugins/BackOffHandler.js.map
packages/app-desktop/services/plugins/PlatformImplementation.d.ts
packages/app-desktop/services/plugins/PlatformImplementation.js
packages/app-desktop/services/plugins/PlatformImplementation.js.map
@@ -1144,6 +1159,9 @@ packages/lib/models/NoteTag.js.map
packages/lib/models/Resource.d.ts
packages/lib/models/Resource.js
packages/lib/models/Resource.js.map
packages/lib/models/Resource.test.d.ts
packages/lib/models/Resource.test.js
packages/lib/models/Resource.test.js.map
packages/lib/models/ResourceLocalState.d.ts
packages/lib/models/ResourceLocalState.js
packages/lib/models/ResourceLocalState.js.map
@@ -1630,6 +1648,9 @@ packages/lib/services/searchengine/SearchEngineUtils.js.map
packages/lib/services/searchengine/SearchEngineUtils.test.d.ts
packages/lib/services/searchengine/SearchEngineUtils.test.js
packages/lib/services/searchengine/SearchEngineUtils.test.js.map
packages/lib/services/searchengine/SearchFilter.test.d.ts
packages/lib/services/searchengine/SearchFilter.test.js
packages/lib/services/searchengine/SearchFilter.test.js.map
packages/lib/services/searchengine/filterParser.d.ts
packages/lib/services/searchengine/filterParser.js
packages/lib/services/searchengine/filterParser.js.map
@@ -1924,6 +1945,9 @@ packages/renderer/headerAnchor.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
packages/renderer/htmlUtils.test.d.ts
packages/renderer/htmlUtils.test.js
packages/renderer/htmlUtils.test.js.map
packages/renderer/index.d.ts
packages/renderer/index.js
packages/renderer/index.js.map
@@ -1954,6 +1978,9 @@ packages/tools/generate-images.js.map
packages/tools/git-changelog.d.ts
packages/tools/git-changelog.js
packages/tools/git-changelog.js.map
packages/tools/licenseChecker.d.ts
packages/tools/licenseChecker.js
packages/tools/licenseChecker.js.map
packages/tools/release-android.d.ts
packages/tools/release-android.js
packages/tools/release-android.js.map
@@ -1999,6 +2026,15 @@ packages/tools/website/updateDownloadPage.js.map
packages/tools/website/utils/frontMatter.d.ts
packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/frontMatter.js.map
packages/tools/website/utils/openGraph.d.ts
packages/tools/website/utils/openGraph.js
packages/tools/website/utils/openGraph.js.map
packages/tools/website/utils/openGraph.test.d.ts
packages/tools/website/utils/openGraph.test.js
packages/tools/website/utils/openGraph.test.js.map
packages/tools/website/utils/parser.d.ts
packages/tools/website/utils/parser.js
packages/tools/website/utils/parser.js.map
packages/tools/website/utils/pressCarousel.d.ts
packages/tools/website/utils/pressCarousel.js
packages/tools/website/utils/pressCarousel.js.map

View File

@@ -37,6 +37,9 @@ echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME"
echo "GITHUB_REF=$GITHUB_REF"
echo "RUNNER_OS=$RUNNER_OS"
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "BUILD_SEQUENCIAL=$BUILD_SEQUENCIAL"
echo "SERVER_REPOSITORY=$SERVER_REPOSITORY"
echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
@@ -168,10 +171,10 @@ cd "$ROOT_DIR/packages/app-desktop"
if [[ $GIT_TAG_NAME = v* ]]; then
echo "Step: Building and publishing desktop application..."
USE_HARD_LINKS=false yarn run dist
elif [[ $GIT_TAG_NAME = server-v* ]] && [[ $IS_LINUX = 1 ]]; then
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
echo "Step: Building Docker Image..."
cd "$ROOT_DIR"
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
else
echo "Step: Building but *not* publishing desktop application..."
USE_HARD_LINKS=false yarn run dist --publish=never

25
.github/stale.yml vendored
View File

@@ -1,25 +0,0 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- "good first issue"
- "upstream"
- "backlog"
- "high"
- "medium"
- "spec"
- "cannot reproduce"
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs.
You may comment on the issue and I will leave it open.
Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.
only: issues

View File

@@ -0,0 +1,23 @@
name: 'Close stale issues'
on:
schedule:
- cron: '0 16 * * *'
permissions:
issues: write
jobs:
ProcessStaleIssues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
# Use this to do a dry run from a pull request
# debug-only: true
stale-issue-message: "Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may comment on the issue and I will leave it open. Thank you for your contributions."
days-before-stale: 30
days-before-close: 7
operations-per-run: 1000
exempt-issue-labels: 'good first issue,upstream,backlog,high,medium,spec,cannot reproduce,enhancement'
stale-issue-label: 'stale'
close-issue-message: 'Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, feel free to create a new issue with up-to-date information.'
# Don't process pull requests at all
days-before-pr-stale: -1

View File

@@ -74,6 +74,7 @@ jobs:
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
run: |
"${GITHUB_WORKSPACE}/.github/scripts/run_ci.sh"
@@ -84,10 +85,23 @@ jobs:
CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
# To ensure that the operations stop on failure, all commands
# should be on one line with "&&" in between.
run: |
yarn install
cd packages/app-desktop
yarn run dist
yarn install && cd packages/app-desktop && yarn run dist
# Build and package the Windows app, without publishing it, just to
# verify that the build process hasn't been broken.
- name: Build Windows app (no publishing)
if: runner.os == 'Windows' && !startsWith(github.ref, 'refs/tags/v')
env:
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
SERVER_REPOSITORY: joplin/server
SERVER_TAG_PREFIX: server
run: |
yarn install && cd packages/app-desktop && yarn run dist --publish=never
ServerDockerImage:
runs-on: ${{ matrix.os }}
@@ -122,7 +136,9 @@ jobs:
corepack enable
- name: Build Docker Image
env:
BUILD_SEQUENCIAL: 1
run: |
yarn install
yarn run buildServerDocker --tag-name server-v0.0.0
yarn run buildServerDocker --tag-name server-v0.0.0 --repository joplin/server

36
.gitignore vendored
View File

@@ -225,6 +225,9 @@ packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
packages/app-desktop/gui/ErrorBoundary.d.ts
packages/app-desktop/gui/ErrorBoundary.js
packages/app-desktop/gui/ErrorBoundary.js.map
packages/app-desktop/gui/FolderIconBox.d.ts
packages/app-desktop/gui/FolderIconBox.js
packages/app-desktop/gui/FolderIconBox.js.map
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
@@ -477,6 +480,12 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js.map
packages/app-desktop/gui/NoteEditor/utils/index.d.ts
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/index.js.map
@@ -711,6 +720,9 @@ packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map
packages/app-desktop/gui/utils/loadScript.d.ts
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gui/utils/loadScript.js.map
packages/app-desktop/loadResources.testEnv.d.ts
packages/app-desktop/loadResources.testEnv.js
packages/app-desktop/loadResources.testEnv.js.map
packages/app-desktop/plugins/GotoAnything.d.ts
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/plugins/GotoAnything.js.map
@@ -723,6 +735,9 @@ packages/app-desktop/services/commands/stateToWhenClauseContext.js.map
packages/app-desktop/services/commands/types.d.ts
packages/app-desktop/services/commands/types.js
packages/app-desktop/services/commands/types.js.map
packages/app-desktop/services/plugins/BackOffHandler.d.ts
packages/app-desktop/services/plugins/BackOffHandler.js
packages/app-desktop/services/plugins/BackOffHandler.js.map
packages/app-desktop/services/plugins/PlatformImplementation.d.ts
packages/app-desktop/services/plugins/PlatformImplementation.js
packages/app-desktop/services/plugins/PlatformImplementation.js.map
@@ -1134,6 +1149,9 @@ packages/lib/models/NoteTag.js.map
packages/lib/models/Resource.d.ts
packages/lib/models/Resource.js
packages/lib/models/Resource.js.map
packages/lib/models/Resource.test.d.ts
packages/lib/models/Resource.test.js
packages/lib/models/Resource.test.js.map
packages/lib/models/ResourceLocalState.d.ts
packages/lib/models/ResourceLocalState.js
packages/lib/models/ResourceLocalState.js.map
@@ -1620,6 +1638,9 @@ packages/lib/services/searchengine/SearchEngineUtils.js.map
packages/lib/services/searchengine/SearchEngineUtils.test.d.ts
packages/lib/services/searchengine/SearchEngineUtils.test.js
packages/lib/services/searchengine/SearchEngineUtils.test.js.map
packages/lib/services/searchengine/SearchFilter.test.d.ts
packages/lib/services/searchengine/SearchFilter.test.js
packages/lib/services/searchengine/SearchFilter.test.js.map
packages/lib/services/searchengine/filterParser.d.ts
packages/lib/services/searchengine/filterParser.js
packages/lib/services/searchengine/filterParser.js.map
@@ -1914,6 +1935,9 @@ packages/renderer/headerAnchor.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
packages/renderer/htmlUtils.test.d.ts
packages/renderer/htmlUtils.test.js
packages/renderer/htmlUtils.test.js.map
packages/renderer/index.d.ts
packages/renderer/index.js
packages/renderer/index.js.map
@@ -1944,6 +1968,9 @@ packages/tools/generate-images.js.map
packages/tools/git-changelog.d.ts
packages/tools/git-changelog.js
packages/tools/git-changelog.js.map
packages/tools/licenseChecker.d.ts
packages/tools/licenseChecker.js
packages/tools/licenseChecker.js.map
packages/tools/release-android.d.ts
packages/tools/release-android.js
packages/tools/release-android.js.map
@@ -1989,6 +2016,15 @@ packages/tools/website/updateDownloadPage.js.map
packages/tools/website/utils/frontMatter.d.ts
packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/frontMatter.js.map
packages/tools/website/utils/openGraph.d.ts
packages/tools/website/utils/openGraph.js
packages/tools/website/utils/openGraph.js.map
packages/tools/website/utils/openGraph.test.d.ts
packages/tools/website/utils/openGraph.test.js
packages/tools/website/utils/openGraph.test.js.map
packages/tools/website/utils/parser.d.ts
packages/tools/website/utils/parser.js
packages/tools/website/utils/parser.js.map
packages/tools/website/utils/pressCarousel.d.ts
packages/tools/website/utils/pressCarousel.js
packages/tools/website/utils/pressCarousel.js.map

View File

@@ -307,7 +307,7 @@ p,
div.navbar-mobile-content a.sponsor-button {
padding: 4px 12px;
font-size: 0.9em;
margin-right: 1em;
margin-right: 0.5em;
}
#nav-section.white-bg a {
@@ -670,9 +670,9 @@ footer .bottom-links-row p {
margin-right: auto;
}
.news-page img,
.news-item-page img {
max-width: 650px;
.news-page .main-content img,
.news-item-page .main-content img {
max-width: 100%;
}
/*****************************************************************
@@ -728,6 +728,23 @@ footer .bottom-links-row p {
}
}
/*****************************************************************
MEDIUM VIEW
- Make menu bar elements smaller and closer to each others
so that everything fit.
*****************************************************************/
@media (max-width: 990px) {
#nav-section > .container {
max-width: 960px;
}
#nav-section .button-link {
padding: 4px 10px;
font-size: 15px;
}
}
/*****************************************************************
NARROW VIEW
- Top right menu is displayed
@@ -740,6 +757,23 @@ footer .bottom-links-row p {
padding-bottom: 130px;
}
#menu-mobile .social-links {
display: flex;
justify-content: center;
margin-top: 20px;
}
#menu-mobile .social-links a {
margin-left: 15px;
font-size: 20px;
}
#menu-mobile .social-links .social-link-mastodon,
#menu-mobile .social-links .social-link-reddit,
#menu-mobile .social-links .social-link-patreon {
display: none;
}
.front-page h1 {
font-size: 2.5em;
}
@@ -857,7 +891,7 @@ footer .bottom-links-row p {
}
#menu-mobile .button-link {
padding: 10px 15px;
padding: 4px 12px;
font-size: 16px;
margin-left: 0px;
}
@@ -903,6 +937,25 @@ footer .bottom-links-row p {
}
}
/*****************************************************************
VERY NARROW VIEW
eg for Galaxy Fold
*****************************************************************/
@media (max-width: 350px) {
#nav-section .navbar-mobile-content a.sponsor-button {
background-color: transparent;
color: #0557ba !important;
font-size: 18px;
}
#nav-section .navbar-mobile-content a.sponsor-button .sponsor-button-label {
display: none;
}
}
/*****************************************************************
PLANS PAGE
*****************************************************************/
@@ -1020,6 +1073,10 @@ footer .bottom-links-row p {
display: none;
}
.joplin-cloud-feature-list table {
width: 100%;
}
.price-row .plan-type {
display: flex;
align-items: center;

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -10,8 +10,8 @@
<link rel="icon" href="{{imageBaseUrl}}/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Joplin, the open source note-taking application" />
<link rel="stylesheet" href="{{{assetUrls.css.fontawesome}}}">
{{> openGraphTags}}
<link
rel="stylesheet"
href="{{cssBaseUrl}}/bootstrap5.0.2.min.css"

View File

@@ -23,7 +23,7 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
<link rel="icon" href="{{imageBaseUrl}}/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Joplin, the open source note-taking application" />
{{> openGraphTags}}
<link
rel="stylesheet"
href="{{cssBaseUrl}}/bootstrap5.0.2.min.css"
@@ -65,11 +65,6 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
{{{contentHtml}}}
{{#showBottomLinks}}
<div class="bottom-links">
{{#discussOnForumLink}}
<a class="bottom-link" href="{{{discussOnForumLink}}}">
<i class="fab fa-discourse"></i></i> Discuss on the forum
</a>
{{/discussOnForumLink}}
{{#showImproveThisDoc}}
<a class="bottom-link" href="https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}">
<i class="fab fa-github"></i> Improve this doc

View File

@@ -1,15 +1,6 @@
<footer class="darkblue-bg">
<div class="container">
<div class="row">
<div class="col-12 col-md-12 social-links">
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed"><i class="fab fa-twitter"></i></a>
<a href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
<a href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
<a href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
<a href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
<a href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
</div>
</div>
{{> socialFeeds}}
<div class="row bottom-links-row">
<div class="col-12 col-md-6">

View File

@@ -12,7 +12,8 @@
</a>
</div>
<div class="col-9 text-right d-none d-md-block">
<a href="{{baseUrl}}/news/" class="fw500">What's New</a>
{{> twitterLink}}
<a href="{{baseUrl}}/news/" class="fw500">News</a>
<a href="{{baseUrl}}/help/" class="fw500">Help</a>
<a href="{{forumUrl}}" class="fw500">Forum</a>
{{#showJoplinCloudLinks}}
@@ -21,6 +22,7 @@
{{> supportButton}}
</div>
<div class="col-9 text-right d-block d-md-none navbar-mobile-content">
{{> twitterLink}}
{{> supportButton}}
<span class="pointer"
@@ -43,7 +45,7 @@
</div>
<div class="text-center menu-mobile-top">
<a href="{{baseUrl}}/news/" class="fw500 mobile-menu-link">What's New</a>
<a href="{{baseUrl}}/news/" class="fw500 mobile-menu-link">News</a>
<a href="{{baseUrl}}/help/" class="fw500 mobile-menu-link">Help</a>
<a href="{{forumUrl}}" class="fw500 mobile-menu-link">Forum</a>
</div>
@@ -59,6 +61,8 @@
{{#showToc}}
<div id="toc-mobile">{{{tocHtml}}}</div>
{{/showToc}}
{{> socialFeeds}}
<div>
<p class="light-blue mobile-menu-link-bottom text-center">

View File

@@ -0,0 +1,14 @@
{{#openGraph}}
<meta name="description" content="{{openGraph.description}}" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@joplinapp" />
<meta property="og:url" content="{{{openGraph.url}}}" />
<meta property="og:title" content="{{openGraph.title}}" />
<meta property="twitter:title" content="{{openGraph.title}}" />
<meta property="og:description" content="{{openGraph.description}}" />
<meta property="twitter:description" content="{{openGraph.description}}" />
{{#openGraph.image}}
<meta property="og:image" content="{{{openGraph.image}}}" />
<meta property="twitter:image" content="{{{openGraph.image}}}" />
{{/openGraph.image}}
{{/openGraph}}

View File

@@ -6,11 +6,11 @@
</div>
<div class="plan-price plan-price-monthly">
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month</sub>
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month{{#footnote}} (*){{/footnote}}</sub>
</div>
<div class="plan-price plan-price-yearly">
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month</sub>
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month{{#footnote}} (*){{/footnote}}</sub>
</div>
</div>
@@ -20,17 +20,19 @@
</div>
</div>
{{#featuresOn}}
{{#featureLabelsOn}}
<p><i class="fas fa-check feature feature-on"></i>{{.}}</p>
{{/featuresOn}}
{{/featureLabelsOn}}
{{#featuresOff}}
{{#featureLabelsOff}}
<p class="unchecked-text"><i class="fas fa-times feature feature-off"></i>{{.}}</p>
{{/featuresOff}}
{{/featureLabelsOff}}
<p class="text-center subscribe-wrapper">
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton">{{cfaLabel}}</a>
</p>
{{#footnote}}<sub>(*) {{.}}</sub>{{/footnote}}
</div>
<script>

View File

@@ -0,0 +1,10 @@
<div class="row">
<div class="col-12 col-md-12 social-links">
<a class="social-link-twitter" href="https://twitter.com/joplinapp" title="Joplin Twitter feed"><i class="fab fa-twitter"></i></a>
<a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
<a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
<a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
<a class="social-link-reddit" href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
<a class="social-link-github" href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
</div>
</div>

View File

@@ -1,3 +1,3 @@
<a class="button-link btn-blue sponsor-button" href="{{baseUrl}}/donate">
<i class="fas fa-heart heart-full"></i><i class="far fa-heart heart-line"></i>&nbsp;Support us
<i class="fas fa-heart heart-full"></i><i class="far fa-heart heart-line"></i><span class="sponsor-button-label">&nbsp;Support us</span>
</a>

View File

@@ -0,0 +1 @@
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500"><i class="fab fa-twitter"></i></a>

View File

@@ -42,13 +42,23 @@
{{> plan}}
{{/plans.pro}}
{{#plans.business}}
{{#plans.teams}}
{{> plan}}
{{/plans.business}}
{{/plans.teams}}
<p class="joplin-cloud-login-info">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
</div>
<div class="row">
<div>
<h1>Feature comparison</h1>
<div class="joplin-cloud-feature-list">
{{{featureListHtml}}}
</div>
<p>&nbsp;</p>
</div>
</div>
<div class="row">
{{{faqHtml}}}
</div>

View File

@@ -18,13 +18,15 @@ There are also a few forks of existing packages under the "fork-*" name.
## Required dependencies
- Install node 16+ - https://nodejs.org/en/
- macOS: Install Cocoapods - `brew install cocoapods`
- Windows: Install Windows Build Tools - `yarn install -g windows-build-tools --vs2015`
- Install Node 16+. On Windows, also install the build tools - https://nodejs.org/en/
- [Enable Yarn](https://yarnpkg.com/getting-started/install): `corepack enable`
- macOS: Install Cocoapods - `brew install cocoapods`. Apple Silicon [may require libvips](https://github.com/laurent22/joplin/pull/5966#issuecomment-1007158597) - `brew install vips`.
- Linux: Install dependencies - `sudo apt install build-essential libnss3 libsecret-1-dev python rsync`
## Building
Make sure the path to the project directory does not contain spaces or the build may fail.
Before doing anything else, from the root of the project, run:
yarn install
@@ -59,7 +61,7 @@ Normally the **bundler** should start automatically with the application. If it
## Building the clipper
cd packages/app-clipper/popup
yarn run watch # To watch for changes
npm run watch # To watch for changes
To test the extension please refer to the relevant pages for each browser: [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Trying_it_out) / [Chrome](https://developer.chrome.com/docs/extensions/mv3/getstarted/). Please note that the extension in dev mode will only connect to a dev instance of the desktop app (and vice-versa).

View File

@@ -31,14 +31,15 @@ Joplin is available in multiple languages thanks to the help of its users. You c
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
- Explain WHY you want to add this change. Explain it inside the pull request and you may link to an issue for additional information, but the PR should gives a clear overview of why you want to add this.
- Explain WHY you want to add this change. Explain it inside the pull request and you may link to an issue for additional information, but the PR should give a clear overview of why you want to add this.
- Bug fixes are always welcome. Start by reviewing the [list of bugs](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
- A good way to easily start contributing is to pick and work on a [good first issue](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). We try to make these issues as clear as possible and provide basic info on how the code should be changed, and if something is unclear feel free to ask for more information on the issue.
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
- All the applications share the same backend (database, synchronisation, settings, models, business logic, etc.) so if you change something in the backend in one app, makes sure it still work in the other apps. Usually it does, but keep this in mind.
- **Changes that will consist of more than 50 lines of code should be discussed on the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
- All the applications share the same backend (database, synchronisation, settings, models, business logic, etc.) so if you change something in the backend in one app, make sure it still works in the other apps. Usually it does, but keep this in mind.
- Pull requests that make many changes using an automated tool, like for spell fixing, styling, etc. will not be accepted. An exception would be if the changes have been discussed in the forum and someone has agreed to review **and test** the pull request.
- Pull requests that make address multiple issues will most likely stall and eventually be closed. This is because we might be fine with one of the changes but not with others and untangling that kind of pull request is too much hassle both for maintainers and the person who submitted it. So most of the time someone gives up and the PR gets closed. So please keep the pull request focused on one issue.
- Pull requests that address multiple issues will most likely stall and eventually be closed. This is because we might be fine with one of the changes but not with others and untangling that kind of pull request is too much hassle both for maintainers and the person who submitted it. So most of the time someone gives up and the PR gets closed. So please keep the pull request focused on one issue.
- **Do not mark your reviewer's comments as "resolved"**. If you do that, the comments will be hidden and the reviewer will not know what are the pending issues in the pull request. Only the reviewer should resolve the comments.
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/dev/BUILD.md) for more details.
@@ -52,37 +53,57 @@ For changes made to the Desktop client that affect the user interface, refer to
## Automated tests
When submitting a pull request for a new feature or a bug fix, please add automated tests for your code whenever possible. Tests in Joplin are divided into **unit tests** and **feature tests**.
When submitting a pull request for a new feature or a bug fix, please add automated tests. We use [Jest](https://jestjs.io/) as a testing framework so you will need to be familiar with it or go through their documentation.
* **Unit tests** are used to test models, services or utility classes - they are relatively low level. Unit tests should be prefixed with the type of class that is being tested - for example "models_Folder" or "services_SearchEngine".
### Running the tests
* **Feature tests** on the other hand are to test higher level functionalities such as interactions with the GUI and how they affect the underlying model. Often these tests would dispatch Redux actions, and inspect how the application state has been changed. The feature tests should be prefixed with "feature_", for example "feature_TagList". There's a good explanation on what qualifies as a feature test in [this post](https://github.com/laurent22/joplin/pull/2819#issuecomment-603502230).
The tests are under packages/app-cli/tests. To get them running, you first need to build the CLI app:
```sh
yarn install
cd packages/app-cli
```
To run all the test units:
To run all the test units, run from the root:
```sh
yarn test
```
Or you can go inside a package folder, and run the tests from there. For example to run all the library tests, go in `packages/lib` and run `yarn test`
To run just one particular file:
```sh
yarn test --filter=markdownUtils # Don't add the .js extension
# Run all the tests in markdownUtils.test.ts
yarn test markdownUtils
```
To filter tests. For example, to run all the test units that contain "should handle conflict" in their description:
To run only a particular test in a file:
```sh
yarn test --filter="should handle conflict"
# Run only the test described as "should handle conflict"
# inside markdownUtils.test.ts:
yarn test markdownUtils --filter="should handle conflict"
```
### Adding a new test file
To add a test, simply create a new file with an extension `.test.ts` in the same directory. For example if you are working on the file `example.ts`, create a file `example.test.ts` for the unit tests. If this file already exist, simply add your tests directly to it.
### Setting the testing environment
Many utility functions are available under the package `@joplin/lib/testing/test-utils`. Have a look for example at [Note.test.ts](https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Note.test.ts) to see how to setup test units with database support and synchroniser support. Note that this is not needed for all tests - if you just have a simple functions to test you won't need that extra setup.
### Testing React Hooks
To test React Hooks please use the package `@testing-library/react-hooks`. See [useLayoutItemSizes.test.ts](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.ts) for an example.
### If it is not possible to add tests
More often than not, it is actually possible to add tests - just go back to your code and see if it can be refactored and certain functionalities moved to simple functions (with no dependencies). Once you have a simple function, you can easily add unit tests for it.
Additionally, if the unit tests are not sufficient, please provide a **manual testing plan**, which should include detailed steps on:
- How to test that your feature is working. Include at least 5 tests. Try to think of the possible inputs - if it's a list, how does it work with 0 elements, or 1, or 10, or 100,000. If it's a text input, how does it work with an empty string, or a very large string, etc. Basically don't just put one test that check the best case scenario.
- How to verify that related parts of the applications are not broken. For example if you changed the note loading logic, check that the toolbar is still working as expected (and not modifying the previously loaded note for example), check that switching from one note to another still works. Look at the note list and verify that the note title is updated there too, etc.
A reviewer should be able to run the app with your changes, then do the above steps to verify that everything's working as expected.
## About abandoned pull requests
It happens that a pull request is started but not finished and despite our attempts to contact the contributor, we don't hear from them again.

View File

@@ -1,88 +1,75 @@
FROM node:16-bullseye
# =============================================================================
# Build stage
# =============================================================================
FROM node:16-bullseye AS builder
RUN apt-get update \
&& apt-get install -y \
python \
python tini \
&& rm -rf /var/lib/apt/lists/*
# Enables Yarn
RUN corepack enable
RUN echo "Node: $(node --version)"
RUN echo "Npm: $(npm --version)"
RUN echo "Yarn: $(yarn --version)"
WORKDIR /build
COPY .yarn/plugins ./.yarn/plugins
COPY .yarn/releases ./.yarn/releases
COPY package.json .
COPY .yarnrc.yml .
COPY yarn.lock .
COPY gulpfile.js .
COPY tsconfig.json .
COPY packages/turndown ./packages/turndown
COPY packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm
COPY packages/fork-htmlparser2 ./packages/fork-htmlparser2
COPY packages/server/package*.json ./packages/server/
COPY packages/fork-sax ./packages/fork-sax
COPY packages/fork-uslug ./packages/fork-uslug
COPY packages/htmlpack ./packages/htmlpack
COPY packages/renderer ./packages/renderer
COPY packages/tools ./packages/tools
COPY packages/lib ./packages/lib
COPY packages/server ./packages/server
# For some reason there's both a .yarn/cache and .yarn/berry/cache that are
# being generated, and both have the same content. Not clear why it does this
# but we can delete it anyway. We can delete the cache because we use
# `nodeLinker: node-modules`. If we ever implement Zero Install, we'll need to
# keep the cache.
#
# Note that `yarn install` ignores `NODE_ENV=production` and will install dev
# dependencies too, but this is fine because we need them to build the app.
RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \
&& yarn cache clean \
&& rm -rf .yarn/berry
# =============================================================================
# Final stage - we copy only the relevant files from the build stage and start
# from a smaller base image.
# =============================================================================
FROM node:16-bullseye-slim
ARG user=joplin
RUN useradd --create-home --shell /bin/bash $user
USER $user
ENV NODE_ENV development
WORKDIR /home/$user
RUN mkdir /home/$user/logs
# Install the root scripts but don't run postinstall (which would bootstrap
# and build TypeScript files, but we don't have the TypeScript files at
# this point)
COPY --chown=$user:$user package*.json ./
COPY --chown=$user:$user .yarn ./.yarn
COPY --chown=$user:$user .yarnrc.yml .
COPY --chown=$user:$user yarn.lock .
RUN yarn install --inline-builds --mode=skip-build
# To take advantage of the Docker cache, we first copy all the package.json
# and package-lock.json files, as they rarely change, and then bootstrap
# all the packages.
#
# Note that bootstrapping the packages will run all the postinstall
# scripts, which means that for packages that have such scripts, we need to
# copy all the files.
#
# We can't run boostrap with "--ignore-scripts" because that would
# prevent certain sub-packages, such as sqlite3, from being built
COPY --chown=$user:$user packages/fork-sax/package*.json ./packages/fork-sax/
COPY --chown=$user:$user packages/fork-uslug/package*.json ./packages/fork-uslug/
COPY --chown=$user:$user packages/htmlpack/package*.json ./packages/htmlpack/
COPY --chown=$user:$user packages/renderer/package*.json ./packages/renderer/
COPY --chown=$user:$user packages/tools/package*.json ./packages/tools/
COPY --chown=$user:$user packages/lib/package*.json ./packages/lib/
COPY --chown=$user:$user tsconfig.json .
# The following have postinstall scripts so we need to copy all the files.
# Since they should rarely change this is not an issue
COPY --chown=$user:$user packages/turndown ./packages/turndown
COPY --chown=$user:$user packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm
COPY --chown=$user:$user packages/fork-htmlparser2 ./packages/fork-htmlparser2
COPY --chown=$user:$user packages/server/package*.json ./packages/server/
# Then bootstrap only, without compiling the TypeScript files
RUN yarn install --inline-builds --mode=skip-build
# Now copy the source files. Put lib and server last as they are more likely to change.
COPY --chown=$user:$user packages/fork-sax ./packages/fork-sax
COPY --chown=$user:$user packages/fork-uslug ./packages/fork-uslug
COPY --chown=$user:$user packages/htmlpack ./packages/htmlpack
COPY --chown=$user:$user packages/renderer ./packages/renderer
COPY --chown=$user:$user packages/tools ./packages/tools
COPY --chown=$user:$user packages/lib ./packages/lib
COPY --chown=$user:$user packages/server ./packages/server
# Finally build everything, in particular the TypeScript files.
RUN yarn run build
COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages
COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini
ENV NODE_ENV=production
ENV RUNNING_IN_DOCKER=1
EXPOSE ${APP_PORT}
CMD [ "yarn", "--prefix", "packages/server", "start" ]
# Use Tini to start Joplin Server:
# 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"]
# Build-time metadata
# https://github.com/opencontainers/image-spec/blob/master/annotations.md

View File

@@ -219,6 +219,8 @@ then
Type=Application
Categories=Office;
MimeType=x-scheme-handler/joplin;
X-GNOME-SingleWindow=true // should be removed eventually as it was upstream to be an XDG specification
SingleMainWindow=true
EOF
# Update application icons

15
LICENSE
View File

@@ -1,15 +1,20 @@
All code in this repository is licensed under the MIT License **unless a
directory contains a LICENSE file**, in which case that LICENSE file applies to
the code in that sub-directory.
directory contains a LICENSE or LICENSE.md file**, in which case that file
applies to the code in that sub-directory.
For example, packages/fork-sax contains a ISC LICENSE file, thus all code under
the packages/fork-sax directory is licensed under ISC.
For example, packages/server contains a LICENSE.md file, thus all code under the
packages/server directory is licensed under that license.
For example, packages/app-cli does NOT contain a LICENSE file, thus all code
under that directory is licensed under the default license, which is MIT.
* * *
Joplin® is a trademark of Cozic Ltd registered in the European Union, with
filing number 018544315.
* * *
Logo and Icon License
The Joplin logos and icons are copyright (c) Laurent Cozic, all rights reserved,
@@ -20,7 +25,7 @@ icons please contact the author in order to get a permission.
MIT License
Copyright (c) 2016-2021 Laurent Cozic
Copyright (c) 2016-2022 Laurent Cozic
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

154
README.md
View File

@@ -4,13 +4,19 @@
* * *
🌞 Joplin participates in **Google Summer of Code 2022**! More info on [the announcement post](https://github.com/laurent22/joplin/blob/dev/readme/news/20220308-gsoc2022-start.md). 🌞
* * *
<img width="64" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/LinuxIcons/256x256.png" align="left" /> **Joplin** is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](#markdown).
Notes exported from Evernote via .enex files [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
Notes exported from Evernote [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be [synchronised](#synchronisation) with various cloud services including [Nextcloud](https://nextcloud.com/), Dropbox, OneDrive, WebDAV or the file system (for example with a network directory). When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
The notes can be securely [synchronised](#synchronisation) using [end-to-end encryption](#encryption) with various cloud services including Nextcloud, Dropbox, OneDrive and [Joplin Cloud](https://joplinapp.org/plans/).
The application is available for Windows, Linux, macOS, Android and iOS (the terminal app also works on FreeBSD). A [Web Clipper](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md), to save web pages and screenshots from your browser, is also available for [Firefox](https://addons.mozilla.org/firefox/addon/joplin-web-clipper/) and [Chrome](https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek?hl=en-GB).
Full text search is available on all platforms to quickly find the information you need. The app can be customised using plugins and themes, and you can also easily create your own.
The application is available for Windows, Linux, macOS, Android and iOS. A [Web Clipper](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md), to save web pages and screenshots from your browser, is also available for [Firefox](https://addons.mozilla.org/firefox/addon/joplin-web-clipper/) and [Chrome](https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek?hl=en-GB).
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/home-top-img.png" style="max-width: 100%; max-height: 35em;"></div>
@@ -22,11 +28,11 @@ Three types of applications are available: for **desktop** (Windows, macOS and L
Operating System | Download
---|---
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/Joplin-Setup-2.6.10.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/Joplin-2.6.10.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/Joplin-2.6.10.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-Setup-2.7.15.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-2.7.15.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-2.7.15.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Linux**, the recommended way is to use the following installation script as it will handle the desktop icon too:
@@ -36,7 +42,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/J
Operating System | Download | Alt. Download
---|---|---
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.6.9/joplin-v2.6.9.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.6.9/joplin-v2.6.9-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.7.2/joplin-v2.7.2.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.7.2/joplin-v2.7.2-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -49,19 +55,18 @@ To start it, type `joplin`.
For usage information, please refer to the full [Joplin Terminal Application Documentation](https://joplinapp.org/terminal/).
### Unsupported methods
There are other ways to install the terminal application. However, they are not supported and problems must be reported to the upstream projects.
Operating system | Method
-----------------|----------------
Arch Linux | An Arch Linux package is available [here](https://aur.archlinux.org/packages/joplin/). To install it, use an AUR wrapper such as yay: `yay -S joplin`. Both the CLI tool (type `joplin`) and desktop app (type `joplin-desktop`) are packaged. You can also install a compiled version with the [chaotic-aur](https://wiki.archlinux.org/index.php/Unofficial_user_repositories#chaotic-aur) repository. For support, please go to the [GitHub repo](https://github.com/masterkorp/joplin-pkgbuild). If you are interested in [pre-release](https://joplinapp.org/prereleases/) you have [joplin-beta](https://aur.archlinux.org/packages/joplin-beta).
Flatpak | A Flatpak is available on [Flathub](https://flathub.org/apps/details/net.cozic.joplin_desktop). To install it, run `flatpak install net.cozic.joplin_desktop` after [setting up Flathub](https://flatpak.org/setup/). GUI software managers on most distros support Flatpak installation.
## Web Clipper
The Web Clipper is a browser extension that allows you to save web pages and screenshots from your browser. For more information on how to install and use it, see the [Web Clipper Help Page](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md).
## Unofficial Alternative Distributions
There are a number of unofficial alternative Joplin distributions. If you do not want to or cannot use appimages or any of the other officially supported releases then you may wish to consider these.
However these come with a caveat in that they are not officially supported so certain issues may not be supportable by the main project. Rather support requests, bug reports and general advice would need to go to the maintainers of these distributions.
A community maintained list of these distributions can be found here: [Unofficial Joplin distributions](https://discourse.joplinapp.org/t/unofficial-alternative-joplin-distributions/23703)
# Sponsors
<!-- SPONSORS-ORG -->
@@ -75,10 +80,11 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
| :---: | :---: | :---: | :---: |
| <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/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <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/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/82579431?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl) | <img width="50" src="https://avatars2.githubusercontent.com/u/808091?s=96&v=4"/></br>[cuongtransc](https://github.com/cuongtransc) | <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/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) | <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <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/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | <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/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) | <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/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | | |
<!-- SPONSORS-GITHUB -->
<!-- TOC -->
@@ -125,7 +131,9 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- Development
- [How to build the apps](https://github.com/laurent22/joplin/blob/dev/BUILD.md)
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
- [End-to-end encryption spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
@@ -135,11 +143,11 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- [Server: Delta Sync](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_delta_sync.md)
- [Server: Sharing](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_sharing.md)
- Google Summer of Code 2021
- Google Summer of Code 2022
- [Google Summer of Code 2021](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2021/index.md)
- [How to submit a GSoC pull request](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2021/pull_request_guidelines.md)
- [Project Ideas](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2021/ideas.md)
- [Google Summer of Code 2022](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2022/index.md)
- [How to submit a GSoC pull request](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2022/pull_request_guidelines.md)
- [Project Ideas](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2022/ideas.md)
- About
@@ -300,10 +308,10 @@ To add a **Bucket Policy** from the AWS S3 Web Console, navigate to the **Permis
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:PutObject"
@@ -472,24 +480,6 @@ Notes are sorted by "relevance". Currently it means the notes that contain the r
In the desktop application, press <kbd>Ctrl+P</kbd> or <kbd>Cmd+P</kbd> and type a note title or part of its content to jump to it. Or type <kbd>#</kbd> followed by a tag name, or <kbd>@</kbd> followed by a notebook name.
# Privacy
Joplin values your privacy and security by giving you complete control over your information and digital footprint.
Joplin applications do not send any data to any service without your authorisation. Any data that Joplin saves, such as notes or images, are saved to your own device and you are free to delete this data at any time.
Joplin has many modern features, some of which use third-party services. You can disable any or all of these features in the application settings. These features are:
|Feature | Description | Default|
|--------|-------------|--------|
|Auto-update|Joplin periodically connects to GitHub to check for new releases.|Enabled|
|Geo-location|Joplin saves geo-location information in note properties when you create a note.|Enabled|
|Synchronisation|Joplin supports synchronisation of your notes across multiple devices. If you choose to synchronise with a third-party, such as OneDrive, the notes will be sent to your OneDrive account, in which case the third-party privacy policy applies.|Disabled|
Joplin is developed as an open-source application and the source code is freely available online to inspect.
For any question about Joplin privacy, please leave a message on the [Joplin Forum](https://discourse.joplinapp.org/).
# Donations
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
@@ -530,47 +520,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 93%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 27%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 67%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 53%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:s.robin@tutanota.de) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 51%
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 90%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 26%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 65%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 51%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pthrp_bnsrv@aleeas.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 50%
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 30%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 29%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | miucci | 92%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 34%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 92%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 85%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | Alexander Dawson | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 64%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Felipe Viggiano](mailto:felipeviggiano@gmail.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 59%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 43%
<img src="https://joplinapp.org/images/flags/country-4x3/vi.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 83%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 76%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [南宫小骏](mailto:jackytsu@vip.qq.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 89%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 33%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 76%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 83%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | Alexander Dawson | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 62%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Felipe Viggiano](mailto:felipeviggiano@gmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 57%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 41%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [horaceyoung](mailto:yonghaoharry@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 87%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -1,8 +1,19 @@
# This is a sample docker-compose file that can be used to run Joplin Server
# along with a PostgreSQL server.
#
# All environment variables are optional. If you don't set them, you will get a
# warning from docker-compose, however the app should use working defaults.
# Update the following fields in the stanza below:
#
# POSTGRES_USER
# POSTGRES_PASSWORD
# APP_BASE_URL
#
# APP_BASE_URL: This is the base public URL where the service will be running.
# - If Joplin Server needs to be accessible over the internet, configure APP_BASE_URL as follows: https://example.com/joplin.
# - If Joplin Server does not need to be accessible over the internet, set the the APP_BASE_URL to your server's hostname.
# For Example: http://[hostname]:22300. The base URL can include the port.
# APP_PORT: The local port on which the Docker container will listen.
# - This would typically be mapped to port to 443 (TLS) with a reverse proxy.
# - If Joplin Server does not need to be accessible over the internet, the port can be mapped to 22300.
version: '3'

View File

@@ -0,0 +1 @@
eine Notiz- und Aufgaben-App mit Sync zwischen Linux, macOS, Windows

View File

@@ -0,0 +1,9 @@
<strong>Joplin</strong> is a note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in <a href="#markdown">Markdown format</a>.
Notes exported from Evernote and other applications <a href="https://joplinapp.org/help/#importing">can be imported</a> into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be securely <a href="https://joplinapp.org/help/#synchronisation">synchronised</a> using <a href="https://joplinapp.org/help/#encryption">end-to-end encryption</a> with various cloud services including Nextcloud, Dropbox, OneDrive and <a href="https://joplinapp.org/plans/">Joplin Cloud</a>.
Full text search is available on all platforms to quickly find the information you need. The app can be customised using plugins and themes, and you can also easily create your own.
The application is available for Windows, Linux, macOS, Android and iOS. A <a href="https://joplinapp.org/clipper/">Web Clipper</a>, to save web pages and screenshots from your browser, is also available for <a href="https://addons.mozilla.org/firefox/addon/joplin-web-clipper/">Firefox</a> and <a href="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek?hl=en-GB">Chrome</a>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -0,0 +1 @@
a note taking and to-do app with sync between Linux, macOS, Windows, and mobile

View File

@@ -20,6 +20,22 @@ const tasks = {
await utils.execCommandVerbose('git', ['push']);
},
},
build: {
fn: async () => {
// Building everything in parallel seems to be unreliable on CI as
// certain scripts randomly fail with missing files or folder, or
// cannot delete certain directories (eg. copyPluginAssets or
// copyApplicationAssets). Because of this, on CI, we run the build
// sequencially. Locally we run it in parallel, which is much
// faster, especially when having to rebuild after adding a
// dependency.
if (process.env.BUILD_SEQUENCIAL === '1') {
await utils.execCommandVerbose('yarn', ['run', 'buildSequential']);
} else {
await utils.execCommandVerbose('yarn', ['run', 'buildParallel']);
}
},
},
};
utils.registerGulpTasks(gulp, tasks);

View File

@@ -12,7 +12,8 @@
"node": ">=16"
},
"scripts": {
"build": "yarn workspaces foreach --verbose --interlaced --parallel run build && yarn run tsc",
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 run build && yarn run tsc",
"buildSequential": "yarn workspaces foreach --verbose --interlaced run build && yarn run tsc",
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
"buildCommandIndex": "gulp buildCommandIndex",
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../joplin-website/docs/api/references/plugin_api packages/lib/services/plugins/api/",
@@ -28,9 +29,9 @@
"linter-ci": "eslint --resolve-plugins-relative-to . --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",
"linter": "eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"postinstall": "yarn run build",
"publishAll": "git pull && yarn run build && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
"releaseAndroid": "yarn run build && export PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" && node packages/tools/release-android.js",
"postinstall": "gulp build",
"publishAll": "git pull && yarn run buildParallel && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
"releaseAndroid": "PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" node packages/tools/release-android.js",
"releaseAndroidClean": "node packages/tools/release-android.js",
"releaseCli": "node packages/tools/release-cli.js",
"releaseClipper": "node packages/tools/release-clipper.js",

View File

@@ -313,6 +313,10 @@ async function fetchAllNotes() {
lines.push('');
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
lines.push('');
lines.push('Or to **update** a resource:');
lines.push('');
lines.push('\tcurl -X PUT -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my modified title"}\' http://localhost:41184/resources/8fe1417d7b184324bf6b0122b76c4696');
lines.push('');
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
lines.push('');
lines.push('**From a plugin** the syntax to create a resource is also a bit special:');
@@ -368,6 +372,11 @@ async function fetchAllNotes() {
lines.push(`Sets the properties of the ${singular} with ID :id`);
lines.push('');
if (model.type === BaseModel.TYPE_RESOURCE) {
lines.push('You may also update the file data by specifying a file (See `POST /resources` example).');
lines.push('');
}
lines.push(`## DELETE /${tableName}/:id`);
lines.push('');
lines.push(`Deletes the ${singular} with ID :id`);

View File

@@ -0,0 +1,4 @@
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="">
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="" title="some-title">
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="some-alt-text">
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="some-alt-text" title="some-title">

View File

@@ -0,0 +1,4 @@
![](:/39e66095b2cd427e9f13464487514d2e)
![](:/39e66095b2cd427e9f13464487514d2e "some-title")
![some-alt-text](:/39e66095b2cd427e9f13464487514d2e)
![some-alt-text](:/39e66095b2cd427e9f13464487514d2e "some-title")

View File

@@ -19,7 +19,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Joplin Web Clipper</title>
</head>
<body>
<noscript>

View File

@@ -63,13 +63,15 @@ class AppComponent extends Component {
contentScriptLoaded: false,
selectedTags: [],
contentScriptError: '',
newNoteId: null,
});
this.confirm_click = () => {
this.confirm_click = async () => {
const content = Object.assign({}, this.props.clippedContent);
content.tags = this.state.selectedTags.join(',');
content.parent_id = this.props.selectedFolderId;
bridge().sendContentToJoplin(content);
const response = await bridge().sendContentToJoplin(content);
this.setState({ newNoteId: response.id });
};
this.contentTitle_change = (event) => {
@@ -402,6 +404,24 @@ class AppComponent extends Component {
);
};
const openNewNoteButton = () => {
if (!this.state.newNoteId) { return null; } else {
return (
// The jopin:// link must be opened in a new tab. When it's opened for the first time, a system dialog will ask for the user's permission.
// The system dialog is too big to fit into the popup so the user will not be able to see the dialog buttons and get stuck.
<a
className="Button"
href={`joplin://x-callback-url/openNote?id=${encodeURIComponent(this.state.newNoteId)}`}
target="_blank"
onClick={() => this.setState({ newNoteId: null })}
>
Open newly created note
</a>
);
}
};
const tagDataListOptions = [];
for (let i = 0; i < this.props.tags.length; i++) {
const tag = this.props.tags[i];
@@ -437,6 +457,7 @@ class AppComponent extends Component {
</div>
{ warningComponent }
{ previewComponent }
{ openNewNoteButton() }
{ clipperStatusComp() }
</div>
);

View File

@@ -463,9 +463,11 @@ class Bridge {
// This is the perfect Heisenbug - it happens always when opening the popup the first time EXCEPT
// when the debugger is open. Then everything is working fine and the bug NEVER EVER happens,
// so it's impossible to understand what's going on.
await this.clipperApiExec('POST', 'notes', { nounce: this.nounce_++ }, content);
const response = await this.clipperApiExec('POST', 'notes', { nounce: this.nounce_++ }, content);
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });
return response;
} catch (error) {
if (error.message === '{"error":"Duplicate Nounce"}') {
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });

View File

@@ -12,5 +12,5 @@ runForSharingCommands-*
runForTestingCommands-*
style.min.css
build/lib/
vendor/
vendor/*
!vendor/loadEmojiLib.js

View File

@@ -24,7 +24,6 @@ import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import appReducer, { createAppDefaultState } from './app.reducer';
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
import Folder from '@joplin/lib/models/Folder';
const fs = require('fs-extra');
import Tag from '@joplin/lib/models/Tag';
import { reg } from '@joplin/lib/registry';
const packageInfo = require('./packageInfo.js');
@@ -63,6 +62,7 @@ import ShareService from '@joplin/lib/services/share/ShareService';
import checkForUpdates from './checkForUpdates';
import { AppState } from './app.reducer';
import syncDebugLog from '@joplin/lib/services/synchronizer/syncDebugLog';
import eventManager from '@joplin/lib/eventManager';
// import { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';
const pluginClasses = [
@@ -234,23 +234,6 @@ class Application extends BaseApplication {
});
}
async loadCustomCss(filePath: string) {
let cssString = '';
if (await fs.pathExists(filePath)) {
try {
cssString = await fs.readFile(filePath, 'utf-8');
} catch (error) {
let msg = error.message ? error.message : '';
msg = `Could not load custom css from ${filePath}\n${msg}`;
error.message = msg;
throw error;
}
}
return cssString;
}
private async checkForLegacyTemplates() {
const templatesDir = `${Setting.value('profileDir')}/templates`;
if (await shim.fsDriver().exists(templatesDir)) {
@@ -463,8 +446,9 @@ class Application extends BaseApplication {
await this.checkForLegacyTemplates();
// Note: Auto-update currently doesn't work in Linux: it downloads the update
// but then doesn't install it on exit.
// Note: Auto-update is a misnomer in the code.
// The code below only checks, if a new version is available.
// We only allow Windows and macOS users to automatically check for updates
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
@@ -523,6 +507,12 @@ class Application extends BaseApplication {
ResourceEditWatcher.instance().initialize(reg.logger(), (action: any) => { this.store().dispatch(action); }, (path: string) => bridge().openItem(path));
// Forwards the local event to the global event manager, so that it can
// be picked up by the plugin manager.
ResourceEditWatcher.instance().on('resourceChange', (event: any) => {
eventManager.emit('resourceChange', event);
});
RevisionService.instance().runInBackground();
// Make it available to the console window - useful to call revisionService.collectRevisions()

View File

@@ -1,22 +1,25 @@
import ElectronAppWrapper from './ElectronAppWrapper';
import shim from '@joplin/lib/shim';
import { _, setLocale } from '@joplin/lib/locale';
import { BrowserWindow, nativeTheme, nativeImage } from 'electron';
const { dirname, toSystemSlashes } = require('@joplin/lib/path-utils');
const { BrowserWindow, nativeTheme } = require('electron');
interface LastSelectedPath {
file: string;
directory: string;
}
interface LastSelectedPaths {
[key: string]: LastSelectedPath;
interface OpenDialogOptions {
properties?: string[];
defaultPath?: string;
createDirectory?: boolean;
filters?: any[];
}
export class Bridge {
private electronWrapper_: ElectronAppWrapper;
private lastSelectedPaths_: LastSelectedPaths;
private lastSelectedPaths_: LastSelectedPath;
constructor(electronWrapper: ElectronAppWrapper) {
this.electronWrapper_ = electronWrapper;
@@ -159,16 +162,16 @@ export class Bridge {
return filePath;
}
async showOpenDialog(options: any = null) {
async showOpenDialog(options: OpenDialogOptions = null) {
const { dialog } = require('electron');
if (!options) options = {};
let fileType = 'file';
if (options.properties && options.properties.includes('openDirectory')) fileType = 'directory';
if (!('defaultPath' in options) && this.lastSelectedPaths_[fileType]) options.defaultPath = this.lastSelectedPaths_[fileType];
if (!('defaultPath' in options) && (this.lastSelectedPaths_ as any)[fileType]) options.defaultPath = (this.lastSelectedPaths_ as any)[fileType];
if (!('createDirectory' in options)) options.createDirectory = true;
const { filePaths } = await dialog.showOpenDialog(this.window(), options);
const { filePaths } = await dialog.showOpenDialog(this.window(), options as any);
if (filePaths && filePaths.length) {
this.lastSelectedPaths_[fileType] = dirname(filePaths[0]);
(this.lastSelectedPaths_ as any)[fileType] = dirname(filePaths[0]);
}
return filePaths;
}
@@ -282,6 +285,10 @@ export class Bridge {
app.exit();
}
public createImageFromPath(path: string) {
return nativeImage.createFromPath(path);
}
}
let bridge_: Bridge = null;

View File

@@ -201,11 +201,11 @@ export default async function checkForUpdates(inBackground: boolean, parentWindo
});
if (buttonIndex === 0) {
bridge().openExternal(release.downloadUrl ? release.downloadUrl : release.pageUrl);
void bridge().openExternal(release.downloadUrl ? release.downloadUrl : release.pageUrl);
} else if (buttonIndex === 1) {
await addSkippedVersion(release.version);
} else if (buttonIndex === 2) {
bridge().openExternal('https://joplinapp.org/changelog/');
void bridge().openExternal('https://joplinapp.org/changelog/');
}
}
}

View File

@@ -28,11 +28,11 @@ class ClipperConfigScreenComponent extends React.Component {
}
chromeButton_click() {
bridge().openExternal('https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek');
void bridge().openExternal('https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek');
}
firefoxButton_click() {
bridge().openExternal('https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/');
void bridge().openExternal('https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/');
}
copyToken_click() {
@@ -56,7 +56,7 @@ class ClipperConfigScreenComponent extends React.Component {
const containerStyle = Object.assign({}, theme.containerStyle, {
overflowY: 'scroll',
padding: theme.configScreenPadding,
// padding: theme.configScreenPadding,
backgroundColor: theme.backgroundColor3,
});

View File

@@ -155,11 +155,11 @@ export default function(props: Props) {
const onNameClick = useCallback(() => {
const manifest = item.manifest;
if (!manifest.homepage_url) return;
bridge().openExternal(manifest.homepage_url);
void bridge().openExternal(manifest.homepage_url);
}, [item]);
const onRecommendedClick = useCallback(() => {
bridge().openExternal('https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins');
void bridge().openExternal('https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins');
}, []);
// For plugins in dev mode things like enabling/disabling or

View File

@@ -198,7 +198,7 @@ export default function(props: Props) {
}, [pluginSettings, props.onChange]);
const onBrowsePlugins = useCallback(() => {
bridge().openExternal('https://github.com/joplin/plugins/blob/master/README.md#plugins');
void bridge().openExternal('https://github.com/joplin/plugins/blob/master/README.md#plugins');
}, []);
const onPluginSettingsChange = useCallback((event: OnPluginSettingChangeEvent) => {

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useState } from 'react';
import { useCallback, useState, useRef, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
import Dialog from '../Dialog';
@@ -8,25 +8,34 @@ import StyledInput from '../style/StyledInput';
import { IconSelector, ChangeEvent } from './IconSelector';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import Folder from '@joplin/lib/models/Folder';
import { FolderIcon } from '@joplin/lib/services/database/types';
import { FolderEntity, FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import Button from '../Button/Button';
import bridge from '../../services/bridge';
import shim from '@joplin/lib/shim';
import FolderIconBox from '../FolderIconBox';
interface Props {
themeId: number;
dispatch: Function;
folderId: string;
parentId: string;
}
export default function(props: Props) {
const [folderTitle, setFolderTitle] = useState('');
const [folderIcon, setFolderIcon] = useState<FolderIcon>();
const titleInputRef = useRef(null);
const isNew = !props.folderId;
useAsyncEffect(async (event: AsyncEffectEvent) => {
if (isNew) return;
const folder = await Folder.load(props.folderId);
if (event.cancelled) return;
setFolderTitle(folder.title);
setFolderIcon(Folder.unserializeIcon(folder.icon));
}, [props.folderId]);
}, [props.folderId, isNew]);
const onClose = useCallback(() => {
props.dispatch({
@@ -35,6 +44,14 @@ export default function(props: Props) {
});
}, [props.dispatch]);
useEffect(() => {
titleInputRef.current.focus();
setTimeout(() => {
titleInputRef.current.select();
}, 100);
}, []);
const onButtonRowClick = useCallback(async (event: ClickEvent) => {
if (event.buttonName === 'cancel') {
onClose();
@@ -42,15 +59,29 @@ export default function(props: Props) {
}
if (event.buttonName === 'ok') {
await Folder.save({
id: props.folderId,
const folder: FolderEntity = {
title: folderTitle,
icon: Folder.serializeIcon(folderIcon),
});
onClose();
};
if (!isNew) folder.id = props.folderId;
if (props.parentId) folder.parent_id = props.parentId;
try {
const savedFolder = await Folder.save(folder, { userSideValidation: true });
onClose();
props.dispatch({
type: 'FOLDER_SELECT',
id: savedFolder.id,
});
} catch (error) {
bridge().showErrorMessageBox(error.message);
}
return;
}
}, [onClose, folderTitle, folderIcon, props.folderId]);
}, [onClose, folderTitle, folderIcon, props.folderId, props.parentId]);
const onFolderTitleChange = useCallback((event: any) => {
setFolderTitle(event.target.value);
@@ -64,23 +95,54 @@ export default function(props: Props) {
setFolderIcon(null);
}, []);
const onBrowseClick = useCallback(async () => {
const filePaths = await bridge().showOpenDialog({
filters: [
{
name: _('Images'),
extensions: ['jpg', 'jpeg', 'png'],
},
],
});
if (filePaths.length !== 1) return;
const filePath = filePaths[0];
try {
const dataUrl = await shim.imageToDataUrl(filePath, 256);
setFolderIcon(icon => {
return {
...icon,
emoji: '',
name: '',
type: FolderIconType.DataUrl,
dataUrl,
};
});
} catch (error) {
await bridge().showErrorMessageBox(error.message);
}
}, []);
function renderForm() {
return (
<div>
<div className="form">
<div className="form-input-group">
<label>{_('Title')}</label>
<StyledInput type="text" value={folderTitle} onChange={onFolderTitleChange}/>
<StyledInput type="text" ref={titleInputRef} value={folderTitle} onChange={onFolderTitleChange}/>
</div>
<div className="form-input-group">
<label>{_('Icon')}</label>
<div className="icon-selector-row">
{ folderIcon && <div className="foldericon"><FolderIconBox folderIcon={folderIcon} /></div> }
<IconSelector
title={_('Select emoji...')}
icon={folderIcon}
onChange={onFolderIconChange}
/>
<Button ml={1} title={_('Clear')} onClick={onClearClick}/>
<Button ml={1} title={_('Select file...')} onClick={onBrowseClick}/>
{ folderIcon && <Button ml={1} title={_('Clear')} onClick={onClearClick}/> }
</div>
</div>
</div>
@@ -96,10 +158,12 @@ export default function(props: Props) {
);
}
const dialogTitle = isNew ? _('Create notebook') : _('Edit notebook');
function renderDialogWrapper() {
return (
<div className="dialog-root">
<DialogTitle title={_('Edit notebook')}/>
<DialogTitle title={dialogTitle}/>
{renderContent()}
<DialogButtonRow
themeId={props.themeId}

View File

@@ -3,7 +3,7 @@ import { useEffect, useState, useCallback, useRef } from 'react';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import { loadScript } from '../utils/loadScript';
import Button from '../Button/Button';
import { FolderIcon } from '@joplin/lib/services/database/types';
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import bridge from '../../services/bridge';
export interface ChangeEvent {
@@ -15,6 +15,7 @@ type ChangeHandler = (event: ChangeEvent)=> void;
interface Props {
onChange: ChangeHandler;
icon: FolderIcon | null;
title: string;
}
export const IconSelector = (props: Props) => {
@@ -62,7 +63,7 @@ export const IconSelector = (props: Props) => {
});
const onEmoji = (selection: FolderIcon) => {
props.onChange({ value: selection });
props.onChange({ value: { ...selection, type: FolderIconType.Emoji } });
};
p.on('emoji', onEmoji);
@@ -78,16 +79,25 @@ export const IconSelector = (props: Props) => {
picker.togglePicker(buttonRef.current);
}, [picker]);
const buttonText = props.icon ? props.icon.emoji : '...';
// const buttonText = props.icon ? props.icon.emoji : '...';
return (
<Button
disabled={!picker}
ref={buttonRef}
onClick={onClick}
title={buttonText}
isSquare={true}
fontSize={20}
title={props.title}
/>
);
// return (
// <Button
// disabled={!picker}
// ref={buttonRef}
// onClick={onClick}
// title={buttonText}
// isSquare={true}
// fontSize={20}
// />
// );
};

View File

@@ -1,4 +1,13 @@
.icon-selector-row {
display: flex;
flex-direction: row;
align-items: center;
}
.icon-selector-row > .foldericon {
margin-right: 5px;
display: flex;
border: 1px solid var(--joplin-divider-color);
padding: 5px;
background-color: var(--joplin-background-color);
}

View File

@@ -0,0 +1,17 @@
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
interface Props {
folderIcon: FolderIcon;
}
export default function(props: Props) {
const folderIcon = props.folderIcon;
if (folderIcon.type === FolderIconType.Emoji) {
return <span style={{ fontSize: 20 }}>{folderIcon.emoji}</span>;
} else if (folderIcon.type === FolderIconType.DataUrl) {
return <img style={{ width: 20, height: 20 }} src={folderIcon.dataUrl} />;
} else {
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
}
}

View File

@@ -5,7 +5,7 @@ export default function styles(themeId: number) {
return {
container: {
...theme.containerStyle,
padding: theme.configScreenPadding,
// padding: theme.configScreenPadding,
backgroundColor: theme.backgroundColor3,
},
actionsContainer: {

View File

@@ -1,7 +1,6 @@
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
import CommandService, { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import Folder from '@joplin/lib/models/Folder';
const bridge = require('@electron/remote').require('./bridge').default;
import { Options } from './openFolderDialog';
export const declaration: CommandDeclaration = {
name: 'newFolder',
@@ -9,35 +8,15 @@ export const declaration: CommandDeclaration = {
iconName: 'fa-book',
};
export const runtime = (comp: any): CommandRuntime => {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, parentId: string = null) => {
comp.setState({
promptOptions: {
label: _('Notebook title:'),
onClose: async (answer: string) => {
if (answer) {
let folder = null;
try {
const toSave: any = { title: answer };
if (parentId) toSave.parent_id = parentId;
folder = await Folder.save(toSave, { userSideValidation: true });
} catch (error) {
bridge().showErrorMessageBox(error.message);
}
const options: Options = {
isNew: true,
parentId: parentId,
};
if (folder) {
comp.props.dispatch({
type: 'FOLDER_SELECT',
id: folder.id,
});
}
}
comp.setState({ promptOptions: null });
},
},
});
void CommandService.instance().execute('openFolderDialog', options);
},
};
};

View File

@@ -1,6 +1,12 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
export interface Options {
isNew?: boolean;
folderId?: string;
parentId?: string;
}
export const declaration: CommandDeclaration = {
name: 'openFolderDialog',
label: () => _('Edit'),
@@ -8,13 +14,22 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, folderId: string) => {
execute: async (context: CommandContext, options: Options = null) => {
options = {
isNew: false,
...options,
};
if (options.isNew && !('parentId' in options)) throw new Error('parentId mst be specified when creating a new folder');
if (!options.isNew && !('folderId' in options)) throw new Error('folderId property is required');
context.dispatch({
type: 'DIALOG_OPEN',
name: 'editFolder',
isOpen: true,
props: {
folderId,
folderId: options.folderId,
parentId: options.parentId,
},
});
},

View File

@@ -27,9 +27,9 @@ export const runtime = (): CommandRuntime => {
// but doesn't on macOS, so we need to convert it to a path
// before passing it to openPath.
const decodedPath = fileUriToPath(urlDecode(link), shim.platformName());
require('electron').shell.openPath(decodedPath);
void require('electron').shell.openPath(decodedPath);
} else {
require('electron').shell.openExternal(link);
void require('electron').shell.openExternal(link);
}
} else {
bridge().showErrorMessageBox(_('Unsupported link or message: %s', link));

View File

@@ -19,7 +19,7 @@ export const runtime = (): CommandRuntime => {
useSpellChecker = useSpellChecker === null ? context.state.settings['spellChecker.enabled'] : useSpellChecker;
const menuItems = SpellCheckerService.instance().spellCheckerConfigMenuItems(selectedLanguage, useSpellChecker);
const menu = Menu.buildFromTemplate(menuItems);
const menu = Menu.buildFromTemplate(menuItems as any);
menu.popup(bridge().window());
},

View File

@@ -89,6 +89,7 @@ interface Props {
['spellChecker.language']: string;
plugins: PluginStates;
customCss: string;
locale: string;
}
const commandNames: string[] = menuCommandNames();
@@ -249,7 +250,11 @@ function useMenu(props: Props) {
const keymapService = KeymapService.instance();
const pluginCommandNames = props.pluginMenuItems.map((view: any) => view.commandName);
const menuItemDic = menuUtils.commandsToMenuItems(commandNames.concat(pluginCommandNames), (commandName: string) => onMenuItemClickRef.current(commandName));
const menuItemDic = menuUtils.commandsToMenuItems(
commandNames.concat(pluginCommandNames),
(commandName: string) => onMenuItemClickRef.current(commandName),
props.locale
);
const quitMenuItem = {
label: _('Quit'),
@@ -342,6 +347,12 @@ function useMenu(props: Props) {
}
}
importItems.push({ type: 'separator' });
importItems.push({
label: _('Other applications...'),
click: () => { void bridge().openExternal('https://discourse.joplinapp.org/t/importing-notes-from-other-notebook-applications/22425'); },
});
exportItems.push(
menuItemDic.exportPdf
);
@@ -561,8 +572,15 @@ function useMenu(props: Props) {
menuItemDic.textPaste,
menuItemDic.textSelectAll,
separator(),
menuItemDic['editor.undo'],
menuItemDic['editor.redo'],
// Using the generic "undo"/"redo" roles mean the menu
// item will work in every text fields, whether it's the
// editor or a regular text field.
{
role: 'undo',
},
{
role: 'redo',
},
separator(),
menuItemDic.textBold,
menuItemDic.textItalic,
@@ -697,13 +715,13 @@ function useMenu(props: Props) {
submenu: [{
label: _('Website and documentation'),
accelerator: keymapService.getAccelerator('help'),
click() { bridge().openExternal('https://joplinapp.org'); },
click() { void bridge().openExternal('https://joplinapp.org'); },
}, {
label: _('Joplin Forum'),
click() { bridge().openExternal('https://discourse.joplinapp.org'); },
click() { void bridge().openExternal('https://discourse.joplinapp.org'); },
}, {
label: _('Make a donation'),
click() { bridge().openExternal('https://joplinapp.org/donate/'); },
click() { void bridge().openExternal('https://joplinapp.org/donate/'); },
}, {
label: _('Check for updates...'),
visible: shim.isMac() ? false : true,
@@ -816,7 +834,7 @@ function useMenu(props: Props) {
menuItemDic.textCut,
menuItemDic.textPaste,
menuItemDic.textSelectAll,
],
] as any,
},
]));
} else {
@@ -830,7 +848,7 @@ function useMenu(props: Props) {
clearTimeout(timeoutId);
timeoutId = null;
};
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled'], props.plugins, props.customCss]);
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled'], props.plugins, props.customCss, props.locale]);
useMenuStates(menu, props);
@@ -872,6 +890,7 @@ const mapStateToProps = (state: AppState) => {
return {
menuItemProps: menuUtils.commandsToMenuItemProps(commandNames.concat(pluginCommandNames(state.pluginService.plugins)), whenClauseContext),
locale: state.settings.locale,
routeName: state.route.routeName,
selectedFolderId: state.selectedFolderId,
layoutButtonSequence: state.settings.layoutButtonSequence,

View File

@@ -67,7 +67,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
usePluginServiceRegistration(ref);
const { resetScroll, editor_scroll, setEditorPercentScroll, setViewerPercentScroll, editor_resize,
const { resetScroll, editor_scroll, setEditorPercentScroll, setViewerPercentScroll, editor_resize, editor_update, getLineScrollPercent,
} = useScrollHandler(editorRef, webviewRef, props.onScroll);
const codeMirror_change = useCallback((newBody: string) => {
@@ -502,10 +502,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
padding-left: .2em;
}
div.CodeMirror span.cm-strong {
color: ${theme.colorBright};
}
div.CodeMirror span.cm-hr {
color: ${theme.dividerColor};
}
@@ -519,8 +515,9 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
color: ${theme.searchMarkerColor} !important;
}
/* We need !important because the search marker is overridden by CodeMirror's own text selection marker */
.cm-search-marker-selected {
background: ${theme.selectedColor2};
background: ${theme.selectedColor2} !important;
color: ${theme.color2} !important;
}
@@ -580,9 +577,14 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const arg0 = args && args.length >= 1 ? args[0] : null;
if (msg.indexOf('checkboxclick:') === 0) {
const newBody = shared.toggleCheckbox(msg, props.content);
const { line, from, to } = shared.toggleCheckboxRange(msg, props.content);
if (editorRef.current) {
editorRef.current.updateBody(newBody);
// To cancel CodeMirror's layout drift, the scroll position
// is recorded before updated, and then it is restored.
// Ref. https://github.com/laurent22/joplin/issues/5890
const percent = getLineScrollPercent();
editorRef.current.replaceRange(line, from, to);
setEditorPercentScroll(percent);
}
} else if (msg === 'percentScroll') {
const percent = arg0;
@@ -730,15 +732,19 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
// It might be buggy, refer to the below issue
// https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703
useEffect(() => {
function pointerInsideEditor(x: number, y: number) {
function pointerInsideEditor(params: any) {
const x = params.x, y = params.y, isEditable = params.isEditable, inputFieldType = params.inputFieldType;
const elements = document.getElementsByClassName('codeMirrorEditor');
if (!elements.length) return null;
// inputFieldType: The input field type of CodeMirror is "textarea" so the inputFieldType = "none",
// and any single-line input above codeMirror has inputFieldType value according to the type of input e.g.(text = plainText, password = password, ...).
if (!elements.length || !isEditable || inputFieldType !== 'none') return null;
const rect = convertToScreenCoordinates(Setting.value('windowContentZoomFactor'), elements[0].getBoundingClientRect());
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y;
}
async function onContextMenu(_event: any, params: any) {
if (!pointerInsideEditor(params.x, params.y)) return;
if (!pointerInsideEditor(params)) return;
const menu = new Menu();
@@ -842,6 +848,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
onEditorPaste={onEditorPaste}
isSafeMode={props.isSafeMode}
onResize={editor_resize}
onUpdate={editor_update}
/>
</div>
);

View File

@@ -94,6 +94,7 @@ export interface EditorProps {
onEditorPaste: any;
isSafeMode: boolean;
onResize: any;
onUpdate: any;
}
function Editor(props: EditorProps, ref: any) {
@@ -148,6 +149,14 @@ function Editor(props: EditorProps, ref: any) {
event.dataTransfer.dropEffect = 'copy';
}, []);
const editor_resize = useCallback((cm: any) => {
props.onResize(cm);
}, [props.onResize]);
const editor_update = useCallback((cm: any) => {
props.onUpdate(cm);
}, [props.onUpdate]);
useEffect(() => {
if (!editorParent.current) return () => {};
@@ -190,7 +199,8 @@ function Editor(props: EditorProps, ref: any) {
cm.on('paste', editor_paste);
cm.on('drop', editor_drop);
cm.on('dragover', editor_drag);
cm.on('refresh', props.onResize);
cm.on('refresh', editor_resize);
cm.on('update', editor_update);
// It's possible for searchMarkers to be available before the editor
// In these cases we set the markers asap so the user can see them as
@@ -204,7 +214,8 @@ function Editor(props: EditorProps, ref: any) {
cm.off('paste', editor_paste);
cm.off('drop', editor_drop);
cm.off('dragover', editor_drag);
cm.off('refresh', props.onResize);
cm.off('refresh', editor_resize);
cm.off('update', editor_update);
editorParent.current.removeChild(cm.getWrapperElement());
setEditor(null);
};

View File

@@ -8,6 +8,7 @@ export default function useEditorSearch(CodeMirror: any) {
const [scrollbarMarks, setScrollbarMarks] = useState(null);
const [previousKeywordValue, setPreviousKeywordValue] = useState(null);
const [previousIndex, setPreviousIndex] = useState(null);
const [previousSearchTimestamp, setPreviousSearchTimestamp] = useState(0);
const [overlayTimeout, setOverlayTimeout] = useState(null);
const overlayTimeoutRef = useRef(null);
overlayTimeoutRef.current = overlayTimeout;
@@ -51,7 +52,7 @@ export default function useEditorSearch(CodeMirror: any) {
// Highlights the currently active found work
// It's possible to get tricky with this fucntions and just use findNext/findPrev
// but this is fast enough and works more naturally with the current search logic
function highlightSearch(cm: any, searchTerm: RegExp, index: number, scrollTo: boolean) {
function highlightSearch(cm: any, searchTerm: RegExp, index: number, scrollTo: boolean, withSelection: boolean) {
const cursor = cm.getSearchCursor(searchTerm);
let match: any = null;
@@ -64,7 +65,13 @@ export default function useEditorSearch(CodeMirror: any) {
}
if (match) {
if (scrollTo) cm.scrollIntoView(match);
if (scrollTo) {
if (withSelection) {
cm.setSelection(match.from, match.to);
} else {
cm.scrollTo(match);
}
}
return cm.markText(match.from, match.to, { className: 'cm-search-marker-selected' });
}
@@ -90,7 +97,7 @@ export default function useEditorSearch(CodeMirror: any) {
CodeMirror.defineExtension('setMarkers', function(keywords: any, options: any) {
if (!options) {
options = { selectedIndex: 0 };
options = { selectedIndex: 0, searchTimestamp: 0 };
}
clearMarkers();
@@ -107,14 +114,15 @@ export default function useEditorSearch(CodeMirror: any) {
const searchTerm = getSearchTerm(keyword);
// We only want to scroll the first keyword into view in the case of a multi keyword search
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex);
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo);
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
if (match) marks.push(match);
}
setMarkers(marks);
setPreviousIndex(options.selectedIndex);
setPreviousSearchTimestamp(options.searchTimestamp);
// SEARCHOVERLAY
// We only want to highlight all matches when there is only 1 search term

View File

@@ -7,6 +7,10 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
const ignoreNextEditorScrollTime_ = useRef(Date.now());
const ignoreNextEditorScrollEventCount_ = useRef(0);
const delayedSetEditorPercentScrollTimeoutID_ = useRef(null);
const scrollTopIsUncertain_ = useRef(true);
const lastResizeHeight_ = useRef(NaN);
const lastLinesHeight_ = useRef(NaN);
const restoreEditorPercentScrollTimeoutId_ = useRef<any>(null);
// Ignores one next scroll event for a short time.
const ignoreNextEditorScrollEvent = () => {
@@ -54,23 +58,33 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
if (isCodeMirrorReady(cm)) {
// calculates editor's GUI-dependent pixel-based raw percent
const newEditorPercent = translateScrollPercentL2E(cm, scrollPercent_.current);
const oldEditorPercent = cm.getScrollPercent();
const oldEditorPercent = scrollTopIsUncertain_.current ? NaN : cm.getScrollPercent();
if (!(Math.abs(newEditorPercent - oldEditorPercent) < 1e-8)) {
ignoreNextEditorScrollEvent();
cm.setScrollPercent(newEditorPercent);
}
scrollTopIsUncertain_.current = false;
} else {
retry += 1;
if (retry <= 10) {
if (retry <= 3) {
delayedSetEditorPercentScrollTimeoutID_.current = shim.setTimeout(fn, 50);
}
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
};
fn();
};
const restoreEditorPercentScroll = () => {
if (isCodeMirrorReady(editorRef.current)) {
if (restoreEditorPercentScrollTimeoutId_.current) {
shim.clearTimeout(restoreEditorPercentScrollTimeoutId_.current);
restoreEditorPercentScrollTimeoutId_.current = null;
}
const cm = editorRef.current;
if (isCodeMirrorReady(cm)) {
lastLinesHeight_.current = cm.heightAtLine(cm.lineCount()) - cm.heightAtLine(0);
setEditorPercentScrollInternal(scrollPercent_.current);
}
};
@@ -90,22 +104,27 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
}, [scheduleOnScroll]);
const editor_scroll = useCallback(() => {
if (isNextEditorScrollEventIgnored()) return;
const ignored = isNextEditorScrollEventIgnored();
const cm = editorRef.current;
if (isCodeMirrorReady(cm)) {
if (scrollTopIsUncertain_.current) return;
const editorPercent = Math.max(0, Math.min(1, cm.getScrollPercent()));
if (!isNaN(editorPercent)) {
// when switching to another note, the percent can sometimes be NaN
// this is coming from `gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.ts`
// when CodeMirror returns scroll info with heigth == clientHeigth
// https://github.com/laurent22/joplin/issues/4797
// calculates GUI-independent line-based percent
const percent = translateScrollPercentE2L(cm, editorPercent);
scrollPercent_.current = percent;
setViewerPercentScroll(percent);
if (!ignored) {
// calculates GUI-independent line-based percent
const percent = translateScrollPercentE2L(cm, editorPercent);
scrollPercent_.current = percent;
setViewerPercentScroll(percent);
}
}
} else {
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
}, [setViewerPercentScroll]);
@@ -113,17 +132,60 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
scrollPercent_.current = 0;
if (editorRef.current) {
editorRef.current.setScrollPercent(0);
scrollTopIsUncertain_.current = false;
}
}, []);
const editor_resize = useCallback((cm) => {
if (cm) {
restoreEditorPercentScroll();
if (isCodeMirrorReady(cm)) {
// This handler is called when resized and refreshed.
// Only when resized, the scroll position is restored.
const info = cm.getScrollInfo();
const height = info.height - info.clientHeight;
if (height !== lastResizeHeight_.current) {
// When resized, restoring is performed immediately.
restoreEditorPercentScroll();
lastResizeHeight_.current = height;
}
} else {
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
}, []);
// When heights of lines are updated in CodeMirror, 'update' events are raised.
// If such an update event is raised, scroll position should be restored.
// See https://github.com/laurent22/joplin/issues/5981
const editor_update = useCallback((cm) => {
if (isCodeMirrorReady(cm)) {
const linesHeight = cm.heightAtLine(cm.lineCount()) - cm.heightAtLine(0);
if (lastLinesHeight_.current !== linesHeight) {
// To avoid cancelling intentional scroll position changes,
// restoring is performed in a timeout handler.
if (!restoreEditorPercentScrollTimeoutId_.current) {
restoreEditorPercentScrollTimeoutId_.current = shim.setTimeout(restoreEditorPercentScroll, 10);
}
}
} else {
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
}, []);
const getLineScrollPercent = useCallback(() => {
const cm = editorRef.current;
if (isCodeMirrorReady(cm)) {
const ePercent = cm.getScrollPercent();
return translateScrollPercentE2L(cm, ePercent);
} else {
return scrollPercent_.current;
}
}, []);
return {
resetScroll, setEditorPercentScroll, setViewerPercentScroll, editor_scroll, editor_resize,
resetScroll, setEditorPercentScroll, setViewerPercentScroll, editor_scroll, editor_resize, editor_update, getLineScrollPercent,
};
}

View File

@@ -3,7 +3,8 @@ import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
import { useEffect } from 'react';
import bridge from '../../../../../services/bridge';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenu';
import { ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenuUtils';
import { menuItems } from '../../../utils/contextMenu';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
import CommandService from '@joplin/lib/services/CommandService';
import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordinates';
@@ -67,6 +68,8 @@ export default function(editor: any, plugins: PluginStates, dispatch: Function)
contextMenuActionOptions.current = {
itemType,
resourceId,
filename: null,
mime: null,
linkToCopy,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',

View File

@@ -93,11 +93,11 @@ const declarations: CommandDeclaration[] = [
},
{
name: 'editor.undo',
label: () => _('Undo'),
label: () => _('Editor: %s', _('Undo')),
},
{
name: 'editor.redo',
label: () => _('Redo'),
label: () => _('Editor: %s', _('Redo')),
},
{
name: 'editor.indentLess',

View File

@@ -0,0 +1,41 @@
/** @jest-environment ./loadResources.testEnv */
// eslint-disable-next-line strict, lines-around-directive
'use strict';
// use strict is necessary here so that typescript doesn't place "use strict" above the jest docblock
// https://github.com/microsoft/TypeScript/issues/15819#issuecomment-782235619
import { textToDataUri, svgUriToPng } from './contextMenuUtils';
jest.mock('@joplin/lib/models/Resource');
describe('contextMenu', () => {
it('should provide proper copy path', async () => {
const testCase = [
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">test</svg>',
'image/svg+xml',
];
const expectedText = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIj50ZXN0PC9zdmc+';
expect(textToDataUri(testCase[0], testCase[1])).toBe(expectedText);
});
it('should convert to png binary', async () => {
const testCase = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiIGhlaWdodD0iMTAwcHgiIHdpZHRoPSIxMDBweCI+CjxnPgoJPHBhdGggZD0iTTI4LjEsMzYuNmM0LjYsMS45LDEyLjIsMS42LDIwLjksMS4xYzguOS0wLjQsMTktMC45LDI4LjksMC45YzYuMywxLjIsMTEuOSwzLjEsMTYuOCw2Yy0xLjUtMTIuMi03LjktMjMuNy0xOC42LTMxLjMgICBjLTQuOS0wLjItOS45LDAuMy0xNC44LDEuNEM0Ny44LDE3LjksMzYuMiwyNS42LDI4LjEsMzYuNnoiLz4KCTxwYXRoIGQ9Ik03MC4zLDkuOEM1Ny41LDMuNCw0Mi44LDMuNiwzMC41LDkuNWMtMyw2LTguNCwxOS42LTUuMywyNC45YzguNi0xMS43LDIwLjktMTkuOCwzNS4yLTIzLjFDNjMuNywxMC41LDY3LDEwLDcwLjMsOS44eiIvPgoJPHBhdGggZD0iTTE2LjUsNTEuM2MwLjYtMS43LDEuMi0zLjQsMi01LjFjLTMuOC0zLjQtNy41LTctMTEtMTAuOGMtMi4xLDYuMS0yLjgsMTIuNS0yLjMsMTguN0M5LjYsNTEuMSwxMy40LDUwLjIsMTYuNSw1MS4zeiIvPgoJPHBhdGggZD0iTTksMzEuNmMzLjUsMy45LDcuMiw3LjYsMTEuMSwxMS4xYzAuOC0xLjYsMS43LTMuMSwyLjYtNC42YzAuMS0wLjIsMC4zLTAuNCwwLjQtMC42Yy0yLjktMy4zLTMuMS05LjItMC42LTE3LjYgICBjMC44LTIuNywxLjgtNS4zLDIuNy03LjRjLTUuMiwzLjQtOS44LDgtMTMuMywxMy43QzEwLjgsMjcuOSw5LjgsMjkuNyw5LDMxLjZ6Ii8+Cgk8cGF0aCBkPSJNMTUuNCw1NC43Yy0yLjYtMS02LjEsMC43LTkuNywzLjRjMS4yLDYuNiwzLjksMTMsOCwxOC41QzEzLDY5LjMsMTMuNSw2MS44LDE1LjQsNTQuN3oiLz4KCTxwYXRoIGQ9Ik0zOS44LDU3LjZDNTQuMyw2Ni43LDcwLDczLDg2LjUsNzYuNGMwLjYtMC44LDEuMS0xLjYsMS43LTIuNWM0LjgtNy43LDctMTYuMyw2LjgtMjQuOGMtMTMuOC05LjMtMzEuMy04LjQtNDUuOC03LjcgICBjLTkuNSwwLjUtMTcuOCwwLjktMjMuMi0xLjdjLTAuMSwwLjEtMC4yLDAuMy0wLjMsMC40Yy0xLDEuNy0yLDMuNC0yLjksNS4xQzI4LjIsNDkuNywzMy44LDUzLjksMzkuOCw1Ny42eiIvPgoJPHBhdGggZD0iTTI2LjIsODguMmMzLjMsMiw2LjcsMy42LDEwLjIsNC43Yy0zLjUtNi4yLTYuMy0xMi42LTguOC0xOC41Yy0zLjEtNy4yLTUuOC0xMy41LTktMTcuMmMtMS45LDgtMiwxNi40LTAuMywyNC43ICAgQzIwLjYsODQuMiwyMy4yLDg2LjMsMjYuMiw4OC4yeiIvPgoJPHBhdGggZD0iTTMwLjksNzNjMi45LDYuOCw2LjEsMTQuNCwxMC41LDIxLjJjMTUuNiwzLDMyLTIuMyw0Mi42LTE0LjZDNjcuNyw3Niw1Mi4yLDY5LjYsMzcuOSw2MC43QzMyLDU3LDI2LjUsNTMsMjEuMyw0OC42ICAgYy0wLjYsMS41LTEuMiwzLTEuNyw0LjZDMjQuMSw1Ny4xLDI3LjMsNjQuNSwzMC45LDczeiIvPgo8L2c+Cjwvc3ZnPg==';
const png = await svgUriToPng(document, testCase);
expect(png).toBeInstanceOf(Uint8Array);
});
it('should throw error on invalid svg uri', async () => {
// We are mocking console.error since jsdom throws errors to console when we try to load an invalid img
// https://github.com/facebook/jest/pull/5267#issuecomment-356605468
const consoleError = console.error;
console.error = jest.fn();
const testCases: Array<string> = [
'data:image/svg+xml;base64,error',
'invalid',
];
for (const testCase of testCases) {
await expect(svgUriToPng(document, testCase)).rejects.toBeInstanceOf(Error);
}
console.error = consoleError;
});
});

View File

@@ -1,8 +1,8 @@
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
const bridge = require('@electron/remote').require('./bridge').default;
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import Resource from '@joplin/lib/models/Resource';
@@ -11,43 +11,10 @@ import BaseModel from '@joplin/lib/BaseModel';
import { processPastedHtml } from './resourceHandling';
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
const { toSystemSlashes } = require('@joplin/lib/path-utils');
export enum ContextMenuItemType {
None = '',
Image = 'image',
Resource = 'resource',
Text = 'text',
Link = 'link',
}
export interface ContextMenuOptions {
itemType: ContextMenuItemType;
resourceId: string;
linkToCopy: string;
textToCopy: string;
htmlToCopy: string;
insertContent: Function;
isReadOnly?: boolean;
}
interface ContextMenuItem {
label: string;
onAction: Function;
isActive: Function;
}
interface ContextMenuItems {
[key: string]: ContextMenuItem;
}
async function resourceInfo(options: ContextMenuOptions): Promise<any> {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const resourcePath = resource ? Resource.fullPath(resource) : '';
return { resource, resourcePath };
}
function handleCopyToClipboard(options: ContextMenuOptions) {
if (options.textToCopy) {
clipboard.writeText(options.textToCopy);
@@ -56,6 +23,12 @@ function handleCopyToClipboard(options: ContextMenuOptions) {
}
}
async function saveFileData(data: any, filename: string) {
const newFilePath = await bridge().showSaveDialog({ defaultPath: filename });
if (!newFilePath) return;
await writeFile(newFilePath, data);
}
export async function openItemById(itemId: string, dispatch: Function, hash: string = '') {
const item = await BaseItem.loadItemById(itemId);
@@ -101,7 +74,7 @@ export function menuItems(dispatch: Function): ContextMenuItems {
onAction: async (options: ContextMenuOptions) => {
await openItemById(options.resourceId, dispatch);
},
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
},
saveAs: {
label: _('Save as...'),
@@ -113,7 +86,32 @@ export function menuItems(dispatch: Function): ContextMenuItems {
if (!filePath) return;
await fs.copy(resourcePath, filePath);
},
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
// We handle images received as text seperately as it can be saved in multiple formats
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
},
saveAsSvg: {
label: _('Save as SVG'),
onAction: async (options: ContextMenuOptions) => {
await saveFileData(options.textToCopy, options.filename);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
},
saveAsPng: {
label: _('Save as PNG'),
onAction: async (options: ContextMenuOptions) => {
// First convert it to png then save
if (options.mime != 'image/svg+xml') {
throw new Error(`Unsupported image type: ${options.mime}`);
}
if (!options.filename) {
throw new Error('Filename is needed to save as png');
}
const dataUri = textToDataUri(options.textToCopy, options.mime);
const png = await svgUriToPng(document, dataUri);
const filename = options.filename.replace('.svg', '.png');
await saveFileData(png, filename);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
},
revealInFolder: {
label: _('Reveal file in folder'),
@@ -121,30 +119,46 @@ export function menuItems(dispatch: Function): ContextMenuItems {
const { resourcePath } = await resourceInfo(options);
bridge().showItemInFolder(resourcePath);
},
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
},
copyPathToClipboard: {
label: _('Copy path to clipboard'),
onAction: async (options: ContextMenuOptions) => {
const { resourcePath } = await resourceInfo(options);
clipboard.writeText(toSystemSlashes(resourcePath));
let path = '';
if (options.textToCopy && options.mime) {
path = textToDataUri(options.textToCopy, options.mime);
} else {
const { resourcePath } = await resourceInfo(options);
if (resourcePath) path = toSystemSlashes(resourcePath);
}
if (!path) return;
clipboard.writeText(path);
},
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
},
copyImage: {
label: _('Copy image'),
onAction: async (options: ContextMenuOptions) => {
const { resourcePath } = await resourceInfo(options);
const image = bridge().createImageFromPath(resourcePath);
clipboard.writeImage(image);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image,
},
cut: {
label: _('Cut'),
onAction: async (options: ContextMenuOptions) => {
handleCopyToClipboard(options);
options.insertContent('');
},
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!options.textToCopy || !!options.htmlToCopy),
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType != ContextMenuItemType.Image && (!options.isReadOnly && (!!options.textToCopy || !!options.htmlToCopy)),
},
copy: {
label: _('Copy'),
onAction: async (options: ContextMenuOptions) => {
handleCopyToClipboard(options);
},
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy || !!options.htmlToCopy,
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType != ContextMenuItemType.Image && (!!options.textToCopy || !!options.htmlToCopy),
},
paste: {
label: _('Paste'),
@@ -176,7 +190,6 @@ export default async function contextMenu(options: ContextMenuOptions, dispatch:
const items = menuItems(dispatch);
if (!('readyOnly' in options)) options.isReadOnly = true;
for (const itemKey in items) {
const item = items[itemKey];

View File

@@ -0,0 +1,92 @@
import Resource from '@joplin/lib/models/Resource';
export enum ContextMenuItemType {
None = '',
Image = 'image',
Resource = 'resource',
Text = 'text',
Link = 'link',
}
export interface ContextMenuOptions {
itemType: ContextMenuItemType;
resourceId: string;
mime: string;
filename: string;
linkToCopy: string;
textToCopy: string;
htmlToCopy: string;
insertContent: Function;
isReadOnly?: boolean;
}
export interface ContextMenuItem {
label: string;
onAction: Function;
isActive: Function;
}
export interface ContextMenuItems {
[key: string]: ContextMenuItem;
}
export async function resourceInfo(options: ContextMenuOptions): Promise<any> {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const filePath = resource ? Resource.fullPath(resource) : null;
const filename = resource ? (resource.filename ? resource.filename : resource.title) : options.filename ? options.filename : '';
return { resource, filePath, filename };
}
export function textToDataUri(text: string, mime: string): string {
return `data:${mime};base64,${Buffer.from(text).toString('base64')}`;
}
export const svgUriToPng = (document: Document, svg: string) => {
return new Promise<Uint8Array>((resolve, reject) => {
let canvas: HTMLCanvasElement;
let img: HTMLImageElement;
const cleanUpAndReject = (e: Error) => {
if (canvas) canvas.remove();
if (img) img.remove();
return reject(e);
};
try {
img = document.createElement('img');
if (!img) throw new Error('Failed to create img element');
} catch (e) {
return cleanUpAndReject(e);
}
img.onload = function() {
try {
canvas = document.createElement('canvas');
if (!canvas) throw new Error('Failed to create canvas element');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get context');
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
const pngUri = canvas.toDataURL('image/png');
if (!pngUri) throw new Error('Failed to generate png uri');
const pngBase64 = pngUri.split(',')[1];
const byteString = atob(pngBase64);
// write the bytes of the string to a typed array
const buff = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
buff[i] = byteString.charCodeAt(i);
}
canvas.remove();
img.remove();
resolve(buff);
} catch (err) {
cleanUpAndReject(err);
}
};
img.onerror = function(e) {
cleanUpAndReject(new Error(e.toString()));
};
img.src = svg;
});
};

View File

@@ -35,6 +35,8 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
const menu = await contextMenu({
itemType: arg0 && arg0.type,
resourceId: arg0.resourceId,
filename: arg0.filename,
mime: arg0.mime,
textToCopy: arg0.textToCopy,
linkToCopy: arg0.linkToCopy || null,
htmlToCopy: '',

View File

@@ -1,6 +1,7 @@
import { useState, useCallback } from 'react';
import Logger from '@joplin/lib/Logger';
import { SearchMarkers } from './useSearchMarkers';
const CommandService = require('@joplin/lib/services/CommandService').default;
const logger = Logger.create('useNoteSearchBar');
@@ -70,6 +71,7 @@ export default function useNoteSearchBar() {
const onClose = useCallback(() => {
setShowLocalSearch(false);
setLocalSearch(defaultLocalSearch());
void CommandService.instance().execute('focusElementNoteBody');
}, []);
const setResultCount = useCallback((count: number) => {
@@ -90,6 +92,7 @@ export default function useNoteSearchBar() {
selectedIndex: localSearch.selectedIndex,
separateWordSearch: false,
searchTimestamp: localSearch.timestamp,
withSelection: true,
},
keywords: [
{

View File

@@ -4,6 +4,7 @@ interface SearchMarkersOptions {
searchTimestamp: number;
selectedIndex: number;
separateWordSearch: boolean;
withSelection?: boolean;
}
export interface SearchMarkers {

View File

@@ -56,7 +56,15 @@ function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, s
});
}
},
enabledCondition: '!modalDialogVisible && markdownEditorPaneVisible && oneNoteSelected && noteIsMarkdown',
// We disable the editor commands whenever a modal dialog is visible,
// otherwise the user might type something in a dialog and accidentally
// change something in the editor. However, we still enable them for
// GotoAnything so that it's possible to type eg `textBold` and bold the
// currently selected text.
//
// https://github.com/laurent22/joplin/issues/5707
enabledCondition: '(!modalDialogVisible || gotoAnythingVisible) && markdownEditorPaneVisible && oneNoteSelected && noteIsMarkdown',
};
}

View File

@@ -167,8 +167,8 @@ class NoteSearchBarComponent extends React.Component {
type="text"
style={{ width: 200, marginRight: 5, backgroundColor: this.backgroundColor, color: theme.color }}
/>
{allowScrolling ? nextButton : null}
{allowScrolling ? previousButton : null}
{allowScrolling ? nextButton : null}
{allowScrolling ? matchesFoundString : null}
{!allowScrolling ? viewerWarning : null}
</div>

View File

@@ -56,7 +56,7 @@ class PromptDialog extends React.Component {
top: 0,
left: 0,
width: width,
height: height - paddingTop,
height: height,
backgroundColor: 'rgba(0,0,0,0.6)',
display: visible ? 'flex' : 'none',
alignItems: 'flex-start',

View File

@@ -15,6 +15,7 @@ export const Root = styled.div`
position: relative;
display: flex;
width: 100%;
min-width: 30px;
`;
interface Props {
@@ -138,6 +139,21 @@ function SearchBar(props: Props) {
}
}, [props.notesParentType, onExitSearch]);
// When the searchbar is remounted, exit the search if it was previously open
// or else other buttons stay hidden (e.g. when opening Layout Editor and closing it)
// https://github.com/laurent22/joplin/issues/5953
useEffect(() => {
if (props.notesParentType === 'Search' || props.isFocused) {
if (props.isFocused) {
props.dispatch({
type: 'FOCUS_CLEAR',
field: 'globalSearch',
});
}
void onExitSearch(true);
}
}, []);
return (
<Root className="search-bar">
<SearchInput

View File

@@ -17,11 +17,12 @@ import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note';
import Tag from '@joplin/lib/models/Tag';
import Logger from '@joplin/lib/Logger';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { FolderEntity, FolderIcon } from '@joplin/lib/services/database/types';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import { store } from '@joplin/lib/reducer';
import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService';
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import FolderIconBox from '../FolderIconBox';
const { connect } = require('react-redux');
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
const { themeStyle } = require('@joplin/lib/theme');
@@ -77,6 +78,12 @@ function ExpandLink(props: any) {
);
}
const renderFolderIcon = (folderIcon: FolderIcon) => {
if (!folderIcon) return null;
return <div style={{ marginRight: 5, display: 'flex' }}><FolderIconBox folderIcon={folderIcon}/></div>;
};
function FolderItem(props: any) {
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, folderIcon, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
@@ -84,8 +91,6 @@ function FolderItem(props: any) {
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
const icon = folderIcon ? <span style={{ fontSize: 20, marginRight: 5 }}>{folderIcon.emoji}</span> : null;
return (
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth} ${selected ? 'selected' : ''}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
@@ -105,7 +110,7 @@ function FolderItem(props: any) {
}}
onDoubleClick={onFolderToggleClick_}
>
{icon}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
{renderFolderIcon(folderIcon)}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
{shareIcon} {noteCountComp}
</StyledListItemAnchor>
</StyledListItem>
@@ -294,7 +299,7 @@ class SidebarComponent extends React.Component<Props, State> {
);
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('openFolderDialog', itemId)));
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('openFolderDialog', { folderId: itemId })));
menu.append(new MenuItem({ type: 'separator' }));

View File

@@ -193,47 +193,49 @@ export default function(props: Props) {
const onJoplinCloudLoginClick = useCallback(async () => {
setJoplinCloudLoginInProgress(true);
let result = null;
try {
const result = await SyncTargetJoplinCloud.checkConfig({
result = await SyncTargetJoplinCloud.checkConfig({
password: () => joplinCloudPassword,
path: () => Setting.value('sync.10.path'),
userContentPath: () => Setting.value('sync.10.userContentPath'),
username: () => joplinCloudEmail,
});
if (result.ok) {
Setting.setValue('sync.target', 10);
Setting.setValue('sync.10.username', joplinCloudEmail);
Setting.setValue('sync.10.password', joplinCloudPassword);
await Setting.saveAll();
alert(_('Thank you! Your Joplin Cloud account is now setup and ready to use.'));
closeDialog(props.dispatch);
props.dispatch({
type: 'NAV_GO',
routeName: 'Main',
});
} else {
alert(_('There was an error setting up your Joplin Cloud account. Please verify your email and password and try again. Error was:\n\n%s', result.errorMessage));
}
} finally {
setJoplinCloudLoginInProgress(false);
}
if (result.ok) {
Setting.setValue('sync.target', 10);
Setting.setValue('sync.10.username', joplinCloudEmail);
Setting.setValue('sync.10.password', joplinCloudPassword);
await Setting.saveAll();
alert(_('Thank you! Your Joplin Cloud account is now setup and ready to use.'));
closeDialog(props.dispatch);
props.dispatch({
type: 'NAV_GO',
routeName: 'Main',
});
} else {
alert(_('There was an error setting up your Joplin Cloud account. Please verify your email and password and try again. Error was:\n\n%s', result.errorMessage));
}
}, [joplinCloudEmail, joplinCloudPassword, props.dispatch]);
const onJoplinCloudCreateAccountClick = useCallback(() => {
bridge().openExternal('https://joplinapp.org/plans/');
void bridge().openExternal('https://joplinapp.org/plans/');
}, []);
function renderJoplinCloudLoginForm() {
return (
<JoplinCloudLoginForm>
<div>{_('Login below.')} <CreateAccountLink href="#" onClick={onJoplinCloudCreateAccountClick}>{_('Or create an account.')}</CreateAccountLink></div>
<FormLabel>Email</FormLabel>
<div style={{ fontSize: '16px' }}>{_('Login below.')} <CreateAccountLink href="#" onClick={onJoplinCloudCreateAccountClick}>{_('Or create an account.')}</CreateAccountLink></div>
<FormLabel>{_('Email')}</FormLabel>
<StyledInput type="email" onChange={onJoplinCloudEmailChange}/>
<FormLabel>Password</FormLabel>
<FormLabel>{_('Password')}</FormLabel>
<StyledInput type="password" onChange={onJoplinCloudPasswordChange}/>
<SelectButton mt="1.3em" disabled={joplinCloudLoginInProgress} level={ButtonLevel.Primary} title={_('Login')} onClick={onJoplinCloudLoginClick}/>
</JoplinCloudLoginForm>

View File

@@ -1,14 +1,15 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const CommandService = require('@joplin/lib/services/CommandService').default;
class TagItemComponent extends React.Component {
render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.tagStyle);
const title = this.props.title;
const { title, id } = this.props;
return <span style={style}>{title}</span>;
return <button style={style} onClick={() => CommandService.instance().execute('openTag', id)}>{title}</button>;
}
}

View File

@@ -42,6 +42,7 @@ function TagList(props: Props) {
for (let i = 0; i < tags.length; i++) {
const props = {
title: tags[i].title,
id: tags[i].id,
key: tags[i].id,
};
output.push(<TagItem {...props} />);

View File

@@ -40,8 +40,13 @@ export default function() {
'toggleVisiblePanes',
'editor.deleteLine',
'editor.duplicateLine',
'editor.undo',
'editor.redo',
// We cannot put the undo/redo commands in the menu because they are
// editor-specific commands. If we put them there it will break the
// undo/redo in regular text fields.
// https://github.com/laurent22/joplin/issues/6214
// 'editor.undo',
// 'editor.redo',
'editor.indentLess',
'editor.indentMore',
'editor.toggleComment',

View File

@@ -277,6 +277,9 @@
let restoreAndRefreshTimeoutID_ = null;
let restoreAndRefreshTimeout_ = Date.now();
// If 'noteRenderComplete' message is ongoing, resizing should not trigger a 'percentScroll' messsage.
let noteRenderCompleteMessageIsOngoing_ = false;
// A callback anonymous function invoked when the scroll height changes.
const onRendering = observeRendering((cause, height, heightChanged) => {
if (!alreadyAllImagesLoaded && !scrollmap.isPresent()) {
@@ -285,6 +288,7 @@
alreadyAllImagesLoaded = true;
scrollmap.refresh();
restorePercentScroll();
noteRenderCompleteMessageIsOngoing_ = true;
ipcProxySendToHost('noteRenderComplete');
return;
}
@@ -293,6 +297,8 @@
const restoreAndRefresh = () => {
scrollmap.refresh();
restorePercentScroll();
// To ensures Editor's scroll position is synced with Viewer's
if (!noteRenderCompleteMessageIsOngoing_) ipcProxySendToHost('percentScroll', percentScroll_);
};
const now = Date.now();
if (now < restoreAndRefreshTimeout_) {
@@ -343,11 +349,13 @@
if (scrollmap.isPresent()) {
// Now, ready to receive scrollToHash/setPercentScroll from Editor.
noteRenderCompleteMessageIsOngoing_ = true;
ipcProxySendToHost('noteRenderComplete');
}
}
ipc.setPercentScroll = (event) => {
noteRenderCompleteMessageIsOngoing_ = false;
setPercentScroll(event.percent);
}
@@ -578,9 +586,24 @@
}));
document.addEventListener('contextmenu', webviewLib.logEnabledEventHandler(event => {
let element = event.target;
// To handle right clicks on resource icons
let element = event.target;
// Mermaid svgs are wrapped inside a <pre> with class "mermaid"
let mermaidElement = element.closest(".mermaid")?.children[0];
if (mermaidElement) {
const svgString = new XMLSerializer().serializeToString(mermaidElement);
if (!!svgString) {
ipcProxySendToHost('contextMenu', {
type: 'image',
textToCopy: svgString,
mime: 'image/svg+xml',
filename: mermaidElement.id + '.svg',
});
}
return;
}
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
if (element && element.getAttribute('data-resource-id')) {

View File

@@ -0,0 +1,20 @@
/**
* A Jest custom test Environment to load the resources for the tests.
* Use this test envirenment when you work with resources like images, files.
* See gui/NoteEditor/utils/contextMenu.test.ts for an example.
*/
const JSDOMEnvironment = require('jest-environment-jsdom');
import type { EnvironmentContext } from '@jest/environment';
import type { Config } from '@jest/types';
export default class CustomEnvironment extends JSDOMEnvironment {
constructor(config: Config.ProjectConfig, context?: EnvironmentContext) {
// Resources is set to 'usable' to enable fetching of resources like images and fonts while testing
// Which does not happen by default in jest
// https://stackoverflow.com/a/49482563
config.testEnvironmentOptions.resources = 'usable';
super(config, context);
}
}

View File

@@ -151,7 +151,7 @@ General classes
body, button {
color: var(--joplin-color);
font-size: 16px;
font-size: 13px;
}
div, span, a {
@@ -159,7 +159,7 @@ div, span, a {
}
h2 {
font-size: 24px;
font-size: 20px;
&.-no-top-margin {
margin-top: 0;
@@ -193,7 +193,7 @@ div.form,
p {
&.-small {
font-size: 13px;
font-size: 11px;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.7.0",
"version": "2.7.14",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -67,7 +67,7 @@
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"allowToChangeInstallationDirectory": false,
"differentialPackage": false
},
"portable": {
@@ -116,6 +116,7 @@
"app-builder-bin": "^1.9.11",
"babel-cli": "^6.26.0",
"babel-preset-react": "^6.24.1",
"canvas": "^2.9.0",
"electron": "14.1.0",
"electron-builder": "^22.11.7",
"electron-notarize": "^1.0.0",
@@ -147,7 +148,7 @@
"debounce": "^1.2.0",
"electron-window-state": "^4.1.1",
"formatcoords": "^1.1.3",
"fs-extra": "^5.0.0",
"fs-extra": "10.0.0",
"highlight.js": "^10.2.1",
"immer": "^7.0.5",
"keytar": "^7.0.0",

View File

@@ -19,6 +19,7 @@ const { mergeOverlappingIntervals } = require('@joplin/lib/ArrayUtils.js');
import markupLanguageUtils from '../utils/markupLanguageUtils';
import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
import Logger from '@joplin/lib/Logger';
import { MarkupToHtml } from '@joplin/renderer';
const logger = Logger.create('GotoAnything');
@@ -81,7 +82,7 @@ class Dialog extends React.PureComponent<Props, State> {
private inputRef: any;
private itemListRef: any;
private listUpdateIID_: any;
private markupToHtml_: any;
private markupToHtml_: MarkupToHtml;
private userCallback_: any = null;
constructor(props: Props) {
@@ -496,10 +497,10 @@ class Dialog extends React.PureComponent<Props, State> {
const isSelected = item.id === this.state.selectedItemId;
const rowStyle = isSelected ? style.rowSelected : style.row;
const titleHtml = item.fragments
? `<span style="font-weight: bold; color: ${theme.colorBright};">${item.title}</span>`
: surroundKeywords(this.state.keywords, item.title, `<span style="font-weight: bold; color: ${theme.colorBright};">`, '</span>', { escapeHtml: true });
? `<span style="font-weight: bold; color: ${theme.color};">${item.title}</span>`
: surroundKeywords(this.state.keywords, item.title, `<span style="font-weight: bold; color: ${theme.searchMarkerColor}; background-color: ${theme.searchMarkerBackgroundColor}">`, '</span>', { escapeHtml: true });
const fragmentsHtml = !item.fragments ? null : surroundKeywords(this.state.keywords, item.fragments, `<span style="font-weight: bold; color: ${theme.colorBright};">`, '</span>', { escapeHtml: true });
const fragmentsHtml = !item.fragments ? null : surroundKeywords(this.state.keywords, item.fragments, `<span style="color: ${theme.searchMarkerColor}; background-color: ${theme.searchMarkerBackgroundColor}">`, '</span>', { escapeHtml: true });
const folderIcon = <i style={{ fontSize: theme.fontSize, marginRight: 2 }} className="fa fa-book" />;
const pathComp = !item.path ? null : <div style={style.rowPath}>{folderIcon} {item.path}</div>;

View File

@@ -10,6 +10,12 @@
# ./runForTesting.sh 1 createUsers,createData,reset,e2ee,sync && ./runForTesting.sh 2 reset,e2ee,sync && ./runForTesting.sh 1
# ----------------------------------------------------------------------------------
# First user has E2EE, but second one doesn't:
# ----------------------------------------------------------------------------------
# ./runForTesting.sh 1 createUsers,createData,reset,e2ee,sync && ./runForTesting.sh 2 reset,sync && ./runForTesting.sh 1
# ----------------------------------------------------------------------------------
# Without E2EE:
# ----------------------------------------------------------------------------------
@@ -41,6 +47,11 @@ if [ "$USER_NUM" = "1a" ]; then
USER_PROFILE_NUM=1a
fi
if [ "$USER_NUM" = "1b" ]; then
USER_NUM=1
USER_PROFILE_NUM=1b
fi
COMMANDS=($(echo $2 | tr "," "\n"))
PROFILE_DIR=~/.config/joplindev-desktop-$USER_PROFILE_NUM
@@ -54,6 +65,10 @@ do
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
elif [[ $CMD == "createUserDeletions" ]]; then
curl --data '{"action": "createUserDeletions"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
elif [[ $CMD == "createData" ]]; then
echo 'mkbook "shared"' >> "$CMD_FILE"

View File

@@ -17,6 +17,7 @@ export default function stateToWhenClauseContext(state: AppState, options: WhenC
markdownEditorPaneVisible: state.settings['editor.codeView'] && state.noteVisiblePanes.includes('editor'),
markdownViewerPaneVisible: state.settings['editor.codeView'] && state.noteVisiblePanes.includes('viewer'),
modalDialogVisible: !!Object.keys(state.visibleDialogs).length,
gotoAnythingVisible: !!state.visibleDialogs['gotoAnything'],
sidebarVisible: !!state.mainLayout && layoutItemProp(state.mainLayout, 'sideBar', 'visible'),
noteListHasNotes: !!state.notes.length,

View File

@@ -0,0 +1,71 @@
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('BackOffHandler');
// This handler performs two checks:
//
// 1. If the plugin makes many API calls one after the other, a delay is going
// to be applied before responding. The delay is set using backOffIntervals_.
// When a plugin needs to be throttled that way a warning is displayed so
// that the author gets an opportunity to fix it.
//
// 2. If the plugin makes many simultaneous calls (over 100), the handler throws
// an exception to stop the plugin. In that case the plugin will be broken,
// but most plugins will not get this error anyway because call are usually
// made in sequence. It might reveal a bug though - for example if the plugin
// makes a call every 1 second, but does not wait for the response (or assume
// the response will come in less than one second). In that case, the back
// off intervals combined with the incorrect code will make the plugin fail.
export default class BackOffHandler {
private backOffIntervals_ = Array(100).fill(0).concat([0, 1, 1, 2, 3, 5, 8]);
private lastRequestTime_ = 0;
private pluginId_: string;
private resetBackOffInterval_ = (this.backOffIntervals_[this.backOffIntervals_.length - 1] + 1) * 1000;
private backOffIndex_ = 0;
private waitCount_ = 0;
private maxWaitCount_ = 100;
public constructor(pluginId: string) {
this.pluginId_ = pluginId;
}
private backOffInterval() {
const now = Date.now();
if (now - this.lastRequestTime_ > this.resetBackOffInterval_) {
this.backOffIndex_ = 0;
} else {
this.backOffIndex_++;
}
this.lastRequestTime_ = now;
const effectiveIndex = this.backOffIndex_ >= this.backOffIntervals_.length ? this.backOffIntervals_.length - 1 : this.backOffIndex_;
return this.backOffIntervals_[effectiveIndex];
}
public async wait(path: string, args: any) {
const interval = this.backOffInterval();
if (!interval) return;
this.waitCount_++;
// For now don't actually apply a backoff and don't abort.
logger.warn(`Plugin ${this.pluginId_}: Applying a backoff of ${interval} seconds due to frequent plugin API calls. Consider reducing the number of calls, caching the data, or requesting more data per call. API call was: `, path, args, `[Wait count: ${this.waitCount_}]`);
if (this.waitCount_ > this.maxWaitCount_) logger.error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
this.waitCount_--;
// if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
// await time.sleep(interval);
// this.waitCount_--;
}
}

View File

@@ -7,6 +7,7 @@ import Setting from '@joplin/lib/models/Setting';
import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandlersToIds';
import shim from '@joplin/lib/shim';
import Logger from '@joplin/lib/Logger';
import BackOffHandler from './BackOffHandler';
const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('PluginRunner');
@@ -83,8 +84,9 @@ function mapEventIdsToHandlers(pluginId: string, arg: any) {
export default class PluginRunner extends BasePluginRunner {
protected eventHandlers_: EventHandlers = {};
private backOffHandlers_: Record<string, BackOffHandler> = {};
constructor() {
public constructor() {
super();
this.eventHandler = this.eventHandler.bind(this);
@@ -95,7 +97,14 @@ export default class PluginRunner extends BasePluginRunner {
return cb(...args);
}
async run(plugin: Plugin, pluginApi: Global) {
private backOffHandler(pluginId: string): BackOffHandler {
if (!this.backOffHandlers_[pluginId]) {
this.backOffHandlers_[pluginId] = new BackOffHandler(pluginId);
}
return this.backOffHandlers_[pluginId];
}
public async run(plugin: Plugin, pluginApi: Global) {
const scriptPath = `${Setting.value('tempDir')}/plugin_${plugin.id}.js`;
await shim.fsDriver().writeFile(scriptPath, plugin.scriptText, 'utf8');
@@ -111,7 +120,7 @@ export default class PluginRunner extends BasePluginRunner {
bridge().electronApp().registerPluginWindow(plugin.id, pluginWindow);
pluginWindow.loadURL(`${require('url').format({
void pluginWindow.loadURL(`${require('url').format({
pathname: require('path').join(__dirname, 'plugin_index.html'),
protocol: 'file:',
slashes: true,
@@ -148,6 +157,13 @@ export default class PluginRunner extends BasePluginRunner {
const debugMappedArgs = fullPath.includes('setHtml') ? '<hidden>' : mappedArgs;
logger.debug(`Got message (3): ${fullPath}`, debugMappedArgs);
try {
await this.backOffHandler(plugin.id).wait(fullPath, debugMappedArgs);
} catch (error) {
logger.error(error);
return;
}
let result: any = null;
let error: any = null;
try {

View File

@@ -40,6 +40,7 @@ export default class PerFolderSortOrderService {
this.loadSharedSortOrder();
eventManager.appStateOn('notesParentType', this.onFolderSelectionMayChange.bind(this, 'notesParentType'));
eventManager.appStateOn('selectedFolderId', this.onFolderSelectionMayChange.bind(this, 'selectedFolderId'));
this.previousFolderId = Setting.value('activeFolderId');
}
public static isSet(folderId: string): boolean {

View File

@@ -30,7 +30,10 @@ function convertJsx(path) {
if (fileIsNewerThan(jsxPath, jsPath)) {
console.info(`Compiling ${jsxPath}...`);
const result = spawnSync('yarn', ['run', 'babel', '--presets', 'react', '--out-file', jsPath, jsxPath]);
// { shell: true } is needed to get it working on Windows:
// https://discourse.joplinapp.org/t/attempting-to-build-on-windows/22559/12
const result = spawnSync('yarn', ['run', 'babel', '--presets', 'react', '--out-file', jsPath, jsxPath], { shell: true });
if (result.status !== 0) {
const msg = [];
if (result.stdout) msg.push(result.stdout.toString());

View File

@@ -3,8 +3,62 @@ const glob = require('glob');
const { resolve } = require('path');
const { dirname } = require('@joplin/tools/gulp/utils');
const rootDir = resolve(__dirname, '../../..');
const nodeModulesDir = resolve(__dirname, '../node_modules');
function stripOffRootDir(path) {
if (path.startsWith(rootDir)) return path.substr(rootDir.length + 1);
return path;
}
const msleep = async (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
// Running this script on CI is very unreliable. It fails with errors that don't
// make much sense, such as:
//
// [Error: ENOENT: no such file or directory, copyfile
// '/home/runner/work/joplin/joplin/Assets/TinyMCE/langs/ro_RO.js' ->
// '/home/runner/work/joplin/joplin/packages/app-desktop/vendor/lib/tinymce/langs/ro_RO.js']
//
// (but "Assets/TinyMCE/langs/ro_RO.js" exists, since it's in the repo, and it's
// normal that "tinymce/langs/ro_RO.js" doesn't exist since we want to create
// it...)
//
// Another one, when trying to delete a directory:
//
// ENOTEMPTY: directory not empty
//
// (also makes no sense since the point of calling `remove()` is to remove a
// directory that is not empty)
//
// Those errors are random - they may or may not happen on a CI run, and always
// on different files. Since they don't make sense and are seemingly impossible
// to fix, we instead implement a retry mechanism with exponential backoff. The
// failures are relatively rare so 5 attempts should be enough to ensure all CI
// runs succeed.
//
// It's possible the same technique should be added to copyPluginAssets too.
const withRetry = async (fn) => {
for (let i = 0; i < 5; i++) {
try {
await fn();
return;
} catch (error) {
console.warn(`withRetry: Failed calling function - will retry (${i})`, error);
await msleep(1000 + i * 1000);
}
}
throw new Error('withRetry: Could not run function after multiple attempts');
};
async function main() {
const langSourceDir = resolve(__dirname, '../../../Assets/TinyMCE/langs');
const buildLibDir = resolve(__dirname, '../vendor/lib');
@@ -50,11 +104,11 @@ async function main() {
}
if (action === 'delete') {
await remove(destDir);
await withRetry(() => remove(destDir));
} else {
console.info(`Copying ${sourceDir} => ${destDir}`);
await mkdirp(destDir);
await copy(sourceDir, destDir, { overwrite: true });
console.info(`Copying ${stripOffRootDir(sourceDir)} => ${stripOffRootDir(destDir)}`);
await withRetry(() => mkdirp(destDir));
await withRetry(() => copy(sourceDir, destDir, { overwrite: true }));
}
}
}
@@ -70,10 +124,10 @@ async function main() {
destFile = `${buildLibDir}/${file}`;
}
await mkdirp(dirname(destFile));
await withRetry(() => mkdirp(dirname(destFile)));
console.info(`Copying ${sourceFile} => ${destFile}`);
await copy(sourceFile, destFile, { overwrite: true });
console.info(`Copying ${stripOffRootDir(sourceFile)} => ${stripOffRootDir(destFile)}`);
await withRetry(() => copy(sourceFile, destFile, { overwrite: true }));
}
const supportedLocales = glob.sync(`${langSourceDir}/*.js`).map(s => {

View File

@@ -1,4 +1,28 @@
const utils = require('@joplin/tools/gulp/utils');
const { copy, mkdirp, remove } = require('fs-extra');
const msleep = async (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
// Same as copyApplicationAssets - probably both scripts should be merged in
// one.
const withRetry = async (fn) => {
for (let i = 0; i < 5; i++) {
try {
await fn();
return;
} catch (error) {
console.warn(`withRetry: Failed calling function - will retry (${i})`, error);
await msleep(1000 + i * 1000);
}
}
throw new Error('withRetry: Could not run function after multiple attempts');
};
async function main() {
const rootDir = `${__dirname}/..`;
@@ -9,9 +33,16 @@ async function main() {
`${rootDir}/pluginAssets`,
];
for (const destDir of destDirs) {
console.info(`Copying to ${destDir}`);
await utils.copyDir(sourceDir, destDir);
for (const action of ['delete', 'copy']) {
for (const destDir of destDirs) {
if (action === 'delete') {
await withRetry(() => remove(destDir));
} else {
console.info(`Copying to ${destDir}`);
await withRetry(() => mkdirp(destDir));
await withRetry(() => copy(sourceDir, destDir, { overwrite: true }));
}
}
}
}

View File

@@ -0,0 +1,2 @@
import { EmojiButton } from './lib/@joeattardi/emoji-button/dist/index.js';
window.EmojiButton = EmojiButton;

View File

@@ -146,8 +146,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097665
versionName "2.7.0"
versionCode 2097667
versionName "2.7.2"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -210,7 +210,7 @@ class CameraView extends Component {
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-end' }}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginBottom: 20 }}>
{ reverseCameraButton }
<TouchableOpacity onPress={this.photo_onPress}>
<TouchableOpacity onPress={this.photo_onPress} disabled={this.state.snapping}>
<View style={{ flexDirection: 'row', borderRadius: 90, width: 90, height: 90, backgroundColor: '#ffffffaa', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Icon
name={photoIcon}

View File

@@ -103,7 +103,7 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
return (
<View style={{ flex: 0, margin: 20, alignItems: 'center' }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{ this.state.date && <Text style={{ ...theme.normalText, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
{ this.state.date && <Text style={{ ...theme.normalText,color: theme.color, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
<Button title="Set date" onPress={this.onSetDate} />
</View>
<DateTimePickerModal

View File

@@ -93,9 +93,6 @@ class NoteScreenComponent extends BaseScreenComponent {
this.doFocusUpdate_ = false;
// iOS doesn't support multiline text fields properly so disable it
this.enableMultilineTitle_ = Platform.OS !== 'ios';
this.saveButtonHasBeenShown_ = false;
this.styles_ = {};
@@ -231,7 +228,6 @@ class NoteScreenComponent extends BaseScreenComponent {
this.onAlarmDialogAccept = this.onAlarmDialogAccept.bind(this);
this.onAlarmDialogReject = this.onAlarmDialogReject.bind(this);
this.todoCheckbox_change = this.todoCheckbox_change.bind(this);
this.titleTextInput_contentSizeChange = this.titleTextInput_contentSizeChange.bind(this);
this.title_changeText = this.title_changeText.bind(this);
this.undoRedoService_stackChange = this.undoRedoService_stackChange.bind(this);
this.screenHeader_undoButtonPress = this.screenHeader_undoButtonPress.bind(this);
@@ -389,7 +385,6 @@ class NoteScreenComponent extends BaseScreenComponent {
paddingBottom: 10, // Added for iOS (Not needed for Android??)
};
if (this.enableMultilineTitle_) styles.titleTextInput.height = this.state.titleTextInputHeight;
if (this.state.HACK_webviewLoadingState === 1) styles.titleTextInput.marginTop = 1;
this.styles_[cacheKey] = StyleSheet.create(styles);
@@ -971,13 +966,6 @@ class NoteScreenComponent extends BaseScreenComponent {
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
}
titleTextInput_contentSizeChange(event: any) {
if (!this.enableMultilineTitle_) return;
const height = event.nativeEvent.contentSize.height;
this.setState({ titleTextInputHeight: height });
}
scheduleFocusUpdate() {
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
@@ -1178,8 +1166,6 @@ class NoteScreenComponent extends BaseScreenComponent {
<View style={titleContainerStyle}>
{isTodo && <Checkbox style={this.styles().checkbox} checked={!!Number(note.todo_completed)} onChange={this.todoCheckbox_change} />}
<TextInput
onContentSizeChange={this.titleTextInput_contentSizeChange}
multiline={this.enableMultilineTitle_}
ref="titleTextField"
underlineColorAndroid="#ffffff00"
autoCapitalize="sentences"

View File

@@ -21,6 +21,7 @@ class NoteTagsDialogComponent extends React.Component {
tagListData: [],
newTags: '',
savingTags: false,
tagFilter: '',
};
const noteHasTag = tagId => {
@@ -88,6 +89,10 @@ class NoteTagsDialogComponent extends React.Component {
this.cancelButton_press = () => {
if (this.props.onCloseRequested) this.props.onCloseRequested();
};
this.filterTags = (allTags) => {
return allTags.filter((tag) => tag.title.includes(this.state.tagFilter.toLowerCase()), allTags);
};
}
UNSAFE_componentWillMount() {
@@ -140,16 +145,16 @@ class NoteTagsDialogComponent extends React.Component {
fontSize: 20,
color: theme.color,
},
newTagBox: {
tagBox: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
paddingLeft: 10,
paddingRight: 10,
borderBottomWidth: 1,
borderBottomColor: theme.dividerColor,
},
newTagBoxLabel: Object.assign({}, theme.normalText, { marginRight: 8 }),
newTagBoxInput: Object.assign({}, theme.lineInput, { flex: 1 }),
tagBoxInput: Object.assign({}, theme.lineInput, { flex: 1 }),
};
this.styles_[themeId] = StyleSheet.create(styles);
@@ -161,7 +166,7 @@ class NoteTagsDialogComponent extends React.Component {
const dialogContent = (
<View style={{ flex: 1 }}>
<View style={this.styles().newTagBox}>
<View style={this.styles().tagBox}>
<Text style={this.styles().newTagBoxLabel}>{_('New tags:')}</Text>
<TextInput
selectionColor={theme.textSelectionColor}
@@ -170,10 +175,23 @@ class NoteTagsDialogComponent extends React.Component {
onChangeText={value => {
this.setState({ newTags: value });
}}
style={this.styles().newTagBoxInput}
style={this.styles().tagBoxInput}
placeholder={_('tag1,tag2,...')}
/>
</View>
<FlatList data={this.state.tagListData} renderItem={this.renderTag} keyExtractor={this.tagKeyExtractor} />
<View style={this.styles().tagBox}>
<TextInput
selectionColor={theme.textSelectionColor}
keyboardAppearance={theme.keyboardAppearance}
value={this.state.tagFilter}
onChangeText={value => {
this.setState({ tagFilter: value });
}}
placeholder={_('Filter tags')}
style={this.styles().tagBoxInput}
/>
</View>
<FlatList data={this.filterTags(this.state.tagListData)} renderItem={this.renderTag} keyExtractor={this.tagKeyExtractor} />
</View>
);

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