Compare commits

...
Author SHA1 Message Date
Laurent Cozic d4c209e638 CLI v2.8.1 2022-05-18 14:03:43 +01:00
Laurent Cozic 2cbe7be16e Releasing sub-packages 2022-05-18 13:59:47 +01:00
Joplin Bot d2bb2e9df4 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-17 18:18:25 +00:00
Laurent Cozic c2a6a13eb4 Desktop release v2.8.8 2022-05-17 14:13:27 +01:00
Laurent Cozic 6ba5a896e8 Update translations 2022-05-17 14:12:42 +01:00
Daeraxa 6937066c60 Docs: Update plugin readmes (#6502) 2022-05-17 14:10:58 +01:00
Retrove 64ef5ebde4 Desktop: Fixes #6503: Dropbox login button is not visible in dark mode (#6513) 2022-05-17 14:10:31 +01:00
Laurent Cozic 7ec3a7be9f Desktop: Remove plugin backoff handler for now 2022-05-17 14:08:54 +01:00
Joplin Bot 81ea66f4ad Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-17 12:23:02 +00:00
Laurent Cozic 075c4053a8 Doc: Added sponsor 2022-05-17 10:09:22 +01:00
Máté Molnár 17256873dc Updated translations (#6516) 2022-05-14 22:49:33 +01:00
Joplin Bot 6f1a806e5c Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-12 00:40:30 +00:00
Joplin Bot 41e95e81de Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-11 18:18:05 +00:00
Joplin Bot f406d112bd Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-11 12:23:05 +00:00
Laurent Cozic 2151a626cd Doc: Disable linkify to prevent text from being incorrectly converted to a link 2022-05-10 16:01:30 +01:00
kik0220 0fc65acfa2 Translation: Update ja_JP.po (#6504) 2022-05-10 14:41:30 +01:00
Tolulope Malomo 2eddd40667 Mobile: Fixes #6328: Error when pressing undo or redo button while editor is closed (#6426) 2022-05-10 10:23:36 +01:00
Mr-Kanister a8723ea512 Update de_DE.po (#6501) 2022-05-10 09:59:57 +01:00
Laurent Cozic a6c3fbb0ed Revert "Merge branch 'dev' into dev"
This reverts commit 5e0c171099, reversing
changes made to b8aa7d0735.

Was accidentally pushed to dev.
2022-05-09 12:41:55 +01:00
Daeraxa 5e0c171099 Merge branch 'dev' into dev 2022-05-09 11:25:35 +01:00
Joplin Bot b8aa7d0735 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-06 12:20:41 +00:00
Laurent Cozic 91d786ddbe Desktop release v2.8.7 2022-05-06 10:39:57 +01:00
Laurent Cozic 1fc0fb39d3 Merge branch 'dev' into release-2.8 2022-05-06 10:39:37 +01:00
Laurent Cozic afdc7ef141 Update translations 2022-05-06 10:37:39 +01:00
Milo Ivir adf317e16c Update Croatian translation (#6489) 2022-05-05 22:37:31 +01:00
Laurent Cozic 191c49c0cd Desktop: Fixes #6459: Sync config was lost when switching profiles 2022-05-05 18:15:10 +01:00
Laurent Cozic 27118c2d00 Update translations 2022-05-05 17:06:00 +01:00
Laurent Cozic 766d7ebb65 Tools: Add spellchecker to pre-commit hook 2022-05-05 16:36:28 +01:00
Laurent Cozic d99312e06c Desktop: Fixes #6435: Make undo/redo menu items translatable 2022-05-05 16:36:28 +01:00
Joplin Bot b28f069f70 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-05 12:21:22 +00:00
Laurent Cozic 876a67d9b0 Chore: Spell checker 2022-05-05 13:19:57 +01:00
Laurent Cozic 814a5a0cd8 Desktop, Mobile: Sort sync target options 2022-05-05 13:19:23 +01:00
Laurent Cozic 5adee9085c Doc: Added E2EE flowcharts 2022-05-04 17:24:50 +01:00
Joplin Bot c86e7f5eed Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-03 12:21:04 +00:00
Laurent Cozic c7444e563b Chore: Removed "canvas" package to fix build using node 18 2022-05-03 11:19:14 +01:00
Laurent Cozic 2486c787a4 Desktop release v2.8.6 2022-05-02 16:04:28 +01:00
Laurent Cozic e518d45719 spell checker 2022-05-02 16:01:27 +01:00
Laurent Cozic 47c3ee0f2c Desktop: Disable crash detection handler for now 2022-05-02 16:01:26 +01:00
Daeraxa f7b5ed9871 Doc: Stale bot - Add link to forum (#6473) 2022-05-02 15:38:22 +01:00
Laurent Cozic 015562c86a Tools: Added cspell config 2022-05-01 19:38:00 +01:00
Daeraxa 7f3e8c5455 test 2022-05-01 16:16:04 +01:00
Mats Estensen cef3eac372 translation: update nb_NO (#6463) 2022-05-01 13:39:26 +01:00
Joplin Bot d97a7bc7ab Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-27 18:17:41 +00:00
Laurent Cozic 9806e3319a Desktop release v2.8.5 2022-04-27 13:21:51 +01:00
Laurent Cozic 22ae50c126 Desktop: More permissive plugin back-off rules 2022-04-27 13:20:25 +01:00
Hieu-Thi Luong 0d4cb5c16a Desktop: Fixed color of links within list in Markdown editor (#6447) 2022-04-27 13:10:28 +01:00
Milo Ivir 0d8c69123b Update Croatian translation (#6441) 2022-04-27 13:08:56 +01:00
Joplin Bot ae5b810bed Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-26 18:17:04 +00:00
Laurent Cozic c95367fbbb Doc: Minor fix on API doc 2022-04-26 17:03:58 +01:00
Laurent Cozic 22c83214be Chore: Disable crash detection in dev mode 2022-04-26 13:26:51 +01:00
Laurent Cozic 86179bd74c Desktop: Fixed getting geolocation for new notes 2022-04-26 13:22:43 +01:00
Laurent Cozic 8abdbfe2bd Fixed MdToHtml regression 2022-04-26 12:52:48 +01:00
Daeraxa 790c98ca40 Docs: Update terminal commands (#6457) 2022-04-25 19:35:50 +01:00
Laurent Cozic 774c20772b Security: Fixed disallowed tag XSS 2022-04-25 17:18:27 +01:00
Laurent Cozic d9a4a9cb30 Desktop: Ask to start in safe mode when the application has crashed 2022-04-25 17:18:26 +01:00
Joplin Bot bd917ae09c Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-21 00:41:59 +00:00
Joplin Bot a7c0e926cf Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-20 18:14:32 +00:00
Laurent Cozic 8a65bb5818 Tools: Add commit links to changelog 2022-04-20 17:01:40 +01:00
Laurent Cozic aa503aee3e Plugin Generator release v2.8.1 2022-04-20 16:31:49 +01:00
Laurent Cozic 373cd805dc Chore: Updated plugin types 2022-04-20 16:30:28 +01:00
Laurent Cozic fc095986b0 Plugins: Add support for file and directory selector in Settings API 2022-04-20 16:21:57 +01:00
Joplin Bot 8e5d862c36 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-19 18:18:20 +00:00
Laurent Cozic 2399ca6321 Tools: Added precommit hook script to check lib paths 2022-04-19 18:57:59 +01:00
Laurent Cozic 889aecc8d4 Desktop release v2.8.4 2022-04-19 18:03:06 +01:00
Laurent Cozic 36e75310bf fix paths 2022-04-19 18:02:31 +01:00
Laurent Cozic d3d944d52b Desktop release v2.8.3 2022-04-19 15:55:00 +01:00
Laurent Cozic 89a498b886 Merge branch 'dev' into release-2.8 2022-04-19 15:54:35 +01:00
Laurent Cozic b7167552ec Desktop: Enabled plugin throttling logic to prevent certain plugins from freezing the app 2022-04-19 15:53:13 +01:00
Joplin Bot 627b16728d Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-19 06:17:18 +00:00
Mr-Kanister 18a7b1a69f Update de_DE.po (#6425) 2022-04-18 10:36:04 +01:00
Joplin Bot 1b1e060226 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-17 18:18:32 +00:00
Laurent Cozic ebbb1d6d37 Doc: Add new sponsor 2022-04-17 18:07:57 +01:00
Laurent Cozic dabd0e1d3b Chore: Fixed yarn.lock 2022-04-17 17:54:36 +01:00
Laurent Cozic 64377cd3c9 Tools: Also update mobile package number when creating a new release 2022-04-17 16:42:12 +01:00
asrient be8001857e Desktop: Fixes #6126: Editor context menu was broken (regression) (#6422) 2022-04-17 16:20:47 +01:00
Kenichi Kobayashi 5d9b43ee31 Desktop: Resolves #5770: Performance improvement when switching note while plugins are running (#6409) 2022-04-17 12:41:27 +01:00
Laurent Cozic 1797e847aa Desktop: Multi-profiles: Automatically restart the app when switching profiles on Linux 2022-04-17 12:26:58 +01:00
Laurent Cozic fd9fe5c931 Desktop: Multi-profiles: Added profile ID in CSS root class so that different profiles can have different styles 2022-04-16 15:13:17 +01:00
Laurent Cozic b4a6e17090 Desktop: Multi-profiles: Assign an ID to profiles and remove path 2022-04-16 15:04:06 +01:00
Laurent Cozic 0cdef66816 Desktop: Multi-profiles: Share custom CSS between profiles 2022-04-16 13:51:17 +01:00
Laurent Cozic a111531810 Desktop: Multi-profiles: Share UI layout between profiles 2022-04-16 13:37:04 +01:00
Laurent Cozic 510df43d8d Desktop: Multi-profiles: Share plugins between profiles 2022-04-16 13:36:14 +01:00
Laurent Cozic 845ac19f0d Desktop: Fixes #6411: Locale was not being preserved when creating a new profile 2022-04-16 13:29:14 +01:00
Elaborendum 01ee178566 Update es_ES.po (#6418) 2022-04-16 11:51:47 +01:00
mrkaato0 3f4e7ff74e Update Finnish translation (#6415) 2022-04-16 11:51:19 +01:00
Laurent Cozic 65ad6543a2 fixed ignored files 2022-04-15 17:49:44 +01:00
Laurent Cozic f27d15a5a7 Desktop: Fixes #6145: Opening a file with ctrl-click in the editor results in a 'network error' dialogue 2022-04-15 17:48:01 +01:00
Laurent Cozic 7c619df2ce Merge branch 'release-2.7' into dev 2022-04-15 15:04:44 +01:00
Laurent Cozic 546447b09b iOS 12.7.2 2022-04-15 12:07:54 +01:00
Laurent Cozic 7ef9d87e76 iOS: Fixes #6375: iOS and Dropbox synchronisation not working on iOS 15 2022-04-15 12:06:24 +01:00
Laurent Cozic 8019306247 Tools: Add React Native HTTP debugging function 2022-04-15 12:04:40 +01:00
Laurent Cozic 818ad62fbe iOS: Fixes #6375: iOS and Dropbox synchronisation not working on iOS 15 2022-04-15 12:03:48 +01:00
Laurent Cozic 671077e1bb Chore: Fixed mobile dropbown regression 2022-04-15 12:02:10 +01:00
Laurent 1b043d856d Chore: Refactor note list on desktop using React Hooks (#6410) 2022-04-14 16:50:42 +01:00
Joplin Bot 7e1ee40333 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-14 12:21:57 +00:00
Laurent Cozic 343b81ad09 Desktop: Resolves #6394: Improve performance when switching notes, when multiple plugins are loaded
By preventing the menu bar to needlessly re-render
2022-04-14 12:27:19 +01:00
Laurent Cozic 7b7e53f3b2 Desktop release v2.8.2 2022-04-14 10:02:15 +01:00
Laurent Cozic 95d5866955 Merge branch 'dev' into release-2.8 2022-04-14 10:02:03 +01:00
Laurent Cozic 39efc88059 Chore: Fixed lib imports 2022-04-14 09:58:34 +01:00
Laurent Cozic 558e55090f Desktop: Resovles #6194: Improved handling of ENTER and ESCAPE keys in dialogs 2022-04-13 14:44:52 +01:00
Laurent Cozic ff066baa26 Desktop, Mobile: Automatically start sync after setting the sync parameters 2022-04-13 12:40:52 +01:00
Laurent e5313a9719 Desktop: Resolves #6338: Improve E2EE usability when accidentally creating multiple keys (#6399) 2022-04-13 12:18:38 +01:00
Joplin Bot 376019b540 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-13 00:39:11 +00:00
Joplin Bot c28979e620 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-12 18:17:04 +00:00
MHolkamp 7e9c7a5954 Update nl_NL.po (#6397)
Translated and corrected the last items in the file.
2022-04-12 17:34:20 +01:00
Laurent Cozic 55db877f85 Tools: Use simpler test passwords 2022-04-12 17:06:53 +01:00
Laurent Cozic f24750f7b4 Tools: Command to merge release version update 2022-04-12 16:46:53 +01:00
Laurent Cozic cfd5416b73 Desktop release v2.8.1 2022-04-12 16:40:41 +01:00
Laurent Cozic ea2418d018 Doc: Fixed typo 2022-04-12 16:12:22 +01:00
Laurent Cozic e8d704f13a Desktop release v2.8.1 2022-04-12 15:32:14 +01:00
Laurent Cozic c94a98b841 Chore: Setup new release 2.8 2022-04-12 15:30:37 +01:00
Laurent Cozic 4fd19d6970 Desktop release v2.7.15 2022-04-12 15:27:05 +01:00
Laurent 6f249c3008 Tools: Fixed Windows app build on CI (#6398) 2022-04-12 15:15:06 +01:00
Laurent Cozic 0374505212 Chore: Fixed CI tests 2022-04-12 12:42:21 +01:00
Laurent Cozic 21706fa00a Desktop: Fixed color of published note on Light theme 2022-04-11 17:46:33 +01:00
Laurent Cozic 74273cd570 Plugins: Allow updating a resource via the data API 2022-04-11 17:01:01 +01:00
Laurent 6458ad0540 Desktop: Resolves #591: Add support for multiple profiles (#6385) 2022-04-11 16:49:32 +01:00
Henry Heino 58bf93a112 iOS: Fixes #6318: Remove white border around Beta Editor (#6326) 2022-04-11 11:57:49 +01:00
Henry Heino 5962b0813e Mobile: Fixes #6324: Support inserting attachments from Beta Editor (#6325) 2022-04-11 11:56:45 +01:00
Ayush Srivastava cffea3ea1e Mobile: Fixes #3564: "Move Note" dropdown menu can be very narrow (#6306) 2022-04-11 11:53:20 +01:00
Kenichi Kobayashi f6e21e0180 Desktop: Fixes #6074: Scroll jumps when typing if heavy scripts or many large elements are used (#6383) 2022-04-10 11:31:17 +01:00
reportxx e02422070e Update Swedish translation (#6382) 2022-04-10 11:30:03 +01:00
Tolulope Malomo 727d64b646 Android: Fixes #6026: Long path in "Export profile" prevents tapping OK button (#6359) 2022-04-10 11:22:30 +01:00
Henry Heino 23e54a60d9 Android: Fixes #5987: Cursor hard to see in dark mode (#6307) 2022-04-10 10:58:11 +01:00
ScriptInfra 0d4978223e Update README.md (#6295) 2022-04-10 10:53:44 +01:00
Mayank Bondre 0b32a29cce Plugins: Resolves #5867: Add support for "categories" manifest field (#6109) 2022-04-10 10:52:31 +01:00
Laurent Cozic a0d77d10ba Tools: Allow setting website build environment from config file 2022-04-09 14:43:59 +01:00
Joplin Bot bdd9c6cf35 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-09 12:19:45 +00:00
Laurent Cozic f2bfa30e04 Api: Fixed updating resource content 2022-04-09 11:58:08 +01:00
Laurent Cozic 8077117e65 Doc: Ignore latest post in updateNews script 2022-04-08 13:16:43 +01:00
Laurent Cozic 7e8927398a Doc: Fixed typo 2022-04-07 19:19:53 +01:00
Laurent Cozic 09dcee876c Doc: Fixed env 2022-04-07 19:04:36 +01:00
Laurent Cozic 23b56f4f70 Tools: Fixed script name 2022-04-07 16:00:00 +01:00
Laurent Cozic b3d09ce776 Doc: Add Joplin Cloud Teams offer to website 2022-04-07 15:35:15 +01:00
Laurent Cozic 84d40b805e Tools: Added tool to automatically post news from local Markdown folder to forum 2022-04-07 15:15:48 +01:00
Laurent Cozic c097a82b7b Doc: Fixed news title 2022-04-07 11:07:48 +01:00
Joplin Bot dfa22b560e Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-06 06:17:47 +00:00
Joplin Bot 7d31a3fe90 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-06 00:38:27 +00:00
妙呀 79aabc2d06 updata zh_CN.po (#6355) 2022-04-05 19:17:20 +01:00
rnbastos 70e82ca64f Update pt_BR.po (#6353) 2022-04-05 19:16:56 +01:00
Laurent Cozic a0662412b2 Tools: Removed Windows build from CI for now - discontinued by GitHub 2022-04-05 17:30:27 +01:00
Laurent Cozic 220b48ef02 Doc: Add news about GSoC Contributor Proposals phase 2022-04-05 15:47:22 +01:00
Laurent Cozic cb637e817b Server: Do not make checkboxes in published notes clickable 2022-04-05 15:42:06 +01:00
Laurent Cozic 27198a16a4 Chore: Make it easier to test note publishing on desktop 2022-04-05 15:37:57 +01:00
Laurent Cozic 1a5bff3bf4 Doc: Move info to Joplin Cloud FAQ 2022-04-05 15:16:48 +01:00
Laurent Cozic 571147acbb Tools: Fixed git changelog tool 2022-04-03 19:27:10 +01:00
Laurent Cozic 9d9420a35c Desktop: Support for Joplin Cloud recursive linked notes 2022-04-03 19:19:24 +01:00
kik0220andkik0220 a79bc69604 Translation: Update ja_JP.po (#6345)
Co-authored-by: kik0220 <kik0220@gmail.com>
2022-03-31 10:50:29 +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 8352e23bdf Desktop release v2.7.15 2022-03-12 16:27:06 +00:00
Laurent Cozic 96137d74f0 Merge branch 'dev' into release-2.7 2022-03-12 16:25:11 +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 75cc9d7f9b Desktop release v2.7.14 2022-02-27 10:51:37 +00:00
Laurent Cozic 693e52a1fd Merge branch 'dev' into release-2.7 2022-02-27 10:51:12 +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 5b5165dbcd Desktop release v2.7.13 2022-02-24 15:10:49 +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 JohnandKenichi Kobayashi 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 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 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 2748df7c61 Server v2.7.1 2021-12-29 10:51:24 +01:00
520 changed files with 42395 additions and 25591 deletions

No files matched your search

+16 -9
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/
+99
View File
@@ -148,6 +148,9 @@ packages/app-desktop/checkForUpdates.js.map
packages/app-desktop/commands/copyDevCommand.d.ts
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyDevCommand.js.map
packages/app-desktop/commands/editProfileConfig.d.ts
packages/app-desktop/commands/editProfileConfig.js
packages/app-desktop/commands/editProfileConfig.js.map
packages/app-desktop/commands/exportFolders.d.ts
packages/app-desktop/commands/exportFolders.js
packages/app-desktop/commands/exportFolders.js.map
@@ -175,6 +178,18 @@ packages/app-desktop/commands/startExternalEditing.js.map
packages/app-desktop/commands/stopExternalEditing.d.ts
packages/app-desktop/commands/stopExternalEditing.js
packages/app-desktop/commands/stopExternalEditing.js.map
packages/app-desktop/commands/switchProfile.d.ts
packages/app-desktop/commands/switchProfile.js
packages/app-desktop/commands/switchProfile.js.map
packages/app-desktop/commands/switchProfile1.d.ts
packages/app-desktop/commands/switchProfile1.js
packages/app-desktop/commands/switchProfile1.js.map
packages/app-desktop/commands/switchProfile2.d.ts
packages/app-desktop/commands/switchProfile2.js
packages/app-desktop/commands/switchProfile2.js.map
packages/app-desktop/commands/switchProfile3.d.ts
packages/app-desktop/commands/switchProfile3.js
packages/app-desktop/commands/switchProfile3.js.map
packages/app-desktop/commands/toggleExternalEditing.d.ts
packages/app-desktop/commands/toggleExternalEditing.js
packages/app-desktop/commands/toggleExternalEditing.js.map
@@ -217,6 +232,9 @@ packages/app-desktop/gui/Dialog.js.map
packages/app-desktop/gui/DialogButtonRow.d.ts
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow.js.map
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.d.ts
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js.map
packages/app-desktop/gui/DialogTitle.d.ts
packages/app-desktop/gui/DialogTitle.js
packages/app-desktop/gui/DialogTitle.js.map
@@ -235,6 +253,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
@@ -256,6 +277,9 @@ packages/app-desktop/gui/KeymapConfig/utils/useKeymap.js.map
packages/app-desktop/gui/MainScreen/MainScreen.d.ts
packages/app-desktop/gui/MainScreen/MainScreen.js
packages/app-desktop/gui/MainScreen/MainScreen.js.map
packages/app-desktop/gui/MainScreen/commands/addProfile.d.ts
packages/app-desktop/gui/MainScreen/commands/addProfile.js
packages/app-desktop/gui/MainScreen/commands/addProfile.js.map
packages/app-desktop/gui/MainScreen/commands/commandPalette.d.ts
packages/app-desktop/gui/MainScreen/commands/commandPalette.js
packages/app-desktop/gui/MainScreen/commands/commandPalette.js.map
@@ -487,6 +511,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
@@ -532,6 +562,9 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
packages/app-desktop/gui/NoteList/commands/index.d.ts
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/commands/index.js.map
packages/app-desktop/gui/NoteList/types.d.ts
packages/app-desktop/gui/NoteList/types.js
packages/app-desktop/gui/NoteList/types.js.map
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
@@ -721,6 +754,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
@@ -772,6 +808,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/restart.d.ts
packages/app-desktop/services/restart.js
packages/app-desktop/services/restart.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.js.map
@@ -1030,6 +1069,9 @@ packages/lib/database.js.map
packages/lib/debug/DebugService.d.ts
packages/lib/debug/DebugService.js
packages/lib/debug/DebugService.js.map
packages/lib/dom.d.ts
packages/lib/dom.js
packages/lib/dom.js.map
packages/lib/dummy.test.d.ts
packages/lib/dummy.test.js
packages/lib/dummy.test.js.map
@@ -1060,6 +1102,9 @@ packages/lib/fs-driver-node.js.map
packages/lib/fsDriver.test.d.ts
packages/lib/fsDriver.test.js
packages/lib/fsDriver.test.js.map
packages/lib/geolocation-node.d.ts
packages/lib/geolocation-node.js
packages/lib/geolocation-node.js.map
packages/lib/hooks/useAsyncEffect.d.ts
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useAsyncEffect.js.map
@@ -1147,6 +1192,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
@@ -1231,6 +1279,9 @@ packages/lib/services/DecryptionWorker.js.map
packages/lib/services/ExternalEditWatcher.d.ts
packages/lib/services/ExternalEditWatcher.js
packages/lib/services/ExternalEditWatcher.js.map
packages/lib/services/ExternalEditWatcher/utils.d.ts
packages/lib/services/ExternalEditWatcher/utils.js
packages/lib/services/ExternalEditWatcher/utils.js.map
packages/lib/services/ItemChangeUtils.d.ts
packages/lib/services/ItemChangeUtils.js
packages/lib/services/ItemChangeUtils.js.map
@@ -1555,6 +1606,24 @@ packages/lib/services/plugins/utils/validatePluginVersion.js.map
packages/lib/services/plugins/utils/validatePluginVersion.test.d.ts
packages/lib/services/plugins/utils/validatePluginVersion.test.js
packages/lib/services/plugins/utils/validatePluginVersion.test.js.map
packages/lib/services/profileConfig/index.d.ts
packages/lib/services/profileConfig/index.js
packages/lib/services/profileConfig/index.js.map
packages/lib/services/profileConfig/index.test.d.ts
packages/lib/services/profileConfig/index.test.js
packages/lib/services/profileConfig/index.test.js.map
packages/lib/services/profileConfig/initProfile.d.ts
packages/lib/services/profileConfig/initProfile.js
packages/lib/services/profileConfig/initProfile.js.map
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.d.ts
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.js
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.js.map
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.d.ts
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.js
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.js.map
packages/lib/services/profileConfig/types.d.ts
packages/lib/services/profileConfig/types.js
packages/lib/services/profileConfig/types.js.map
packages/lib/services/rest/Api.d.ts
packages/lib/services/rest/Api.js
packages/lib/services/rest/Api.js.map
@@ -1633,6 +1702,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
@@ -1927,6 +1999,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
@@ -1945,6 +2020,12 @@ packages/tools/buildServerDocker.js.map
packages/tools/buildServerDocker.test.d.ts
packages/tools/buildServerDocker.test.js
packages/tools/buildServerDocker.test.js.map
packages/tools/checkLibPaths.d.ts
packages/tools/checkLibPaths.js
packages/tools/checkLibPaths.js.map
packages/tools/checkLibPaths.test.d.ts
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.test.js.map
packages/tools/convertThemesToCss.d.ts
packages/tools/convertThemesToCss.js
packages/tools/convertThemesToCss.js.map
@@ -1957,6 +2038,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
@@ -1978,6 +2062,9 @@ packages/tools/release-server.js.map
packages/tools/setupNewRelease.d.ts
packages/tools/setupNewRelease.js
packages/tools/setupNewRelease.js.map
packages/tools/spellcheck.d.ts
packages/tools/spellcheck.js
packages/tools/spellcheck.js.map
packages/tools/tagServerLatest.d.ts
packages/tools/tagServerLatest.js
packages/tools/tagServerLatest.js.map
@@ -1999,9 +2086,21 @@ packages/tools/website/build.js.map
packages/tools/website/updateDownloadPage.d.ts
packages/tools/website/updateDownloadPage.js
packages/tools/website/updateDownloadPage.js.map
packages/tools/website/updateNews.d.ts
packages/tools/website/updateNews.js
packages/tools/website/updateNews.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
+4 -2
View File
@@ -38,6 +38,8 @@ 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"
@@ -169,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
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
+23
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? If you require support or are requesting an enhancement or feature then please create a topic on the [Joplin forum](https://discourse.joplinapp.org/). 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
+18 -5
View File
@@ -5,7 +5,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-2016]
# Removed windows-2016 for now - discontinued by GitHub
os: [macos-latest, ubuntu-latest, windows-2019]
steps:
# Silence apt-get update errors (for example when a module doesn't
@@ -86,10 +87,22 @@ jobs:
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 }}
@@ -128,5 +141,5 @@ jobs:
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
+99
View File
@@ -138,6 +138,9 @@ packages/app-desktop/checkForUpdates.js.map
packages/app-desktop/commands/copyDevCommand.d.ts
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyDevCommand.js.map
packages/app-desktop/commands/editProfileConfig.d.ts
packages/app-desktop/commands/editProfileConfig.js
packages/app-desktop/commands/editProfileConfig.js.map
packages/app-desktop/commands/exportFolders.d.ts
packages/app-desktop/commands/exportFolders.js
packages/app-desktop/commands/exportFolders.js.map
@@ -165,6 +168,18 @@ packages/app-desktop/commands/startExternalEditing.js.map
packages/app-desktop/commands/stopExternalEditing.d.ts
packages/app-desktop/commands/stopExternalEditing.js
packages/app-desktop/commands/stopExternalEditing.js.map
packages/app-desktop/commands/switchProfile.d.ts
packages/app-desktop/commands/switchProfile.js
packages/app-desktop/commands/switchProfile.js.map
packages/app-desktop/commands/switchProfile1.d.ts
packages/app-desktop/commands/switchProfile1.js
packages/app-desktop/commands/switchProfile1.js.map
packages/app-desktop/commands/switchProfile2.d.ts
packages/app-desktop/commands/switchProfile2.js
packages/app-desktop/commands/switchProfile2.js.map
packages/app-desktop/commands/switchProfile3.d.ts
packages/app-desktop/commands/switchProfile3.js
packages/app-desktop/commands/switchProfile3.js.map
packages/app-desktop/commands/toggleExternalEditing.d.ts
packages/app-desktop/commands/toggleExternalEditing.js
packages/app-desktop/commands/toggleExternalEditing.js.map
@@ -207,6 +222,9 @@ packages/app-desktop/gui/Dialog.js.map
packages/app-desktop/gui/DialogButtonRow.d.ts
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow.js.map
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.d.ts
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js.map
packages/app-desktop/gui/DialogTitle.d.ts
packages/app-desktop/gui/DialogTitle.js
packages/app-desktop/gui/DialogTitle.js.map
@@ -225,6 +243,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
@@ -246,6 +267,9 @@ packages/app-desktop/gui/KeymapConfig/utils/useKeymap.js.map
packages/app-desktop/gui/MainScreen/MainScreen.d.ts
packages/app-desktop/gui/MainScreen/MainScreen.js
packages/app-desktop/gui/MainScreen/MainScreen.js.map
packages/app-desktop/gui/MainScreen/commands/addProfile.d.ts
packages/app-desktop/gui/MainScreen/commands/addProfile.js
packages/app-desktop/gui/MainScreen/commands/addProfile.js.map
packages/app-desktop/gui/MainScreen/commands/commandPalette.d.ts
packages/app-desktop/gui/MainScreen/commands/commandPalette.js
packages/app-desktop/gui/MainScreen/commands/commandPalette.js.map
@@ -477,6 +501,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
@@ -522,6 +552,9 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
packages/app-desktop/gui/NoteList/commands/index.d.ts
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/commands/index.js.map
packages/app-desktop/gui/NoteList/types.d.ts
packages/app-desktop/gui/NoteList/types.js
packages/app-desktop/gui/NoteList/types.js.map
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
@@ -711,6 +744,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
@@ -762,6 +798,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/restart.d.ts
packages/app-desktop/services/restart.js
packages/app-desktop/services/restart.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.js.map
@@ -1020,6 +1059,9 @@ packages/lib/database.js.map
packages/lib/debug/DebugService.d.ts
packages/lib/debug/DebugService.js
packages/lib/debug/DebugService.js.map
packages/lib/dom.d.ts
packages/lib/dom.js
packages/lib/dom.js.map
packages/lib/dummy.test.d.ts
packages/lib/dummy.test.js
packages/lib/dummy.test.js.map
@@ -1050,6 +1092,9 @@ packages/lib/fs-driver-node.js.map
packages/lib/fsDriver.test.d.ts
packages/lib/fsDriver.test.js
packages/lib/fsDriver.test.js.map
packages/lib/geolocation-node.d.ts
packages/lib/geolocation-node.js
packages/lib/geolocation-node.js.map
packages/lib/hooks/useAsyncEffect.d.ts
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useAsyncEffect.js.map
@@ -1137,6 +1182,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
@@ -1221,6 +1269,9 @@ packages/lib/services/DecryptionWorker.js.map
packages/lib/services/ExternalEditWatcher.d.ts
packages/lib/services/ExternalEditWatcher.js
packages/lib/services/ExternalEditWatcher.js.map
packages/lib/services/ExternalEditWatcher/utils.d.ts
packages/lib/services/ExternalEditWatcher/utils.js
packages/lib/services/ExternalEditWatcher/utils.js.map
packages/lib/services/ItemChangeUtils.d.ts
packages/lib/services/ItemChangeUtils.js
packages/lib/services/ItemChangeUtils.js.map
@@ -1545,6 +1596,24 @@ packages/lib/services/plugins/utils/validatePluginVersion.js.map
packages/lib/services/plugins/utils/validatePluginVersion.test.d.ts
packages/lib/services/plugins/utils/validatePluginVersion.test.js
packages/lib/services/plugins/utils/validatePluginVersion.test.js.map
packages/lib/services/profileConfig/index.d.ts
packages/lib/services/profileConfig/index.js
packages/lib/services/profileConfig/index.js.map
packages/lib/services/profileConfig/index.test.d.ts
packages/lib/services/profileConfig/index.test.js
packages/lib/services/profileConfig/index.test.js.map
packages/lib/services/profileConfig/initProfile.d.ts
packages/lib/services/profileConfig/initProfile.js
packages/lib/services/profileConfig/initProfile.js.map
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.d.ts
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.js
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.js.map
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.d.ts
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.js
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.js.map
packages/lib/services/profileConfig/types.d.ts
packages/lib/services/profileConfig/types.js
packages/lib/services/profileConfig/types.js.map
packages/lib/services/rest/Api.d.ts
packages/lib/services/rest/Api.js
packages/lib/services/rest/Api.js.map
@@ -1623,6 +1692,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
@@ -1917,6 +1989,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
@@ -1935,6 +2010,12 @@ packages/tools/buildServerDocker.js.map
packages/tools/buildServerDocker.test.d.ts
packages/tools/buildServerDocker.test.js
packages/tools/buildServerDocker.test.js.map
packages/tools/checkLibPaths.d.ts
packages/tools/checkLibPaths.js
packages/tools/checkLibPaths.js.map
packages/tools/checkLibPaths.test.d.ts
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.test.js.map
packages/tools/convertThemesToCss.d.ts
packages/tools/convertThemesToCss.js
packages/tools/convertThemesToCss.js.map
@@ -1947,6 +2028,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
@@ -1968,6 +2052,9 @@ packages/tools/release-server.js.map
packages/tools/setupNewRelease.d.ts
packages/tools/setupNewRelease.js
packages/tools/setupNewRelease.js.map
packages/tools/spellcheck.d.ts
packages/tools/spellcheck.js
packages/tools/spellcheck.js.map
packages/tools/tagServerLatest.d.ts
packages/tools/tagServerLatest.js
packages/tools/tagServerLatest.js.map
@@ -1989,9 +2076,21 @@ packages/tools/website/build.js.map
packages/tools/website/updateDownloadPage.d.ts
packages/tools/website/updateDownloadPage.js
packages/tools/website/updateDownloadPage.js.map
packages/tools/website/updateNews.d.ts
packages/tools/website/updateNews.js
packages/tools/website/updateNews.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
+62 -5
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: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

@@ -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"
@@ -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
@@ -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">
@@ -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">
@@ -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}}
@@ -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>
@@ -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>
@@ -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>
@@ -0,0 +1 @@
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500"><i class="fab fa-twitter"></i></a>
+12 -2
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>
+5 -4
View File
@@ -18,14 +18,15 @@ There are also a few forks of existing packages under the "fork-*" name.
## Required dependencies
- Install node 16+ - https://nodejs.org/en/
- [Enable yarn](https://yarnpkg.com/getting-started/install): `corepack enable`
- 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
+37 -16
View File
@@ -38,7 +38,8 @@ If you want to start contributing to the project's code, please follow these gui
- **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.
+55 -76
View File
@@ -1,96 +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 .
COPY --chown=$user:$user gulpfile.js .
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. We can't just
# run `yarn run build` because that wouldn't run the postinstall scripts in
# dependencies (for example the sqlite3 native module would not be built). So
# instead we run `yarn install`, which is going to install again all the
# packages (but because it's already done it should be fast), and then run the
# postinstall scripts, as well as build scripts.
RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds
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}
# Not clear what's the equivalent of "--prefix" in Yarn 3, so keep using npm for
# now.
CMD [ "npm", "--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
+2 -1
View File
@@ -219,7 +219,8 @@ then
Type=Application
Categories=Office;
MimeType=x-scheme-handler/joplin;
X-GNOME-SingleWindow=true
X-GNOME-SingleWindow=true // should be removed eventually as it was upstream to be an XDG specification
SingleMainWindow=true
EOF
# Update application icons
+5
View File
@@ -10,6 +10,11 @@ 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,
+76 -85
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,23 +55,22 @@ 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 -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a>
<!-- SPONSORS-ORG -->
* * *
@@ -75,9 +80,10 @@ 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/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/77214738?s=96&v=4"/></br>[Polymathic-Company](https://github.com/Polymathic-Company) | <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 -->
@@ -125,21 +131,24 @@ 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)
- [End-to-end encryption spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.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)
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
- [Search Sorting spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/search_sorting.md)
- [E2EE: Technical spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [E2EE: Workflow](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee/workflow.md)
- [Server: File URL Format](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_file_url_format.md)
- [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,7 +309,6 @@ 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",
@@ -409,9 +417,10 @@ Important: userstyle.css and userchrome.css are provided for your convenience, b
# Plugins
The **desktop app** has the ability to extend beyond its standard functionality by the way of plugins. These plugins adhere to the Joplin plugin API and can be installed & configured within the application via the `Plugins` page in the [Configuration screen](https://github.com/laurent22/joplin/blob/dev/readme/config_screen.md). This menu allows the manual installation of the plugin using the single 'Joplin Plugin Archive' (*.jpl) file. Once the application is reloaded the plugins will appear within the plugins menu where they can be toggled on/off or removed entirely.
The **desktop app** has the ability to extend beyond its standard functionality by the way of plugins. These plugins adhere to the Joplin [plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html) and can be installed & configured within the application via the `Plugins` page of the [Configuration screen](https://github.com/laurent22/joplin/blob/dev/readme/config_screen.md).
Plugins are currently maintained by the community in the [Joplin Discourse 'plugins' category](https://discourse.joplinapp.org/c/plugins/18).
From this menu you can search for plugins uploaded to the [Joplin plugins](https://github.com/joplin/plugins) repository as well as manual installation of plugins using a 'Joplin Plugin Archive' (*.jpl) file.
Once the application is reloaded the plugins will appear within the plugins menu where they can be toggled on/off or removed entirely.
For more information see [Plugins](https://github.com/laurent22/joplin/blob/dev/readme/plugins.md)
@@ -473,24 +482,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.
@@ -531,47 +522,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) | 89%
<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 | 25%
<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) | 64%
<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) | | 50%
<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) | 98%
<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) | 100%
<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) | 86%
<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:pueblos_spatulas@aleeas.com) | 100%
<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) | | 49%
<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) | 98%
<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 | 28%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 98%
<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) | 32%
<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) | 88%
<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) | 86%
<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) | 86%
<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) | | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 98%
<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) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 98%
<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) | 81%
<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) | 90%
<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) | 98%
<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) | | 87%
<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) | 96%
<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) | 80%
<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) | 98%
<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) | 89%
<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) | | 72%
<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:paventyang@gmail.com) | 98%
<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) | 87%
<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) | 100%
<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) | 86%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors
+958
View File
@@ -0,0 +1,958 @@
{
"version": "0.2",
"language": "en_GB",
"ignorePaths": [
"**/*.min.*",
"**/*.svg",
"/_mydocs",
"/_releases",
"/.git",
"/.yarn",
"/Assets",
"/packages/app-cli/app/fuzzing.js",
"/packages/app-cli/build",
"/packages/app-cli/tests/support",
"/packages/app-cli/tests/test data",
"/packages/app-cli/tests/tmp",
"/packages/app-clipper/content_scripts/JSDOMParser.js",
"/packages/app-clipper/content_scripts/Readability-readerable.js",
"/packages/app-clipper/content_scripts/Readability.js",
"/packages/app-clipper/popup/build/js/0.chunk.js",
"/packages/app-clipper/popup/build/js/bundle.js",
"/packages/app-clipper/popup/build/js/main.chunk.js",
"/packages/app-clipper/popup/build/js/main.chunk.js",
"/packages/app-clipper/popup/config",
"/packages/app-desktop/vendor/",
"/packages/app-mobile/pluginAssets",
"/packages/fork-sax/examples",
"/packages/fork-sax/lib/sax.js",
"/packages/fork-sax/test",
"/packages/fork-uslug",
"/packages/lib/locales",
"/packages/lib/mime-utils-types.js",
"/packages/lib/parameters.js",
"/packages/lib/plugin_types",
"/packages/lib/resourceUtils.js",
"/packages/lib/services/joplinServer/personalizedUserContentBaseUrl.ts",
"/packages/lib/vendor",
"/packages/lib/welcomeAssets.js",
"/packages/turndown-plugin-gfm/config",
"/packages/turndown/config",
"node_modules"
],
"words": [
"aàáâãäåāą",
"AÀÁÂÃÄÅĀĄ",
"abbrev",
"ABCDEFGHIJ",
"Abhishek",
"Abkhazian",
"accel",
"accomodate",
"accum",
"actualkeyword",
"adata",
"advlist",
"AGSFE",
"Aland",
"Åland",
"alertbanner",
"Allaire",
"alse",
"altool",
"aman",
"ambrt",
"Amharic",
"amothc",
"andrejilderda",
"anki",
"Antarctique",
"antarctiques",
"Antártico",
"anymore",
"apidoc",
"appiconset",
"applewebkit",
"approot",
"arableague",
"Aragonés",
"ARITIM",
"armeabi",
"asterix",
"atest",
"atestb",
"attribname",
"attribvalue",
"authcode",
"autocompleteitem",
"autocompleter",
"Autocompleter",
"AUTOEXEC",
"autohide",
"Avenir",
"Ayiti",
"azamah",
"Azərbaycan",
"backoff",
"Bangla",
"Bêafrîka",
"beforeinput",
"België",
"Belgien",
"Belgique",
"Bénin",
"Bhutani",
"bibtex",
"Bihari",
"Bislama",
"blabla",
"blablablabla",
"boohay",
"Bosna",
"Bouvet",
"Bouvetøya",
"browserslist",
"bthqu",
"btns",
"Bulibiya",
"bullist",
"bulma",
"Byelorussian",
"calebjohn",
"Calédonie",
"Caligraphic",
"callsites",
"Cameroun",
"cantdothat",
"Capslock",
"cardcontainer",
"cardimage",
"cardmenuitem",
"cardtext",
"Caribisch",
"CAUTOEXEC",
"cçćč",
"CÇĆČ",
"cdataend",
"cdatastart",
"cdot",
"ceaf",
"centrafricaine",
"Centrafrican",
"Česká",
"changedtitle",
"charcodes",
"checkboxclick",
"checkmark",
"chemfive",
"choiceitem",
"chromedriver",
"chromeframe",
"chromeos",
"Città",
"Cmds",
"codepoint",
"colorinput",
"colorpicker",
"colorswatch",
"colspan",
"committerdate",
"commmmmand",
"commonmark",
"COMMONMARK",
"Comores",
"compositionend",
"compositionstart",
"compositionupdate",
"conflicter",
"contenteditable",
"contextform",
"contextformbutton",
"contextformtogglebutton",
"contextkey",
"contexttoolbar",
"continuelist",
"Contrl",
"Conv",
"convo",
"copytags",
"cozic",
"Cozic",
"createdb",
"Creds",
"Crna",
"cronspec",
"cros",
"crypted",
"Curaçao",
"curso",
"customeditor",
"customkeymap",
"cyingfan",
"d'Ivoire",
"Danmark",
"Dansk",
"dataimg",
"datauri",
"Datauri",
"datetime",
"Datetime",
"davris",
"dbuuid",
"DDTHH",
"deflist",
"deinit",
"Démocratique",
"deselector",
"deuxième",
"dflt",
"dialogbox",
"dialogs",
"Dialogs",
"DIALOGS",
"Dicourse",
"Distill",
"dists",
"docid",
"docsize",
"doctypes",
"doesnotwork",
"doesntexist",
"doesntlookright",
"domelementtype",
"domhandler",
"Dominicana",
"domutils",
"DONATELINKS",
"downarrow",
"dragdrop",
"draggesture",
"dünn",
"dylib",
"dynamiclib",
"ecuatorial",
"eèéêëěēę",
"EÈÉÊËĚĒĘ",
"Eesti",
"effet",
"efgh",
"égalité",
"Éire",
"elem",
"elementpath",
"elems",
"ellipsize",
"ELOCKED",
"encryptable",
"endregion",
"enex",
"Enex",
"ENEX",
"enumber",
"eqeqeq",
"équatoriale",
"Erro",
"errorish",
"escapeplus",
"eslintignore",
"España",
"étiquette",
"EUNSPECIFIED",
"eventname",
"evermeet",
"evernote",
"Evernote",
"execa",
"expando",
"expirable",
"Expirable",
"expval",
"Færøerne",
"Fahrräder",
"FAILSAFE",
"fallbacks",
"fancymenuitem",
"fancytype",
"favorites",
"Fiber",
"filepicker",
"folderid",
"foldl",
"fontawesome",
"fontface",
"forall",
"forcewake",
"Føroyar",
"fortawesome",
"française",
"françaises",
"Gabuutih",
"gedit",
"geoip",
"Geoip",
"geoloc",
"geoplugin",
"getlastmodified",
"gettext",
"githubusercontent",
"Gora",
"gotchas",
"gradlew",
"Grønland",
"grouptoolbarbutton",
"Gruber",
"gsoc",
"gttest",
"Guåhån",
"guarentee",
"guarentees",
"Guiena",
"Guiné",
"Guinée",
"gulpfile",
"Guyane",
"gvim",
"Haïti",
"hanlder",
"Hausa",
"headerless",
"Heisenbug",
"Hercegovina",
"hift",
"highjack",
"highlited",
"historyhas",
"HMRKG",
"hoge",
"homenote",
"hotfolder",
"Howver",
"Hrvatska",
"htmlentities",
"htmlfile",
"htmlpack",
"htmlpanel",
"ʻĀirani",
"icns",
"iconset",
"iconutil",
"Iforgot",
"iframes",
"ihack",
"iife",
"iìíîïī",
"IÌÍÎÏĪ",
"ijkl",
"imagelink",
"imagetools",
"immer",
"iname",
"Incl",
"infint",
"inflim",
"infty",
"innaccurate",
"inputi",
"inserttable",
"Interlingue",
"Interp",
"interupting",
"Inteval",
"Inuktitut",
"Inupiak",
"Invididual",
"IOERR",
"Ionicons",
"IPHONEOS",
"ipify",
"ipwhois",
"iscompleted",
"Ísland",
"Italiano",
"Itoophiyaa",
"itsgone",
"itunes",
"Jabuuti",
"jackgruber",
"joeattardi",
"jopext",
"joplinapp",
"JOPLINAPP",
"joplincloud",
"joplindev",
"JOPLINMOD",
"joplintest",
"jsbundles",
"justtesting",
"Kalaallit",
"kalba",
"kanban",
"Kashmiri",
"katex",
"keychain",
"keycodes",
"keymaps",
"keytar",
"Kibris",
"Kinyarwanda",
"Kirundi",
"Ködörösêse",
"Komori",
"Kpck",
"Kūki",
"Laothian",
"lastmod",
"Latvija",
"lcov",
"leaft",
"leftarrow",
"leftequilibrium",
"leftrightarrow",
"Lettish",
"Lëtzebuerg",
"Levithan",
"Liban",
"libz",
"Lietuva",
"Lietuvių",
"lineheight",
"Lingala",
"linkg",
"linkurl",
"listbox",
"listfile",
"listpreview",
"loglevel",
"longclick",
"longpress",
"longpresscancel",
"looooooong",
"ltrim",
"Luxemburg",
"Maarten",
"Madagasikara",
"Magyarország",
"majax",
"Mardown",
"markdowncalc",
"Maroc",
"MASTERKEY",
"matchinfo",
"mathchoice",
"mathjax",
"Mathjax",
"MATHJAX",
"mathllap",
"mathml",
"mathrlap",
"mathrm",
"Mauritanie",
"Maxiumm",
"Mayen",
"mchem",
"mechanim",
"mediumtext",
"menubutton",
"mergeff",
"Metadatas",
"México",
"mhchem",
"middlewares",
"migth",
"mkbook",
"MKCOL",
"mkdirp",
"mknote",
"mktodo",
"MMYY",
"modifié",
"monokai",
"MONOSPACE",
"msgctxt",
"msgfmt",
"msgmerge",
"msgstr",
"msleep",
"mtext",
"mult",
"multicursor",
"multimarkdown",
"multimd",
"multistatus",
"multitable",
"mybucket",
"mydir",
"myfile",
"mynote",
"myplugin",
"mytag",
"mytaga",
"mytagb",
"mytagc",
"mytagd",
"mytest",
"mytoken",
"myvalue",
"nanoid",
"Neaus",
"Nederlands",
"nestedmenuitem",
"newone",
"Nextcloud",
"njstrace",
"nñňń",
"NÑŇŃ",
"NOCASE",
"nodechange",
"nodir",
"noexpand",
"nojs",
"nolongershared",
"nonlatin",
"NONLATIN",
"Noreg",
"Norge",
"nospecialcharacters",
"notanumber",
"notarization",
"notetags",
"Notif",
"notindexed",
"notthere",
"nounce",
"Nounce",
"Nounces",
"npmignore",
"numadd",
"numbersareok",
"numdec",
"numdiv",
"numlist",
"Numlock",
"nummult",
"numsub",
"Nunaat",
"obelix",
"odata",
"ohno",
"OHNO",
"oldppk",
"onattribdata",
"onattribend",
"onattribname",
"onattribute",
"oncdata",
"oncdataend",
"oncdatastart",
"onclosetag",
"oncomment",
"oncommentend",
"ondeclaration",
"onedrive",
"onelink",
"onformat",
"onmatch",
"onopentag",
"onopentagend",
"onopentagname",
"onparserinit",
"onprocessinginstruction",
"onselfclosingtag",
"ontext",
"oòóôõöøō",
"OÒÓÔÕÖØŌ",
"opentag",
"opentagname",
"Opptionn",
"orignal",
"Oromo",
"Österreich",
"otherpackage",
"outdented",
"overidding",
"overriden",
"padd",
"pandoc",
"paperclip",
"passthrough",
"Päth",
"Pbuild",
"pbxproj",
"pcmag",
"pcnalx",
"pddv",
"Pehr",
"Percents",
"père",
"Perú",
"pfff",
"PGPASSWORD",
"pidfile",
"PLUGINLEGACY",
"pocount",
"Polska",
"Polski",
"Polynésie",
"Português",
"Potoczny's",
"powerpoint",
"Prakash",
"precommit",
"pred",
"preg",
"prerelease",
"Prerelease",
"presigner",
"prettycron",
"pricetag",
"Príncipe",
"privkey",
"processinginstruction",
"programatically",
"propfind",
"PROPFIND",
"propname",
"propstat",
"protcol",
"pseudoclass",
"pseudos",
"Pushto",
"quot",
"qwer",
"raisebox",
"rbga",
"readerable",
"Readerable",
"READERABLE",
"Redownload",
"reencrypt",
"reencrypted",
"Reencrypting",
"reencrypts",
"regexes",
"Regexs",
"Relavent",
"relayouted",
"rels",
"renamings",
"renderered",
"Renderered",
"República",
"republika",
"République",
"requestheaders",
"resourcetype",
"resynced",
"Rhaeto",
"rightarrow",
"rightequilibrium",
"rightleftarrows",
"rightleftharpoons",
"rmbook",
"rmnote",
"rmusin",
"rnfs",
"RNFS",
"robocopy",
"Roboto",
"România",
"roule",
"rowid",
"ROWID",
"ROWIDs",
"rowspan",
"rseidelsohn",
"rtrim",
"safeext",
"salut",
"Sangho",
"sasss",
"SAVEPOINT",
"schtroumpf",
"Schweiz",
"Scrolllock",
"scrollmap",
"seafdav",
"Seafile",
"searchengine",
"searchlimit",
"SEARCHOVERLAY",
"securerandom",
"segdir",
"selectbox",
"Sénégal",
"Serializers",
"setext",
"settingschema",
"shantanugoel",
"sharee",
"Shiftt",
"Shoft",
"shouldntendwithit",
"shouldstartwiththis",
"Shqip",
"Shqipëria",
"Sicen",
"simplemath",
"Siswati",
"sizeinput",
"SJCL",
"Slovenčina",
"Slovenija",
"Slovensko",
"softbreaks",
"Solarised",
"SOLARIZED",
"someid",
"somewhereelse",
"sourceurl",
"SPACEBAR",
"spaceno",
"Spacify",
"spdfgh",
"spellfix",
"sphemy",
"splitbutton",
"sprintf",
"sqlts",
"srcfolder",
"SSSZ",
"starttls",
"Starttls",
"stepsize",
"stevenlevithan",
"stex",
"stilltryingtohack",
"strack",
"Stringifiable",
"subdir",
"subl",
"sublty",
"sucessful",
"Suomi",
"Sūriyya",
"Svenska",
"Sverige",
"svgs",
"Svizra",
"Svizzera",
"synclock",
"synclog",
"syswide",
"syswidecas",
"taboverride",
"tabpanel",
"taga",
"tagb",
"tagc",
"Tajik",
"takesover",
"targetfolder",
"Tchad",
"Teardown",
"termi",
"termutils",
"Terres",
"Tessarek",
"tessus",
"Testb",
"testcreate",
"testexportfolder",
"testingconnection",
"testingkeychain",
"testunit",
"texify",
"Texify",
"textareas",
"textexportnote",
"textstyle",
"thaaaaaaan",
"thatsok",
"thatsreallylongthatsreallylongthatsreallylongthats",
"thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong",
"Thevenard",
"thisisfine",
"Thmmss",
"Tiếng",
"Tigrinya",
"tiiitlllle",
"tinymce",
"titi",
"titletitle",
"tkwidget",
"tkwidgets",
"Todos",
"togglebutton",
"togglemenuitem",
"toolip",
"tooshort",
"treymo",
"tripledash",
"tsmerge",
"Tsonga",
"tttest",
"Tunisie",
"Türkçe",
"Türkiye",
"Türkmenistan",
"turndown",
"Turndown",
"TWCO",
"typeahead",
"Typora",
"tzip",
"uastring",
"uglifycss",
"uglifyjs",
"Uighur",
"unconflicted",
"underbrace",
"Undos",
"unescaping",
"unhighlighted",
"unixlike",
"unmocked",
"unserialize",
"unserialized",
"unserializes",
"unserializing",
"unshares",
"unsharing",
"unusued",
"uparrow",
"uphy",
"urlconverter",
"urlinput",
"userchrome",
"usercontent",
"userstyle",
"uslug",
"Ustd",
"utems",
"uuidgen",
"uuidv",
"uùúûüůū",
"UÙÚÛÜŮŪ",
"valign",
"Valign",
"vars",
"Vars",
"Vaticano",
"vers",
"verylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongid",
"veryverylongclientidveryverylongclientidveryverylongclientidveryverylongclientid",
"veryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitle",
"Việt",
"viewbox",
"Volapuk",
"Volívia",
"votearrow",
"webclipper",
"webdav",
"webfonts",
"whitespaces",
"widder",
"Wifi",
"Withflags",
"Wolof",
"WONTFIX",
"wrongclienttype",
"wrongpassword",
"wrongtype",
"Wuliwya",
"wxyz",
"xcallbackurl",
"xcassets",
"xcodeproj",
"xcrun",
"xgettext",
"xlink",
"xzvf",
"yarg",
"yosay",
"yufzkns",
"YYYYMMDDTHH",
"zxcvbn",
"zžżź",
"ZŽŻŹ",
"Ελλάδα",
"Ελληνικά",
"Κύπρος",
"Агентство",
"Антарктике",
"Беларусь",
"България",
"Гора",
"језик",
"Казахстан",
"Киргизия",
"Книги",
"Кыргызстан",
"Қазақстан",
"Македонија",
"Молдавия",
"Монгол",
"номер",
"рейтер",
"Рейтер",
"Россия",
"Русский",
"Северна",
"сообщило",
"СООБЩИЛО",
"Србија",
"српски",
"Україна",
"Црна",
"საქართველო",
"Հայաստան",
"ישראל",
"עיברית",
"إرتريا",
"اسلامي",
"اسلامی",
"افغانستان",
"الأُرْدُن",
"الإمارات",
"البحرين",
"الجزائر",
"السعودية",
"السودان",
"الصومال",
"العراق",
"العربيّة",
"ﺍﻟﻘﻤﺮي",
"الكويت",
"المتّحدة",
"المغرب",
"اليَمَن",
"ایران",
"پاکستان",
"تشاد",
"تونس",
"جمهوری",
"جيبوتي",
"دولة",
"دولتدولت",
"سلطنة",
"سوريا",
"عُمان",
"لبنان",
"ليبيا",
"موريتانيا",
"ⵍⵎⵖⵔⵉⴱ",
"ኢትዮጵያ",
"ኤርትራ",
"भारत",
"গণপ্রজাতন্ত্রী",
"লাদেশ",
"இலங்கை",
"ලංකා",
"คือค",
"คือคนไทย",
"ประเทศไทย",
"ປະຊາຊົນລາວ",
"မြန်မာ",
"កម្ពុជា"
]
}
+13 -2
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'
@@ -0,0 +1 @@
eine Notiz- und Aufgaben-App mit Sync zwischen Linux, macOS, Windows
@@ -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

@@ -0,0 +1 @@
a note taking and to-do app with sync between Linux, macOS, Windows, and mobile
+2
View File
@@ -12,6 +12,8 @@ module.exports = {
// '**/*.ts?(x)': () => 'npm run tsc',
'*.{js,jsx,ts,tsx}': [
'yarn run linter-precommit',
'yarn run checkLibPaths',
'yarn run spellcheck',
'git add',
],
};
+6 -1
View File
@@ -18,9 +18,11 @@
"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/",
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
"updateNews": "node ./packages/tools/website/updateNews",
"buildSettingJsonSchema": "yarn workspace joplin start settingschema ../../../joplin-website/docs/schema/settings.json",
"buildTranslations": "node packages/tools/build-translation.js",
"buildWebsite": "node ./packages/tools/website/build.js && yarn run buildPluginDoc && yarn run buildSettingJsonSchema",
"checkLibPaths": "node ./packages/tools/checkLibPaths.js",
"circularDependencyCheck": "madge --warning --circular --extensions js ./",
"clean": "npm run clean --workspaces --if-present && node packages/tools/clean && yarn cache clean",
"dependencyTree": "madge",
@@ -31,7 +33,7 @@
"linter": "eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"postinstall": "gulp build",
"publishAll": "git pull && yarn run buildParallel && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
"releaseAndroid": "yarn run buildParallel && export PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" && node packages/tools/release-android.js",
"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",
@@ -40,6 +42,8 @@
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
"releasePluginRepoCli": "node packages/tools/release-plugin-repo-cli.js",
"releaseServer": "node packages/tools/release-server.js",
"cspell": "cspell",
"spellcheck": "node packages/tools/spellcheck.js",
"tagServerLatest": "node packages/tools/tagServerLatest.js",
"buildServerDocker": "node packages/tools/buildServerDocker.js",
"setupNewRelease": "node ./packages/tools/setupNewRelease",
@@ -59,6 +63,7 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.6.0",
"cspell": "^5.20.0",
"eslint": "^7.6.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-react": "^7.18.0",
+13
View File
@@ -313,8 +313,16 @@ 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('To **update** the resource content, you can make a PUT request with the same arguments:');
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('Or if you only need to update the resource properties (title, etc.), without changing the content, you can make a regular PUT request:');
lines.push('');
lines.push('\tcurl -X PUT --data \'{"title": "My new title"}\' http://localhost:41184/resources/8fe1417d7b184324bf6b0122b76c4696');
lines.push('');
lines.push('**From a plugin** the syntax to create a resource is also a bit special:');
lines.push('');
lines.push('```javascript');
@@ -368,6 +376,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`);
+1 -1
View File
@@ -45,7 +45,7 @@ while [ "$NUM" -lt 400 ]; do
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 10" >> "$CMD_FILE"
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.10.password hunter1hunter2hunter3" >> "$CMD_FILE"
echo "config sync.10.password 111111" >> "$CMD_FILE"
echo "sync" >> "$CMD_FILE"
yarn start --profile "$PROFILE_DIR" batch "$CMD_FILE"
+4 -4
View File
@@ -33,14 +33,14 @@
],
"owner": "Laurent Cozic"
},
"version": "2.7.0",
"version": "2.8.1",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "~2.7",
"@joplin/renderer": "~2.7",
"@joplin/lib": "~2.8",
"@joplin/renderer": "~2.8",
"aws-sdk": "^2.588.0",
"chalk": "^4.1.0",
"compare-version": "^0.1.2",
@@ -67,7 +67,7 @@
"yargs-parser": "^7.0.0"
},
"devDependencies": {
"@joplin/tools": "~2.7",
"@joplin/tools": "~2.8",
"@types/fs-extra": "^9.0.6",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",
+17 -13
View File
@@ -60,16 +60,20 @@ describe('MdToHtml', function() {
actualHtml = actualHtml.replace(/\r?\n/g, '\n');
if (actualHtml !== expectedHtml) {
console.info('');
console.info(`Error converting file: ${mdFilename}`);
console.info('--------------------------------- Got:');
console.info(actualHtml);
console.info('--------------------------------- Raw:');
console.info(actualHtml.split('\n'));
console.info('--------------------------------- Expected:');
console.info(expectedHtml.split('\n'));
console.info('--------------------------------------------');
console.info('');
const msg: string[] = [
'',
`Error converting file: ${mdFilename}`,
'--------------------------------- Got:',
actualHtml,
'--------------------------------- Raw:',
actualHtml.split('\n'),
'--------------------------------- Expected:',
expectedHtml.split('\n'),
'--------------------------------------------',
'',
];
console.info(msg.join('\n'));
expect(false).toBe(true);
// return;
@@ -242,9 +246,9 @@ describe('MdToHtml', function() {
{
const input = '# Head\nFruits\n- Apple\n';
const result = await mdToHtml.render(input, null, { bodyOnly: true, mapsToLine: true });
expect(result.html.trim()).toBe('<h1 id="head" class="maps-to-line" source-line="0">Head</h1>\n' +
'<p class="maps-to-line" source-line="1">Fruits</p>\n' +
'<ul>\n<li class="maps-to-line" source-line="2">Apple</li>\n</ul>'
expect(result.html.trim()).toBe('<h1 id="head" class="maps-to-line" source-line="0" source-line-end="1">Head</h1>\n' +
'<p class="maps-to-line" source-line="1" source-line-end="2">Fruits</p>\n' +
'<ul>\n<li class="maps-to-line" source-line="2" source-line-end="3">Apple</li>\n</ul>'
);
}
}));
@@ -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">
@@ -0,0 +1,4 @@
![](:/39e66095b2cd427e9f13464487514d2e)
![](:/39e66095b2cd427e9f13464487514d2e "some-title")
![some-alt-text](:/39e66095b2cd427e9f13464487514d2e)
![some-alt-text](:/39e66095b2cd427e9f13464487514d2e "some-title")
Whitespace-only changes.
@@ -0,0 +1 @@
<iframe src=""><svg><style><img src="" onerror=this.onerror=confirm('vulnerable_to_XSS')
@@ -334,6 +334,12 @@ export enum SettingItemType {
Button = 6,
}
export enum SettingItemSubType {
FilePathAndArgs = 'file_path_and_args',
FilePath = 'file_path', // Not supported on mobile!
DirectoryPath = 'directory_path', // Not supported on mobile!
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
@@ -350,6 +356,7 @@ export enum SettingStorage {
export interface SettingItem {
value: any;
type: SettingItemType;
subType?: SettingItemSubType;
label: string;
description?: string;
@@ -1,5 +1,5 @@
import joplin from 'api';
import { SettingItemType, ToolbarButtonLocation } from 'api/types';
import { SettingItemSubType, SettingItemType, ToolbarButtonLocation } from 'api/types';
joplin.plugins.register({
onStart: async function() {
@@ -49,6 +49,33 @@ joplin.plugins.register({
description: 'This setting will be saved to settings.json',
['storage' as any]: 2, // Should be `storage: SettingStorage.File`
},
'myFilePathAndArgs': {
value: '',
type: SettingItemType.String,
subType: SettingItemSubType.FilePathAndArgs,
section: 'myCustomSection',
public: true,
label: 'File path and args',
},
'myFilePathOnly': {
value: '',
type: SettingItemType.String,
subType: SettingItemSubType.FilePath,
section: 'myCustomSection',
public: true,
label: 'File path',
},
'myDirectory': {
value: '',
type: SettingItemType.String,
subType: SettingItemSubType.DirectoryPath,
section: 'myCustomSection',
public: true,
label: 'Directory path',
},
});
await joplin.commands.register({
+1 -1
View File
@@ -44,7 +44,7 @@ const processUser = async (userNum: number) => {
try {
const userEmail = `user${userNum}@example.com`;
const userPassword = 'hunter1hunter2hunter3';
const userPassword = '111111';
const commandFile = `${tempDir}/populateDatabase-${userNum}.txt`;
const profileDir = `${homedir()}/.config/joplindev-populate/joplindev-testing-${userNum}`;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper [DEV]",
"version": "2.7.0",
"version": "2.8.0",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",
+23 -2
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>
);
+3 -1
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 } });
+4 -4
View File
@@ -192,7 +192,7 @@ export default class ElectronAppWrapper {
// We got the response from the renderer process:
// save the response and try quit again.
this.rendererProcessQuitReply_ = args;
this.electronApp_.quit();
this.quit();
}
});
@@ -253,7 +253,7 @@ export default class ElectronAppWrapper {
});
}
async quit() {
quit() {
this.electronApp_.quit();
}
@@ -325,7 +325,7 @@ export default class ElectronAppWrapper {
if (!gotTheLock) {
// Another instance is already running - exit
this.electronApp_.quit();
this.quit();
return true;
}
@@ -362,7 +362,7 @@ export default class ElectronAppWrapper {
});
this.electronApp_.on('window-all-closed', () => {
this.electronApp_.quit();
this.quit();
});
this.electronApp_.on('activate', () => {
+29 -24
View File
@@ -241,7 +241,7 @@ class Application extends BaseApplication {
const files = await shim.fsDriver().readDirStats(templatesDir);
for (const file of files) {
if (file.path.endsWith('.md')) {
// There is atleast one template.
// There is at least one template.
this.store().dispatch({
type: 'CONTAINS_LEGACY_TEMPLATES',
});
@@ -324,6 +324,18 @@ class Application extends BaseApplication {
}, 500);
}
public crashDetectionHandler() {
// This handler conflicts with the single instance behaviour, so it's
// not used for now.
// https://discourse.joplinapp.org/t/pre-release-v2-8-is-now-available-updated-27-april/25158/56?u=laurent
if (!Setting.value('wasClosedSuccessfully')) {
const answer = confirm(_('The application did not close properly. Would you like to start in safe mode?'));
Setting.setValue('isSafeMode', !!answer);
}
Setting.setValue('wasClosedSuccessfully', false);
}
public async start(argv: string[]): Promise<any> {
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
// insert an extra argument so that they can be processed in a consistent way everywhere.
@@ -331,6 +343,8 @@ class Application extends BaseApplication {
argv = await super.start(argv);
// this.crashDetectionHandler();
await this.applySettingsSideEffects();
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
@@ -354,8 +368,7 @@ class Application extends BaseApplication {
}
// Loads app-wide styles. (Markdown preview-specific styles loaded in app.js)
const filename = Setting.custom_css_files.JOPLIN_APP;
await injectCustomStyles('appStyles', `${dir}/${filename}`);
await injectCustomStyles('appStyles', Setting.customCssFilePath(Setting.customCssFilenames.JOPLIN_APP));
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
@@ -433,7 +446,7 @@ class Application extends BaseApplication {
});
// Loads custom Markdown preview styles
const cssString = await loadCustomCss(`${Setting.value('profileDir')}/userstyle.css`);
const cssString = await loadCustomCss(Setting.customCssFilePath(Setting.customCssFilenames.RENDERED_MARKDOWN));
this.store().dispatch({
type: 'CUSTOM_CSS_APPEND',
css: cssString,
@@ -446,8 +459,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')) {
@@ -521,6 +535,7 @@ class Application extends BaseApplication {
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
};
@@ -553,11 +568,17 @@ class Application extends BaseApplication {
// setTimeout(() => {
// this.dispatch({
// type: 'DIALOG_OPEN',
// name: 'editFolder',
// props: { folderId: '3d90f7da26b947dc9c8c6c65e86cd231' },
// name: 'syncWizard',
// });
// }, 2000);
// setTimeout(() => {
// this.dispatch({
// type: 'DIALOG_OPEN',
// name: 'editFolder',
// });
// }, 3000);
// setTimeout(() => {
// this.dispatch({
// type: 'NAV_GO',
@@ -568,22 +589,6 @@ class Application extends BaseApplication {
// });
// }, 2000);
// const testData = {
// "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmKpb4JiYiY16pGOabje7uMsFd7DcMnruGxJ9HSpOiOduj3ApKqRu0xWCkGyqpekyOjjooZ98wVkDPUFsyVjN+kG8yKFn2xXC5SeRyhIVbdytjYiGshr6x+T9XVI+HnJKQF3WbrcqSOejlDXJv6u7jKrLAlOT3tkqEb0ZefhcEIajq6kNkH51R0lwsFnzxDIK3MW1wNzmiOfM92f8PFxiOBmUtVIngGPlNgyld1FzKN7Ypz1uS6GOqAtRm325qyfE/+2Jgb7WaDFT7VB5pHnOiojj9+xi1DvQWCbbIYXoMi0XVi9i2ZQfM32aFwiHez5UL61IMWUcqQ0/gldh4HFlAQIDAQAB\n-----END PUBLIC KEY-----",
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAmKpb4JiYiY16pGOabje7uMsFd7DcMnruGxJ9HSpOiOduj3ApKqRu0xWCkGyqpekyOjjooZ98wVkDPUFsyVjN+kG8yKFn2xXC5SeRyhIVbdytjYiGshr6x+T9XVI+HnJKQF3WbrcqSOejlDXJv6u7jKrLAlOT3tkqEb0ZefhcEIajq6kNkH51R0lwsFnzxDIK3MW1wNzmiOfM92f8PFxiOBmUtVIngGPlNgyld1FzKN7Ypz1uS6GOqAtRm325qyfE/+2Jgb7WaDFT7VB5pHnOiojj9+xi1DvQWCbbIYXoMi0XVi9i2ZQfM32aFwiHez5UL61IMWUcqQ0/gldh4HFlAQIDAQABAoIBADFFMffPZ9Nk7MLnPmz54cTnCPGzC63jDLuCAQ0LnWMDxiPW4AJaJUZMt+GioISBOWue+D1JOrsv3iLD3bcxyPBOjP33UYxcfpT0a1Ha+j2FriFygX4zxOIEnlyi8VdkLWCOqGj9BlGXKKzpmx4X76Sbbn9mt9+BGNm2vOUnaZcPTVuOI7K6xZynlzMRYSyhu7J0QdYVK44vZ/TjdD/4pgX+ezrGiwx7OCf/KctjvEoYtXYV2gkBOifOlqYOp0fMEC3mVAZfwpvDTbRchb7h0rxmxfKbWsjPtDblByXBLJZ3PGcKcmJlu4Qsfd2AgrY62r+DbNt3EhK072ZilYIfKD0CgYEAybcDbucr67dWMlFh5b79bvJugw6rj1V59Tp+RX9nKgzaiBUHLun6cK5hbgg9z3ejc2SWlX7D+eOyveVjhDlxUOCFURJLo2oPMRKwBBKJkOJhdtAjPzyceYI6Yj2lvtDeijcZfg8F9YqUTMfisDsEi1MbGnqawWwUerN9P5TjRBcCgYEAwcAfw8KTnQsvXPwWwh6Wabtz0bUAKzA/D6oWTR5IbkBfb3jNU8lmh9H66H0P18Nsa3vozA6buW2LDhHCFFkQ4PUTQVKok1qhAsvJBECxdwMqb5iAXk3Yk3qQYGhR23Zkp1u82wmpSaBLKGr+SL9/q5EamqiR3PQYx/aQTeIaFqcCgYAn/N/xXGKYl/++eeOuZ+5V0DmYQZBBGfDTbIUbweXxsBqiX4jNBBVhwTAPYBLgzhbZCVfQyxCOuVT10EOqMrkED35eVAIqoxvf3pSGOiaLUlV/+EMEhj9+1xI753y0FzQGsmWbV98WjiJYFkgaJ5j/BbqZxTRoo8RrjqmFsT5cgQKBgQCWTc4WlmbfSKMIloOtOf9jrMjvoWOtHXN+WmuMjfaQmR2wI13eJvqEWRA1tXdJ4c/FHk39p0OFOQbL9ljCYknmyhiS72XZUlBgE+kwhGNnuSv9gKftAKUH2+gO8j62awUwk8lRfxA2DsTfaQk1NGH9ncauviDR8QcccRmHYeTtNwKBgQCOvHiVaNw8XJIqt2r3j8pEJcr8LO+WNtLDU+h9NhM5a5NxfeRUlxdrqR0FXS4NkE6E3h9iLIRt2V+0bghzJMhKuwdjC0K6+jCb7ImV+Xcl9LNOQ1mPLBLS1jqdQnBS1ZPtcQpMrVi6dU9vVespylKEyGnQnUUtLgYrbO9OMrP1uQ==\n-----END RSA PRIVATE KEY-----",
// "plaintext": "just testing",
// "ciphertext": "LBicxglLvMyBin8uMpUnF5ARQ+KtAM563RViMepnOcyXa/NOJonNBixm+th+jX44\r\n/rie2ESbWg/FnlR4mHCEpTQJFXt12zpeXvtM8Hy1OQMud1B1Hc9hp1hhd1t6cuDz\r\n/Cs10n1+57V6zwHottYA6tn84cBn678SvPa/WTwgvb9lnBVZbesm3dVIr5uh2hk9\r\nNcVkmqyfi+ilkNQ3FIQfL+ciHvPFUIpljgIOipZhmufubdgMGW1HEUYlsmxLE7ce\r\ndpUQJoIbfKJ1x2dJRoeYsCjvcYFWdMUcg78HkXR+UcObP6zkK8cH33fb6PKKd8Z4\r\nToj4HROza8Dp7uCV5XyBTA=="
// };
// await checkTestData(testData);
// const testData = await createTestData();
// await checkTestData(testData);
// await printTestData();
// await runIntegrationTests();
return null;
+11 -4
View File
@@ -9,6 +9,13 @@ interface LastSelectedPath {
directory: string;
}
interface OpenDialogOptions {
properties?: string[];
defaultPath?: string;
createDirectory?: boolean;
filters?: any[];
}
export class Bridge {
private electronWrapper_: ElectronAppWrapper;
@@ -155,14 +162,14 @@ 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_ 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_ as any)[fileType] = dirname(filePaths[0]);
}
@@ -258,7 +265,7 @@ export class Bridge {
}
}
restart() {
restart(linuxSafeRestart = true) {
// Note that in this case we are not sending the "appClose" event
// to notify services and component that the app is about to close
// but for the current use-case it's not really needed.
@@ -269,7 +276,7 @@ export class Bridge {
execPath: process.env.PORTABLE_EXECUTABLE_FILE,
};
app.relaunch(options);
} else if (shim.isLinux()) {
} else if (shim.isLinux() && linuxSafeRestart) {
this.showInfoMessageBox(_('The app is now going to close. Please relaunch it to complete the process.'));
} else {
app.relaunch();
@@ -0,0 +1,19 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import Setting from '@joplin/lib/models/Setting';
import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils';
import bridge from '../services/bridge';
import { _ } from '@joplin/lib/locale';
export const declaration: CommandDeclaration = {
name: 'editProfileConfig',
label: () => _('Edit profile configuration...'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await openFileWithExternalEditor(`${Setting.value('rootProfileDir')}/profiles.json`, bridge());
},
enabledCondition: 'hasMultiProfiles',
};
};
+10
View File
@@ -1,5 +1,6 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as copyDevCommand from './copyDevCommand';
import * as editProfileConfig from './editProfileConfig';
import * as exportFolders from './exportFolders';
import * as exportNotes from './exportNotes';
import * as focusElement from './focusElement';
@@ -8,11 +9,16 @@ import * as replaceMisspelling from './replaceMisspelling';
import * as restoreNoteRevision from './restoreNoteRevision';
import * as startExternalEditing from './startExternalEditing';
import * as stopExternalEditing from './stopExternalEditing';
import * as switchProfile from './switchProfile';
import * as switchProfile1 from './switchProfile1';
import * as switchProfile2 from './switchProfile2';
import * as switchProfile3 from './switchProfile3';
import * as toggleExternalEditing from './toggleExternalEditing';
import * as toggleSafeMode from './toggleSafeMode';
const index:any[] = [
copyDevCommand,
editProfileConfig,
exportFolders,
exportNotes,
focusElement,
@@ -21,6 +27,10 @@ const index:any[] = [
restoreNoteRevision,
startExternalEditing,
stopExternalEditing,
switchProfile,
switchProfile1,
switchProfile2,
switchProfile3,
toggleExternalEditing,
toggleSafeMode,
];
@@ -1,19 +1,12 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { AppState } from '../app.reducer';
import bridge from '../services/bridge';
import { isInsideContainer } from '@joplin/lib/dom';
export const declaration: CommandDeclaration = {
name: 'replaceMisspelling',
};
function isInsideContainer(node: any, className: string): boolean {
while (node) {
if (node.classList && node.classList.contains(className)) return true;
node = node.parentNode;
}
return false;
}
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, suggestion: string) => {
@@ -0,0 +1,26 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import Setting from '@joplin/lib/models/Setting';
import { saveProfileConfig } from '@joplin/lib/services/profileConfig';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import restart from '../services/restart';
export const declaration: CommandDeclaration = {
name: 'switchProfile',
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, profileId: string) => {
const currentConfig = context.state.profileConfig;
if (currentConfig.currentProfileId === profileId) return;
const newConfig: ProfileConfig = {
...currentConfig,
currentProfileId: profileId,
};
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
await restart(false);
},
};
};
@@ -0,0 +1,16 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '@joplin/lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile1',
label: () => _('Switch to profile %d', 1),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 0));
},
};
};
@@ -0,0 +1,16 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '@joplin/lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile2',
label: () => _('Switch to profile %d', 2),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 1));
},
};
};
@@ -0,0 +1,16 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '@joplin/lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile3',
label: () => _('Switch to profile %d', 3),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 2));
},
};
};
@@ -1,7 +1,7 @@
import { _ } from '@joplin/lib/locale';
import Setting from '@joplin/lib/models/Setting';
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import bridge from '../services/bridge';
import restart from '../services/restart';
export const declaration: CommandDeclaration = {
name: 'toggleSafeMode',
@@ -14,7 +14,7 @@ export const runtime = (): CommandRuntime => {
enabled = enabled !== null ? enabled : !Setting.value('isSafeMode');
Setting.setValue('isSafeMode', enabled);
await Setting.saveAll();
bridge().restart();
await restart();
},
};
};
@@ -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,
});
@@ -7,12 +7,14 @@ import bridge from '../../services/bridge';
import Setting, { AppType, SyncStartupOperation } from '@joplin/lib/models/Setting';
import control_PluginsStates from './controls/plugins/PluginsStates';
import EncryptionConfigScreen from '../EncryptionConfigScreen/EncryptionConfigScreen';
import { reg } from '@joplin/lib/registry';
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const pathUtils = require('@joplin/lib/path-utils');
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const shared = require('@joplin/lib/components/shared/config-shared.js');
import ClipperConfigScreen from '../ClipperConfigScreen';
import restart from '../../services/restart';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
const settingKeyToControl: any = {
@@ -26,7 +28,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
constructor(props: any) {
super(props);
shared.init(this);
shared.init(this, reg);
this.state = {
selectedSectionName: 'general',
@@ -71,12 +73,12 @@ class ConfigScreenComponent extends React.Component<any, any> {
if (!confirm('This cannot be undone. Do you want to continue?')) return;
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalSyncState);
await Setting.saveAll();
bridge().restart();
await restart();
} else if (key === 'sync.clearLocalDataButton') {
if (!confirm('This cannot be undone. Do you want to continue?')) return;
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData);
await Setting.saveAll();
bridge().restart();
await restart();
} else if (key === 'sync.openSyncWizard') {
this.props.dispatch({
type: 'DIALOG_OPEN',
@@ -123,19 +125,6 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.switchSection(event.section.name);
}
keyValueToArray(kv: any) {
const output = [];
for (const k in kv) {
if (!kv.hasOwnProperty(k)) continue;
output.push({
key: k,
label: kv[k],
});
}
return output;
}
renderSectionDescription(section: any) {
const description = Setting.sectionDescription(section.name);
if (!description) return null;
@@ -374,7 +363,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
} else if (md.isEnum) {
const items = [];
const settingOptions = md.options();
const array = this.keyValueToArray(settingOptions);
const array = Setting.enumOptionsToValueLabels(settingOptions, md.optionsOrder ? md.optionsOrder() : [], {
valueKey: 'key',
labelKey: 'label',
});
for (let i = 0; i < array.length; i++) {
const e = array[i];
items.push(
@@ -452,7 +445,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
});
const inputType = md.secure === true ? 'password' : 'text';
if (md.subType === 'file_path_and_args') {
if (md.subType === 'file_path_and_args' || md.subType === 'file_path' || md.subType === 'directory_path') {
inputStyle.marginBottom = subLabel.marginBottom;
const splitCmd = (cmdString: string) => {
@@ -482,15 +475,41 @@ class ConfigScreenComponent extends React.Component<any, any> {
};
const browseButtonClick = async () => {
const paths = await bridge().showOpenDialog();
if (!paths || !paths.length) return;
const cmd = splitCmd(this.state.settings[key]);
cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd));
if (md.subType === 'directory_path') {
const paths = await bridge().showOpenDialog({
properties: ['openDirectory'],
});
if (!paths || !paths.length) return;
updateSettingValue(key, paths[0]);
} else {
const paths = await bridge().showOpenDialog();
if (!paths || !paths.length) return;
const cmd = splitCmd(this.state.settings[key]);
cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd));
}
};
const cmd = splitCmd(this.state.settings[key]);
const argComp = md.subType !== 'file_path_and_args' ? null : (
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div style={subLabel}>{_('Arguments:')}</div>
<input
type={inputType}
style={inputStyle}
onChange={(event: any) => {
onArgsChange(event);
}}
value={cmd[1]}
spellCheck={false}
/>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
);
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
@@ -518,25 +537,9 @@ class ConfigScreenComponent extends React.Component<any, any> {
/>
</div>
</div>
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div style={subLabel}>{_('Arguments:')}</div>
<input
type={inputType}
style={inputStyle}
onChange={(event: any) => {
onArgsChange(event);
}}
value={cmd[1]}
spellCheck={false}
/>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
</div>
</div>
{argComp}
</div>
);
} else {
@@ -621,7 +624,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
private async restartApp() {
await Setting.saveAll();
bridge().restart();
await restart();
}
private async checkNeedRestart() {
-15
View File
@@ -1,4 +1,3 @@
import { useEffect, useCallback } from 'react';
import styled from 'styled-components';
const DialogModalLayer = styled.div`
@@ -33,20 +32,6 @@ interface Props {
}
export default function Dialog(props: Props) {
const onWindowKeydown = useCallback((event: any) => {
if (event.key === 'Escape') {
if (props.onClose) props.onClose();
}
}, [props.onClose]);
useEffect(() => {
window.addEventListener('keydown', onWindowKeydown);
return () => {
window.removeEventListener('keydown', onWindowKeydown);
};
}, [onWindowKeydown]);
return (
<DialogModalLayer className={props.className}>
<DialogRoot>
+17 -22
View File
@@ -1,7 +1,8 @@
const React = require('react');
import { useMemo } from 'react';
const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme');
import * as React from 'react';
import { useMemo, useCallback } from 'react';
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
import useKeyboardHandler from './DialogButtonRow/useKeyboardHandler';
export interface ButtonSpec {
name: string;
@@ -37,32 +38,26 @@ export default function DialogButtonRow(props: Props) {
};
}, [theme.buttonStyle]);
const okButton_click = () => {
if (props.onClick) props.onClick({ buttonName: 'ok' });
};
const onOkButtonClick = useCallback(() => {
if (props.onClick && !props.okButtonDisabled) props.onClick({ buttonName: 'ok' });
}, [props.onClick, props.okButtonDisabled]);
const cancelButton_click = () => {
if (props.onClick) props.onClick({ buttonName: 'cancel' });
};
const onCancelButtonClick = useCallback(() => {
if (props.onClick && !props.cancelButtonDisabled) props.onClick({ buttonName: 'cancel' });
}, [props.onClick, props.cancelButtonDisabled]);
const customButton_click = (event: ClickEvent) => {
const onCustomButtonClick = useCallback((event: ClickEvent) => {
if (props.onClick) props.onClick(event);
};
}, [props.onClick]);
const onKeyDown = (event: any) => {
if (event.keyCode === 13) {
okButton_click();
} else if (event.keyCode === 27) {
cancelButton_click();
}
};
const onKeyDown = useKeyboardHandler({ onOkButtonClick, onCancelButtonClick });
const buttonComps = [];
if (props.customButtons) {
for (const b of props.customButtons) {
buttonComps.push(
<button key={b.name} style={buttonStyle} onClick={() => customButton_click({ buttonName: b.name })} onKeyDown={onKeyDown}>
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} onKeyDown={onKeyDown}>
{b.label}
</button>
);
@@ -71,7 +66,7 @@ export default function DialogButtonRow(props: Props) {
if (props.okButtonShow !== false) {
buttonComps.push(
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={okButton_click} ref={props.okButtonRef} onKeyDown={onKeyDown}>
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={onOkButtonClick} ref={props.okButtonRef} onKeyDown={onKeyDown}>
{props.okButtonLabel ? props.okButtonLabel : _('OK')}
</button>
);
@@ -79,7 +74,7 @@ export default function DialogButtonRow(props: Props) {
if (props.cancelButtonShow !== false) {
buttonComps.push(
<button disabled={props.cancelButtonDisabled} key="cancel" style={Object.assign({}, buttonStyle)} onClick={cancelButton_click}>
<button disabled={props.cancelButtonDisabled} key="cancel" style={Object.assign({}, buttonStyle)} onClick={onCancelButtonClick}>
{props.cancelButtonLabel ? props.cancelButtonLabel : _('Cancel')}
</button>
);
@@ -0,0 +1,62 @@
import { useEffect, useState, useRef, useCallback } from 'react';
import { isInsideContainer } from '@joplin/lib/dom';
interface Props {
onOkButtonClick: Function;
onCancelButtonClick: Function;
}
const globalKeydownHandlers: string[] = [];
export default (props: Props) => {
const [elementId] = useState(`${Math.round(Math.random() * 10000000)}`);
const globalKeydownHandlersRef = useRef(globalKeydownHandlers);
useEffect(() => {
globalKeydownHandlersRef.current.push(elementId);
return () => {
const idx = globalKeydownHandlersRef.current.findIndex(e => e === elementId);
globalKeydownHandlersRef.current.splice(idx, 1);
};
}, []);
const isTopDialog = () => {
const ln = globalKeydownHandlersRef.current.length;
return ln && globalKeydownHandlersRef.current[ln - 1] === elementId;
};
const isInSubModal = (targetElement: any) => {
// If we are inside a sub-modal within the dialog, we shouldn't handle
// global key events. It can be for example the emoji picker. In general
// it's difficult to know whether an element is a modal or not, so we'll
// have to add special cases here. Normally there shouldn't be many of
// these.
if (isInsideContainer(targetElement, 'emoji-picker')) return true;
return false;
};
const onKeyDown = useCallback((event: any) => {
// Early exit if it's neither ENTER nor ESCAPE, because isInSubModal
// function can be costly.
if (event.keyCode !== 13 && event.keyCode !== 27) return;
if (!isTopDialog() || isInSubModal(event.target)) return;
if (event.keyCode === 13) {
if (event.target.nodeName !== 'TEXTAREA') {
props.onOkButtonClick();
}
} else if (event.keyCode === 27) {
props.onCancelButtonClick();
}
}, [props.onOkButtonClick, props.onCancelButtonClick]);
useEffect(() => {
document.addEventListener('keydown', onKeyDown);
return () => {
document.removeEventListener('keydown', onKeyDown);
};
}, [onKeyDown]);
return onKeyDown;
};
@@ -37,6 +37,8 @@ class DropboxLoginScreenComponent extends React.Component<any, any> {
const inputStyle = Object.assign({}, theme.inputStyle, { width: 500 });
const buttonStyle = Object.assign({}, theme.buttonStyle, { marginRight: 10 });
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={containerStyle}>
@@ -49,7 +51,7 @@ class DropboxLoginScreenComponent extends React.Component<any, any> {
<p>
<input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle} />
</p>
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>
<button disabled={this.state.checkingAuthToken} style={buttonStyle} onClick={this.shared_.submit_click}>
{_('Submit')}
</button>
</div>
@@ -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,9 +8,11 @@ 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 { FolderEntity, 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;
@@ -22,6 +24,7 @@ interface Props {
export default function(props: Props) {
const [folderTitle, setFolderTitle] = useState('');
const [folderIcon, setFolderIcon] = useState<FolderIcon>();
const titleInputRef = useRef(null);
const isNew = !props.folderId;
@@ -41,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();
@@ -84,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>
@@ -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}
// />
// );
};
@@ -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);
}
@@ -137,7 +137,7 @@ const EncryptionConfigScreen = (props: Props) => {
return (
<td style={theme.textStyle}>
<input type="password" style={passwordStyle} value={password} onChange={event => onInputPasswordChange(mk, event.target.value)} />{' '}
<button style={theme.buttonStyle} onClick={() => onSavePasswordClick(mk, props.passwords)}>
<button style={theme.buttonStyle} onClick={() => onSavePasswordClick(mk, { ...props.passwords, ...inputPasswords })}>
{_('Save')}
</button>
</td>
@@ -268,7 +268,7 @@ const EncryptionConfigScreen = (props: Props) => {
const buttonTitle = CommandService.instance().label('openMasterPasswordDialog');
const needPasswordMessage = !needMasterPassword ? null : (
<p className="needpassword">{_('Your master password is needed to decrypt some of your data.')}<br/>{_('Please click on "%s" to proceed', buttonTitle)}</p>
<p className="needpassword">{_('Your password is needed to decrypt some of your data.')}<br/>{_('Please click on "%s" to proceed, or set the passwords in the "%s" list below.', buttonTitle, _('Encryption keys'))}</p>
);
return (
@@ -5,6 +5,7 @@
.manage-password-section > .status {
display: flex;
flex-direction: row;
align-items: center;
}
.manage-password-section > .needpassword {
+2 -2
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import versionInfo from '@joplin/lib/versionInfo';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';
import bridge from '../services/bridge';
import restart from '../services/restart';
const packageInfo = require('../packageInfo.js');
const ipcRenderer = require('electron').ipcRenderer;
@@ -75,7 +75,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
const safeMode_click = async () => {
Setting.setValue('isSafeMode', true);
await Setting.saveAll();
bridge().restart();
await restart();
};
try {
@@ -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}`);
}
}
@@ -5,7 +5,7 @@ export default function styles(themeId: number) {
return {
container: {
...theme.containerStyle,
padding: theme.configScreenPadding,
// padding: theme.configScreenPadding,
backgroundColor: theme.backgroundColor3,
},
actionsContainer: {
@@ -8,7 +8,7 @@ import NoteEditor from '../NoteEditor/NoteEditor';
import NoteContentPropertiesDialog from '../NoteContentPropertiesDialog';
import ShareNoteDialog from '../ShareNoteDialog';
import CommandService from '@joplin/lib/services/CommandService';
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import { PluginHtmlContents, PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import Sidebar from '../Sidebar/Sidebar';
import UserWebview from '../../services/plugins/UserWebview';
import UserWebviewDialog from '../../services/plugins/UserWebviewDialog';
@@ -37,9 +37,10 @@ import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncIn
import { parseCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import ElectronAppWrapper from '../../ElectronAppWrapper';
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { MasterKeyEntity } from '../../../lib/services/e2ee/types';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import commands from './commands/index';
import invitationRespond from '../../services/share/invitationRespond';
import restart from '../../services/restart';
const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js');
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
@@ -53,6 +54,7 @@ interface LayerModalState {
interface Props {
plugins: PluginStates;
pluginHtmlContents: PluginHtmlContents;
pluginsLoaded: boolean;
hasNotesBeingSaved: boolean;
dispatch: Function;
@@ -266,18 +268,22 @@ class MainScreenComponent extends React.Component<Props, State> {
if (this.waitForNotesSavedIID_) shim.clearInterval(this.waitForNotesSavedIID_);
this.waitForNotesSavedIID_ = null;
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: !this.props.hasNotesBeingSaved,
});
const sendCanClose = async (canClose: boolean) => {
if (canClose) {
Setting.setValue('wasClosedSuccessfully', true);
await Setting.saveAll();
}
ipcRenderer.send('asynchronous-message', 'appCloseReply', { canClose });
};
await sendCanClose(!this.props.hasNotesBeingSaved);
if (this.props.hasNotesBeingSaved) {
this.waitForNotesSavedIID_ = shim.setInterval(() => {
if (!this.props.hasNotesBeingSaved) {
shim.clearInterval(this.waitForNotesSavedIID_);
this.waitForNotesSavedIID_ = null;
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: true,
});
void sendCanClose(true);
}
}, 50);
}
@@ -556,13 +562,13 @@ class MainScreenComponent extends React.Component<Props, State> {
const onRestartAndUpgrade = async () => {
Setting.setValue('sync.upgradeState', Setting.SYNC_UPGRADE_STATE_MUST_DO);
await Setting.saveAll();
bridge().restart();
await restart();
};
const onDisableSafeModeAndRestart = async () => {
Setting.setValue('isSafeMode', false);
await Setting.saveAll();
bridge().restart();
await restart();
};
const onInvitationRespond = async (shareUserId: string, folderId: string, masterKey: MasterKeyEntity, accept: boolean) => {
@@ -723,12 +729,13 @@ class MainScreenComponent extends React.Component<Props, State> {
}
} else {
const { view, plugin } = viewInfo;
const html = this.props.pluginHtmlContents[plugin.id]?.[view.id] ?? '';
return <UserWebview
key={view.id}
viewId={view.id}
themeId={this.props.themeId}
html={view.html}
html={html}
scripts={view.scripts}
pluginId={plugin.id}
borderBottom={true}
@@ -762,12 +769,13 @@ class MainScreenComponent extends React.Component<Props, State> {
const { plugin, view } = info;
if (view.containerType !== ContainerType.Dialog) continue;
if (!view.opened) continue;
const html = this.props.pluginHtmlContents[plugin.id]?.[view.id] ?? '';
output.push(<UserWebviewDialog
key={view.id}
viewId={view.id}
themeId={this.props.themeId}
html={view.html}
html={html}
scripts={view.scripts}
pluginId={plugin.id}
buttons={view.buttons}
@@ -865,6 +873,7 @@ const mapStateToProps = (state: AppState) => {
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
pluginsLegacy: state.pluginsLegacy,
plugins: state.pluginService.plugins,
pluginHtmlContents: state.pluginService.pluginHtmlContents,
customCss: state.customCss,
editorNoteStatuses: state.editorNoteStatuses,
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
@@ -0,0 +1,34 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { createNewProfile, saveProfileConfig } from '@joplin/lib/services/profileConfig';
import Setting from '@joplin/lib/models/Setting';
import restart from '../../../services/restart';
export const declaration: CommandDeclaration = {
name: 'addProfile',
label: () => _('Create new profile...'),
};
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async (context: CommandContext) => {
comp.setState({
promptOptions: {
label: _('Profile name:'),
buttons: ['create', 'cancel'],
value: '',
onClose: async (answer: string) => {
if (answer) {
const { newConfig, newProfile } = createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfileId = newProfile.id;
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
await restart(false);
}
comp.setState({ promptOptions: null });
},
},
});
},
};
};
@@ -1,4 +1,5 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as addProfile from './addProfile';
import * as commandPalette from './commandPalette';
import * as editAlarm from './editAlarm';
import * as exportPdf from './exportPdf';
@@ -38,6 +39,7 @@ import * as toggleSideBar from './toggleSideBar';
import * as toggleVisiblePanes from './toggleVisiblePanes';
const index:any[] = [
addProfile,
commandPalette,
editAlarm,
exportPdf,
@@ -34,6 +34,12 @@ export default function(props: Props) {
const [updatingPassword, setUpdatingPassword] = useState(false);
const [mode, setMode] = useState<Mode>(Mode.Set);
const showCurrentPassword = useMemo(() => {
if ([MasterPasswordStatus.NotSet, MasterPasswordStatus.Invalid].includes(status)) return false;
if (mode === Mode.Reset) return false;
return true;
}, [status]);
const onClose = useCallback(() => {
props.dispatch({
type: 'DIALOG_CLOSE',
@@ -63,7 +69,7 @@ export default function(props: Props) {
setUpdatingPassword(true);
try {
if (mode === Mode.Set) {
await updateMasterPassword(currentPassword, password1);
await updateMasterPassword(showCurrentPassword ? currentPassword : null, password1);
} else if (mode === Mode.Reset) {
await resetMasterPassword(EncryptionService.instance(), KvStore.instance(), ShareService.instance(), password1);
} else {
@@ -115,7 +121,7 @@ export default function(props: Props) {
}, [password1, password2, updatingPassword, needToRepeatPassword]);
useEffect(() => {
setShowPasswordForm(status === MasterPasswordStatus.NotSet);
setShowPasswordForm([MasterPasswordStatus.NotSet, MasterPasswordStatus.Invalid].includes(status));
}, [status]);
useAsyncEffect(async (event: AsyncEffectEvent) => {
@@ -131,8 +137,7 @@ export default function(props: Props) {
function renderPasswordForm() {
const renderCurrentPassword = () => {
if (status === MasterPasswordStatus.NotSet) return null;
if (mode === Mode.Reset) return null;
if (!showCurrentPassword) return null;
// If the master password is in the keychain we preload it into the
// field and allow displaying it. That way if the user has forgotten
+99 -15
View File
@@ -1,4 +1,4 @@
import { useEffect, useState, useRef, useCallback } from 'react';
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { AppState } from '../app.reducer';
import InteropService from '@joplin/lib/services/interop/InteropService';
import { stateUtils } from '@joplin/lib/reducer';
@@ -18,9 +18,9 @@ import menuCommandNames from './menuCommandNames';
import stateToWhenClauseContext from '../services/commands/stateToWhenClauseContext';
import bridge from '../services/bridge';
import checkForUpdates from '../checkForUpdates';
const { connect } = require('react-redux');
import { reg } from '@joplin/lib/registry';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
const packageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;
@@ -39,7 +39,7 @@ function pluginMenuItemsCommandNames(menuItems: MenuItem[]): string[] {
return output;
}
function pluginCommandNames(plugins: PluginStates): string[] {
function getPluginCommandNames(plugins: PluginStates): string[] {
let output: string[] = [];
for (const view of pluginUtils.viewsByType(plugins, 'menu')) {
@@ -70,6 +70,42 @@ function createPluginMenuTree(label: string, menuItems: MenuItem[], onMenuItemCl
return output;
}
const useSwitchProfileMenuItems = (profileConfig: ProfileConfig, menuItemDic: any) => {
return useMemo(() => {
const switchProfileMenuItems: any[] = [];
for (let i = 0; i < profileConfig.profiles.length; i++) {
const profile = profileConfig.profiles[i];
let menuItem: any = {};
const profileNum = i + 1;
if (menuItemDic[`switchProfile${profileNum}`]) {
menuItem = { ...menuItemDic[`switchProfile${profileNum}`] };
} else {
menuItem = {
label: profile.name,
click: () => {
void CommandService.instance().execute('switchProfile', profile.id);
},
};
}
menuItem.label = profile.name;
menuItem.type = 'checkbox';
menuItem.checked = profileConfig.currentProfileId === profile.id;
switchProfileMenuItems.push(menuItem);
}
switchProfileMenuItems.push({ type: 'separator' });
switchProfileMenuItems.push(menuItemDic.addProfile);
switchProfileMenuItems.push(menuItemDic.editProfileConfig);
return switchProfileMenuItems;
}, [profileConfig, menuItemDic]);
};
interface Props {
dispatch: Function;
menuItemProps: any;
@@ -90,6 +126,7 @@ interface Props {
plugins: PluginStates;
customCss: string;
locale: string;
profileConfig: ProfileConfig;
}
const commandNames: string[] = menuCommandNames();
@@ -174,6 +211,12 @@ function useMenu(props: Props) {
const [keymapLastChangeTime, setKeymapLastChangeTime] = useState(Date.now());
const [modulesLastChangeTime, setModulesLastChangeTime] = useState(Date.now());
// We use a ref here because the plugin state can change frequently when
// switching note since any plugin view might be rendered again. However we
// need this plugin state only in a click handler when exporting notes, and
// for that a ref is sufficient.
const pluginsRef = useRef(props.plugins);
const onMenuItemClick = useCallback((commandName: string) => {
void CommandService.instance().execute(commandName);
}, []);
@@ -241,6 +284,18 @@ function useMenu(props: Props) {
const onImportModuleClickRef = useRef(null);
onImportModuleClickRef.current = onImportModuleClick;
const pluginCommandNames = useMemo(() => props.pluginMenuItems.map((view: any) => view.commandName), [props.pluginMenuItems]);
const menuItemDic = useMemo(() => {
return menuUtils.commandsToMenuItems(
commandNames.concat(pluginCommandNames),
(commandName: string) => onMenuItemClickRef.current(commandName),
props.locale
);
}, [commandNames, pluginCommandNames, props.locale]);
const switchProfileMenuItems: any[] = useSwitchProfileMenuItems(props.profileConfig, menuItemDic);
useEffect(() => {
let timeoutId: any = null;
@@ -249,13 +304,6 @@ 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),
props.locale
);
const quitMenuItem = {
label: _('Quit'),
accelerator: keymapService.getAccelerator('quit'),
@@ -329,7 +377,7 @@ function useMenu(props: Props) {
(action: any) => props.dispatch(action),
module,
{
plugins: props.plugins,
plugins: pluginsRef.current,
customCss: props.customCss,
}
);
@@ -347,6 +395,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
);
@@ -379,6 +433,10 @@ function useMenu(props: Props) {
const newFolderItem = menuItemDic.newFolder;
const newSubFolderItem = menuItemDic.newSubFolder;
const printItem = menuItemDic.print;
const switchProfileItem = {
label: _('Switch profile'),
submenu: switchProfileMenuItems,
};
let toolsItems: any[] = [];
@@ -493,6 +551,8 @@ function useMenu(props: Props) {
platforms: ['darwin'],
},
shim.isMac() ? noItem : switchProfileItem,
shim.isMac() ? {
label: _('Hide %s', 'Joplin'),
platforms: ['darwin'],
@@ -539,6 +599,7 @@ function useMenu(props: Props) {
type: 'separator',
},
printItem,
switchProfileItem,
],
};
@@ -566,8 +627,17 @@ 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',
label: _('Undo'),
},
{
role: 'redo',
label: _('Redo'),
},
separator(),
menuItemDic.textBold,
menuItemDic.textItalic,
@@ -835,7 +905,20 @@ 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.locale]);
}, [
props.routeName,
props.pluginMenuItems,
props.pluginMenus,
keymapLastChangeTime,
modulesLastChangeTime,
props['spellChecker.language'],
props['spellChecker.enabled'],
props.customCss,
props.locale,
props.profileConfig,
switchProfileMenuItems,
menuItemDic,
]);
useMenuStates(menu, props);
@@ -876,7 +959,7 @@ const mapStateToProps = (state: AppState) => {
const whenClauseContext = stateToWhenClauseContext(state);
return {
menuItemProps: menuUtils.commandsToMenuItemProps(commandNames.concat(pluginCommandNames(state.pluginService.plugins)), whenClauseContext),
menuItemProps: menuUtils.commandsToMenuItemProps(commandNames.concat(getPluginCommandNames(state.pluginService.plugins)), whenClauseContext),
locale: state.settings.locale,
routeName: state.route.routeName,
selectedFolderId: state.selectedFolderId,
@@ -894,6 +977,7 @@ const mapStateToProps = (state: AppState) => {
['spellChecker.enabled']: state.settings['spellChecker.enabled'],
plugins: state.pluginService.plugins,
customCss: state.customCss,
profileConfig: state.profileConfig,
};
};
+1 -1
View File
@@ -39,7 +39,7 @@ class NavigatorComponent extends Component {
};
return (
<div style={this.props.style}>
<div style={this.props.style} className={this.props.className}>
<Screen style={screenStyle} {...screenProps} />
</div>
);
@@ -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) => {
@@ -465,6 +465,10 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
color: ${theme.color};
}
div.CodeMirror span.cm-variable-2, div.CodeMirror span.cm-variable-3, div.CodeMirror span.cm-keyword {
color: ${theme.color};
}
div.CodeMirror span.cm-quote {
color: ${theme.color};
opacity: ${theme.blockQuoteOpacity};
@@ -479,10 +483,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
opacity: 0.5;
}
div.CodeMirror span.cm-variable-2, div.CodeMirror span.cm-variable-3, div.CodeMirror span.cm-keyword {
color: ${theme.color};
}
div.CodeMirror span.cm-comment {
color: ${theme.codeColor};
}
@@ -515,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;
}
@@ -576,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;
@@ -643,6 +649,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
// undefined. Maybe due to the error boundary that unmount components.
// Since we can't do much about it we just print an error.
if (webviewRef.current && webviewRef.current.wrappedInstance) {
// To keep consistency among CodeMirror's editing and scroll percents
// of Editor and Viewer.
const percent = getLineScrollPercent();
setEditorPercentScroll(percent);
options.percent = percent;
webviewRef.current.wrappedInstance.send('setHtml', renderedBody.html, options);
} else {
console.error('Trying to set HTML on an undefined webview ref');
@@ -726,15 +737,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();
@@ -838,6 +853,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
onEditorPaste={onEditorPaste}
isSafeMode={props.isSafeMode}
onResize={editor_resize}
onUpdate={editor_update}
/>
</div>
);
@@ -94,11 +94,13 @@ export interface EditorProps {
onEditorPaste: any;
isSafeMode: boolean;
onResize: any;
onUpdate: any;
}
function Editor(props: EditorProps, ref: any) {
const [editor, setEditor] = useState(null);
const editorParent = useRef(null);
const lastEditTime = useRef(NaN);
// Codemirror plugins add new commands to codemirror (or change it's behavior)
// This command adds the smartListIndent function which will be bound to tab
@@ -119,6 +121,7 @@ function Editor(props: EditorProps, ref: any) {
const editor_change = useCallback((cm: any, change: any) => {
if (props.onChange && change.origin !== 'setValue') {
props.onChange(cm.getValue());
lastEditTime.current = Date.now();
}
}, [props.onChange]);
@@ -148,6 +151,15 @@ 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) => {
const edited = Date.now() - lastEditTime.current <= 100;
props.onUpdate(cm, edited);
}, [props.onUpdate]);
useEffect(() => {
if (!editorParent.current) return () => {};
@@ -190,7 +202,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 +217,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);
};
@@ -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.setSelection(match.from, match.to);
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,16 +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 ||
// If there is only one choice, scrollTo should be true. The below is a dummy of nMatches === 1.
options.selectedIndex === 0);
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
@@ -7,13 +7,17 @@ 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 = () => {
const now = Date.now();
if (now >= ignoreNextEditorScrollTime_.current) ignoreNextEditorScrollEventCount_.current = 0;
if (ignoreNextEditorScrollEventCount_.current < 10) { // for safety
ignoreNextEditorScrollTime_.current = now + 200;
ignoreNextEditorScrollTime_.current = now + 1000;
ignoreNextEditorScrollEventCount_.current += 1;
}
};
@@ -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,61 @@ 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: any, edited: boolean) => {
if (isCodeMirrorReady(cm)) {
if (edited) return;
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,
};
}
@@ -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() : '',
@@ -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',
@@ -0,0 +1,57 @@
/** @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');
// These tests are disabled because unfortunately they require the "canvas"
// module, and it's yet another binary module that fails to compile half of the
// time. Since it's only needed here it's not worth the trouble.
describe('contextMenu', () => {
it('should pass', () => {
expect(1).toBe(1);
});
// 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;
// });
});
@@ -2,6 +2,7 @@ import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import Resource from '@joplin/lib/models/Resource';
@@ -10,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);
@@ -55,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);
@@ -100,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...'),
@@ -112,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 separately 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 %s', '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 %s', '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'),
@@ -120,13 +119,20 @@ 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,
},
@@ -137,7 +143,7 @@ export function menuItems(dispatch: Function): ContextMenuItems {
const image = bridge().createImageFromPath(resourcePath);
clipboard.writeImage(image);
},
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image,
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image,
},
cut: {
label: _('Cut'),
@@ -145,14 +151,14 @@ export function menuItems(dispatch: Function): ContextMenuItems {
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'),
@@ -184,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];
@@ -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) {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const resourcePath = resource ? Resource.fullPath(resource) : null;
const filename = resource ? (resource.filename ? resource.filename : resource.title) : options.filename ? options.filename : '';
return { resource, resourcePath, 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;
});
};
@@ -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: '',
@@ -92,6 +92,7 @@ export default function useNoteSearchBar() {
selectedIndex: localSearch.selectedIndex,
separateWordSearch: false,
searchTimestamp: localSearch.timestamp,
withSelection: true,
},
keywords: [
{
@@ -4,6 +4,7 @@ interface SearchMarkersOptions {
searchTimestamp: number;
selectedIndex: number;
separateWordSearch: boolean;
withSelection?: boolean;
}
export interface SearchMarkers {
+201 -204
View File
@@ -1,3 +1,5 @@
import * as React from 'react';
import { useMemo, useEffect, useState, useRef, useCallback } from 'react';
import { AppState } from '../../app.reducer';
import eventManager from '@joplin/lib/eventManager';
import NoteListUtils from '../utils/NoteListUtils';
@@ -11,12 +13,12 @@ import CommandService from '@joplin/lib/services/CommandService';
import shim from '@joplin/lib/shim';
import styled from 'styled-components';
import { themeStyle } from '@joplin/lib/theme';
const React = require('react');
const { ItemList } = require('../ItemList.min.js');
const { connect } = require('react-redux');
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import { Props } from './types';
import usePrevious from '../hooks/usePrevious';
const commands = [
require('./commands/focusElementNoteList'),
@@ -29,50 +31,48 @@ const StyledRoot = styled.div`
border-right: 1px solid ${(props: any) => props.theme.dividerColor};
`;
class NoteListComponent extends React.Component {
constructor() {
super();
const itemAnchorRefs_: any = {
current: {},
};
CommandService.instance().componentRegisterCommands(this, commands);
export const itemAnchorRef = (itemId: string) => {
if (itemAnchorRefs_.current[itemId] && itemAnchorRefs_.current[itemId].current) return itemAnchorRefs_.current[itemId].current;
return null;
};
this.itemHeight = 34;
const NoteListComponent = (props: Props) => {
const [dragOverTargetNoteIndex, setDragOverTargetNoteIndex] = useState(null);
const [width, setWidth] = useState(0);
const [, setHeight] = useState(0);
this.state = {
dragOverTargetNoteIndex: null,
width: 0,
height: 0,
useEffect(() => {
itemAnchorRefs_.current = {};
CommandService.instance().registerCommands(commands);
return () => {
itemAnchorRefs_.current = {};
CommandService.instance().unregisterCommands(commands);
};
}, []);
this.noteListRef = React.createRef();
this.itemListRef = React.createRef();
this.itemAnchorRefs_ = {};
const itemHeight = 34;
this.renderItem = this.renderItem.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.noteItem_titleClick = this.noteItem_titleClick.bind(this);
this.noteItem_noteDragOver = this.noteItem_noteDragOver.bind(this);
this.noteItem_noteDrop = this.noteItem_noteDrop.bind(this);
this.noteItem_checkboxClick = this.noteItem_checkboxClick.bind(this);
this.noteItem_dragStart = this.noteItem_dragStart.bind(this);
this.onGlobalDrop_ = this.onGlobalDrop_.bind(this);
this.registerGlobalDragEndEvent_ = this.registerGlobalDragEndEvent_.bind(this);
this.unregisterGlobalDragEndEvent_ = this.unregisterGlobalDragEndEvent_.bind(this);
this.itemContextMenu = this.itemContextMenu.bind(this);
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
}
const focusItemIID_ = useRef<any>(null);
const noteListRef = useRef(null);
const itemListRef = useRef(null);
style() {
if (this.styleCache_ && this.styleCache_[this.props.themeId]) return this.styleCache_[this.props.themeId];
let globalDragEndEventRegistered_ = false;
const theme = themeStyle(this.props.themeId);
const style = useMemo(() => {
const theme = themeStyle(props.themeId);
const style = {
return {
root: {
backgroundColor: theme.backgroundColor,
},
listItem: {
maxWidth: '100%',
height: this.itemHeight,
height: itemHeight,
boxSizing: 'border-box',
display: 'flex',
alignItems: 'stretch',
@@ -99,76 +99,71 @@ class NoteListComponent extends React.Component {
textDecoration: 'line-through',
},
};
}, [props.themeId, itemHeight]);
this.styleCache_ = {};
this.styleCache_[this.props.themeId] = style;
return style;
}
itemContextMenu(event: any) {
const itemContextMenu = useCallback((event: any) => {
const currentItemId = event.currentTarget.getAttribute('data-id');
if (!currentItemId) return;
let noteIds = [];
if (this.props.selectedNoteIds.indexOf(currentItemId) < 0) {
if (props.selectedNoteIds.indexOf(currentItemId) < 0) {
noteIds = [currentItemId];
} else {
noteIds = this.props.selectedNoteIds;
noteIds = props.selectedNoteIds;
}
if (!noteIds.length) return;
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: this.props.notes,
dispatch: this.props.dispatch,
watchedNoteFiles: this.props.watchedNoteFiles,
plugins: this.props.plugins,
inConflictFolder: this.props.selectedFolderId === Folder.conflictFolderId(),
customCss: this.props.customCss,
notes: props.notes,
dispatch: props.dispatch,
watchedNoteFiles: props.watchedNoteFiles,
plugins: props.plugins,
inConflictFolder: props.selectedFolderId === Folder.conflictFolderId(),
customCss: props.customCss,
});
menu.popup(bridge().window());
}
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles,props.plugins, props.selectedFolderId, props.customCss]);
onGlobalDrop_() {
this.unregisterGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: null });
}
const onGlobalDrop_ = () => {
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
};
registerGlobalDragEndEvent_() {
if (this.globalDragEndEventRegistered_) return;
this.globalDragEndEventRegistered_ = true;
document.addEventListener('dragend', this.onGlobalDrop_);
}
const registerGlobalDragEndEvent_ = () => {
if (globalDragEndEventRegistered_) return;
globalDragEndEventRegistered_ = true;
document.addEventListener('dragend', onGlobalDrop_);
};
unregisterGlobalDragEndEvent_() {
this.globalDragEndEventRegistered_ = false;
document.removeEventListener('dragend', this.onGlobalDrop_);
}
const unregisterGlobalDragEndEvent_ = () => {
globalDragEndEventRegistered_ = false;
document.removeEventListener('dragend', onGlobalDrop_);
};
dragTargetNoteIndex_(event: any) {
return Math.abs(Math.round((event.clientY - this.itemListRef.current.offsetTop() + this.itemListRef.current.offsetScroll()) / this.itemHeight));
}
const dragTargetNoteIndex_ = (event: any) => {
return Math.abs(Math.round((event.clientY - itemListRef.current.offsetTop() + itemListRef.current.offsetScroll()) / itemHeight));
};
noteItem_noteDragOver(event: any) {
if (this.props.notesParentType !== 'Folder') return;
const noteItem_noteDragOver = (event: any) => {
if (props.notesParentType !== 'Folder') return;
const dt = event.dataTransfer;
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
const newIndex = this.dragTargetNoteIndex_(event);
if (this.state.dragOverTargetNoteIndex === newIndex) return;
this.registerGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: newIndex });
const newIndex = dragTargetNoteIndex_(event);
if (dragOverTargetNoteIndex === newIndex) return;
registerGlobalDragEndEvent_();
setDragOverTargetNoteIndex(newIndex);
}
}
};
async noteItem_noteDrop(event: any) {
if (this.props.notesParentType !== 'Folder') return;
const noteItem_noteDrop = async (event: any) => {
if (props.notesParentType !== 'Folder') return;
if (this.props.noteSortOrder !== 'order') {
if (props.noteSortOrder !== 'order') {
const doIt = await bridge().showConfirmMessageBox(_('To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"', _('Custom order'), _('View'), _('Sort notes by')), {
buttons: [_('Do it now'), _('Cancel')],
});
@@ -181,17 +176,17 @@ class NoteListComponent extends React.Component {
// TODO: check that parent type is folder
const dt = event.dataTransfer;
this.unregisterGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: null });
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
const targetNoteIndex = this.dragTargetNoteIndex_(event);
const targetNoteIndex = dragTargetNoteIndex_(event);
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
void Note.insertNotesAt(this.props.selectedFolderId, noteIds, targetNoteIndex);
}
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex);
};
async noteItem_checkboxClick(event: any, item: any) {
const noteItem_checkboxClick = async (event: any, item: any) => {
const checked = event.target.checked;
const newNote = {
id: item.id,
@@ -199,37 +194,37 @@ class NoteListComponent extends React.Component {
};
await Note.save(newNote, { userSideValidation: true });
eventManager.emit('todoToggle', { noteId: item.id, note: newNote });
}
};
async noteItem_titleClick(event: any, item: any) {
const noteItem_titleClick = async (event: any, item: any) => {
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_TOGGLE',
id: item.id,
});
} else if (event.shiftKey) {
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_EXTEND',
id: item.id,
});
} else {
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT',
id: item.id,
});
}
}
};
noteItem_dragStart(event: any) {
const noteItem_dragStart = (event: any) => {
let noteIds = [];
// Here there is two cases:
// - If multiple notes are selected, we drag the group
// - If only one note is selected, we drag the note that was clicked on (which might be different from the currently selected note)
if (this.props.selectedNoteIds.length >= 2) {
noteIds = this.props.selectedNoteIds;
if (props.selectedNoteIds.length >= 2) {
noteIds = props.selectedNoteIds;
} else {
const clickedNoteId = event.currentTarget.getAttribute('data-id');
if (clickedNoteId) noteIds.push(clickedNoteId);
@@ -240,61 +235,66 @@ class NoteListComponent extends React.Component {
event.dataTransfer.setDragImage(new Image(), 1, 1);
event.dataTransfer.clearData();
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
}
};
renderItem(item: any, index: number) {
const renderItem = useCallback((item: any, index: number) => {
const highlightedWords = () => {
if (this.props.notesParentType === 'Search') {
const query = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
if (props.notesParentType === 'Search') {
const query = BaseModel.byId(props.searches, props.selectedSearchId);
if (query) {
return this.props.highlightedWords;
return props.highlightedWords;
}
}
return [];
};
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
const ref = this.itemAnchorRefs_[item.id];
if (!itemAnchorRefs_.current[item.id]) itemAnchorRefs_.current[item.id] = React.createRef();
const ref = itemAnchorRefs_.current[item.id];
return <NoteListItem
ref={ref}
key={item.id}
style={this.style()}
style={style}
item={item}
index={index}
themeId={this.props.themeId}
width={this.state.width}
height={this.itemHeight}
dragItemIndex={this.state.dragOverTargetNoteIndex}
themeId={props.themeId}
width={width}
height={itemHeight}
dragItemIndex={dragOverTargetNoteIndex}
highlightedWords={highlightedWords()}
isProvisional={this.props.provisionalNoteIds.includes(item.id)}
isSelected={this.props.selectedNoteIds.indexOf(item.id) >= 0}
isWatched={this.props.watchedNoteFiles.indexOf(item.id) < 0}
itemCount={this.props.notes.length}
onCheckboxClick={this.noteItem_checkboxClick}
onDragStart={this.noteItem_dragStart}
onNoteDragOver={this.noteItem_noteDragOver}
onNoteDrop={this.noteItem_noteDrop}
onTitleClick={this.noteItem_titleClick}
onContextMenu={this.itemContextMenu}
isProvisional={props.provisionalNoteIds.includes(item.id)}
isSelected={props.selectedNoteIds.indexOf(item.id) >= 0}
isWatched={props.watchedNoteFiles.indexOf(item.id) < 0}
itemCount={props.notes.length}
onCheckboxClick={noteItem_checkboxClick}
onDragStart={noteItem_dragStart}
onNoteDragOver={noteItem_noteDragOver}
onNoteDrop={noteItem_noteDrop}
onTitleClick={noteItem_titleClick}
onContextMenu={itemContextMenu}
/>;
}
}, [style, props.themeId, width, itemHeight, dragOverTargetNoteIndex, props.provisionalNoteIds, props.selectedNoteIds, props.watchedNoteFiles,
props.notes,
props.notesParentType,
props.searches,
props.selectedSearchId,
props.highlightedWords,
]);
itemAnchorRef(itemId: string) {
if (this.itemAnchorRefs_[itemId] && this.itemAnchorRefs_[itemId].current) return this.itemAnchorRefs_[itemId].current;
return null;
}
const previousSelectedNoteIds = usePrevious(props.selectedNoteIds, []);
const previousNotes = usePrevious(props.notes, []);
const previousVisible = usePrevious(props.visible, false);
componentDidUpdate(prevProps: any) {
if (prevProps.selectedNoteIds !== this.props.selectedNoteIds && this.props.selectedNoteIds.length === 1) {
const id = this.props.selectedNoteIds[0];
const doRefocus = this.props.notes.length < prevProps.notes.length;
useEffect(() => {
if (previousSelectedNoteIds !== props.selectedNoteIds && props.selectedNoteIds.length === 1) {
const id = props.selectedNoteIds[0];
const doRefocus = props.notes.length < previousNotes.length;
for (let i = 0; i < this.props.notes.length; i++) {
if (this.props.notes[i].id === id) {
this.itemListRef.current.makeItemIndexVisible(i);
for (let i = 0; i < props.notes.length; i++) {
if (props.notes[i].id === id) {
itemListRef.current.makeItemIndexVisible(i);
if (doRefocus) {
const ref = this.itemAnchorRef(id);
const ref = itemAnchorRef(id);
if (ref) ref.focus();
}
break;
@@ -302,24 +302,24 @@ class NoteListComponent extends React.Component {
}
}
if (prevProps.visible !== this.props.visible) {
this.updateSizeState();
if (previousVisible !== props.visible) {
updateSizeState();
}
}
}, [previousSelectedNoteIds,previousNotes, previousVisible, props.selectedNoteIds, props.notes]);
scrollNoteIndex_(keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) {
const scrollNoteIndex_ = (keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) => {
if (keyCode === 33) {
// Page Up
noteIndex -= (this.itemListRef.current.visibleItemCount() - 1);
noteIndex -= (itemListRef.current.visibleItemCount() - 1);
} else if (keyCode === 34) {
// Page Down
noteIndex += (this.itemListRef.current.visibleItemCount() - 1);
noteIndex += (itemListRef.current.visibleItemCount() - 1);
} else if ((keyCode === 35 && ctrlKey) || (keyCode === 40 && metaKey)) {
// CTRL+End, CMD+Down
noteIndex = this.props.notes.length - 1;
noteIndex = props.notes.length - 1;
} else if ((keyCode === 36 && ctrlKey) || (keyCode === 38 && metaKey)) {
// CTRL+Home, CMD+Up
@@ -334,31 +334,31 @@ class NoteListComponent extends React.Component {
noteIndex += 1;
}
if (noteIndex < 0) noteIndex = 0;
if (noteIndex > this.props.notes.length - 1) noteIndex = this.props.notes.length - 1;
if (noteIndex > props.notes.length - 1) noteIndex = props.notes.length - 1;
return noteIndex;
}
};
async onKeyDown(event: any) {
const onKeyDown = async (event: any) => {
const keyCode = event.keyCode;
const noteIds = this.props.selectedNoteIds;
const noteIds = props.selectedNoteIds;
if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode == 36)) {
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
let noteIndex = BaseModel.modelIndexById(props.notes, noteId);
noteIndex = this.scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
noteIndex = scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
const newSelectedNote = this.props.notes[noteIndex];
const newSelectedNote = props.notes[noteIndex];
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT',
id: newSelectedNote.id,
});
this.itemListRef.current.makeItemIndexVisible(noteIndex);
itemListRef.current.makeItemIndexVisible(noteIndex);
this.focusNoteId_(newSelectedNote.id);
focusNoteId_(newSelectedNote.id);
event.preventDefault();
}
@@ -373,7 +373,7 @@ class NoteListComponent extends React.Component {
// SPACE
event.preventDefault();
const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
const notes = BaseModel.modelsByIds(props.notes, noteIds);
const todos = notes.filter((n: any) => !!n.is_todo);
if (!todos.length) return;
@@ -382,7 +382,7 @@ class NoteListComponent extends React.Component {
await Note.save(toggledTodo);
}
this.focusNoteId_(todos[0].id);
focusNoteId_(todos[0].id);
}
if (keyCode === 9) {
@@ -400,62 +400,63 @@ class NoteListComponent extends React.Component {
// Ctrl+A key
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_ALL',
});
}
}
};
focusNoteId_(noteId: string) {
const focusNoteId_ = (noteId: string) => {
// - We need to focus the item manually otherwise focus might be lost when the
// list is scrolled and items within it are being rebuilt.
// - We need to use an interval because when leaving the arrow pressed, the rendering
// of items might lag behind and so the ref is not yet available at this point.
if (!this.itemAnchorRef(noteId)) {
if (this.focusItemIID_) shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = shim.setInterval(() => {
if (this.itemAnchorRef(noteId)) {
this.itemAnchorRef(noteId).focus();
shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
if (!itemAnchorRef(noteId)) {
if (focusItemIID_.current) shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = shim.setInterval(() => {
if (itemAnchorRef(noteId)) {
itemAnchorRef(noteId).focus();
shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = null;
}
}, 10);
} else {
this.itemAnchorRef(noteId).focus();
itemAnchorRef(noteId).focus();
}
}
};
updateSizeState() {
this.setState({
width: this.noteListRef.current.clientWidth,
height: this.noteListRef.current.clientHeight,
});
}
const updateSizeState = () => {
setWidth(noteListRef.current.clientWidth);
setHeight(noteListRef.current.clientHeight);
};
resizableLayout_resize() {
this.updateSizeState();
}
const resizableLayout_resize = () => {
updateSizeState();
};
componentDidMount() {
this.props.resizableLayoutEventEmitter.on('resize', this.resizableLayout_resize);
this.updateSizeState();
}
useEffect(() => {
props.resizableLayoutEventEmitter.on('resize', resizableLayout_resize);
return () => {
props.resizableLayoutEventEmitter.off('resize', resizableLayout_resize);
};
}, [props.resizableLayoutEventEmitter]);
componentWillUnmount() {
if (this.focusItemIID_) {
shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
}
useEffect(() => {
updateSizeState();
this.props.resizableLayoutEventEmitter.off('resize', this.resizableLayout_resize);
return () => {
if (focusItemIID_.current) {
shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = null;
}
CommandService.instance().componentUnregisterCommands(commands);
};
}, []);
CommandService.instance().componentUnregisterCommands(commands);
}
const renderEmptyList = () => {
if (props.notes.length) return null;
renderEmptyList() {
if (this.props.notes.length) return null;
const theme = themeStyle(this.props.themeId);
const theme = themeStyle(props.themeId);
const padding = 10;
const emptyDivStyle = {
padding: `${padding}px`,
@@ -464,39 +465,35 @@ class NoteListComponent extends React.Component {
backgroundColor: theme.backgroundColor,
fontFamily: theme.fontFamily,
};
// emptyDivStyle.width = emptyDivStyle.width - padding * 2;
// emptyDivStyle.height = emptyDivStyle.height - padding * 2;
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
}
return <div style={emptyDivStyle}>{props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
};
renderItemList(style: any) {
if (!this.props.notes.length) return null;
const renderItemList = () => {
if (!props.notes.length) return null;
return (
<ItemList
ref={this.itemListRef}
disabled={this.props.isInsertingNotes}
itemHeight={this.style().listItem.height}
ref={itemListRef}
disabled={props.isInsertingNotes}
itemHeight={style.listItem.height}
className={'note-list'}
items={this.props.notes}
style={style}
itemRenderer={this.renderItem}
onKeyDown={this.onKeyDown}
items={props.notes}
style={props.size}
itemRenderer={renderItem}
onKeyDown={onKeyDown}
/>
);
}
};
render() {
if (!this.props.size) throw new Error('props.size is required');
if (!props.size) throw new Error('props.size is required');
return (
<StyledRoot ref={this.noteListRef}>
{this.renderEmptyList()}
{this.renderItemList(this.props.size)}
</StyledRoot>
);
}
}
return (
<StyledRoot ref={noteListRef}>
{renderEmptyList()}
{renderItemList()}
</StyledRoot>
);
};
const mapStateToProps = (state: AppState) => {
return {
Loaded 100 of 520 files, more files were not shown because too many files have changed in this diff. Show more