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

Compare commits

..

107 Commits

Author SHA1 Message Date
Laurent Cozic
c44aad544e update 2025-11-06 17:54:15 +01:00
Laurent Cozic
996a0894ae Chore: Fixed Postgres tool path for new Homebrew version 2025-11-06 17:50:59 +01:00
Laurent Cozic
66fa3fc808 Server: Remove query optimisation that now seems to be slower with newer versions of Postgres 2025-11-06 17:12:45 +01:00
renovate[bot]
dab55daf95 Update dependency prosemirror-model to v1.25.3 (#13623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 02:16:07 +00:00
summoner
7f1c31e03f All: Translation: Update hu_HU.po (#13620) 2025-11-04 15:42:36 -05:00
cedecode
0a8255f091 All: Translation: Update de_DE.po (#13618) 2025-11-04 15:41:04 -05:00
Helmut K. C. Tessarek
9f3e6650a9 Update translations 2025-11-03 17:23:28 -05:00
mrjo118
4a17da3df5 All: Fixes #13531: When creating a conflict, ensure the latest note contents are used to create the conflict (#13552) 2025-11-03 20:21:05 +01:00
Henry Heino
2c4f0d4d8c Desktop: Fixes #13574: Fix crash when opening the legacy Markdown editor (#13576) 2025-11-03 20:12:39 +01:00
Henry Heino
9c1c2fb0d4 Chore: Desktop: Enable source maps for error reporting by default (#13577) 2025-11-03 20:12:24 +01:00
Henry Heino
2332e4bf62 Desktop: Fixes #13579: Rich Text Editor: Make cursor jump during editing less likely (#13581) 2025-11-03 20:11:45 +01:00
Henry Heino
a488ac1b27 Desktop: Fixes #13177: Location: Remove geoplugin.net from location providers (#13583) 2025-11-03 20:11:37 +01:00
Henry Heino
6daa41ca66 All: Fixes #13291: Improve performance of item deserialization (#13585) 2025-11-03 20:11:21 +01:00
Henry Heino
cc9517f1a2 Desktop: Resolves #13586: Preserve scroll when switching between Markdown and Rich Text Editors (#13587) 2025-11-03 20:11:12 +01:00
Henry Heino
200a471e55 Chore: OneNote importer: Remove unused dependency (#13590) 2025-11-03 12:21:03 +01:00
renovate[bot]
c21d37bd91 Update dependency @types/serviceworker to v0.0.149 (#13604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 12:20:50 +01:00
renovate[bot]
e36cd0e60b Update dependency mermaid to v11.8.1 (#13607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 21:16:04 +00:00
VortexP
871f55bf11 All: Translation: Update fi_FI.po (#13605) 2025-11-02 16:13:27 -05:00
renovate[bot]
22c9fed663 Update dependency mermaid to v11.8.0 (#13589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 20:14:11 +01:00
renovate[bot]
ea362d7a82 Update dependency @electron/remote to v2.1.3 (#13594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 20:13:54 +01:00
renovate[bot]
9ae9347f89 Update eslint (#13597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 20:28:29 +00:00
Joplin Bot
ae8bb902f9 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-11-01 01:50:23 +00:00
renovate[bot]
90eeec23de Update eslint (#13595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 01:49:05 +00:00
Henry Heino
474fd094c4 Chore: Update licenses.md (#13582) 2025-10-31 10:28:04 +01:00
renovate[bot]
937d8fa4f7 Update dependency react-native-share to v12.1.2 (#13570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 12:10:11 +01:00
renovate[bot]
45c9844616 Update dependency @types/serviceworker to v0.0.148 (#13568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 10:49:02 +01:00
Joplin Bot
12b8ef5a54 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-29 18:41:30 +00:00
mrjo118
18f72c224e Mobile: Fixes #13151: Reset the state of undo and redo buttons when switching editor (#13505) 2025-10-29 18:22:56 +01:00
mrjo118
7ca3aaa83f Web: Fixes #13241: Find and replace toolbar in the note editor is not sized correctly (#13559) 2025-10-29 18:21:30 +01:00
mrjo118
04b1443e5a Mobile: Make title field work with very long text (#13566) 2025-10-29 18:20:56 +01:00
mrjo118
c461741778 All: Fixes #13319: Treat unclosed quotes as fully quoted search terms, to prevent malformed match expression error (#13564) 2025-10-29 18:19:38 +01:00
renovate[bot]
2865b0a803 Update dependency follow-redirects to v1.15.11 (#13565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 17:58:37 +01:00
Laurent Cozic
21e49be22f Doc: Fixed order of tags in spellcheck document 2025-10-29 17:56:40 +01:00
Laurent Cozic
fef761cbab Doc: Added documentation to setup Joplin Server with Keycloak to test SAML auth 2025-10-29 17:55:07 +01:00
renovate[bot]
c15a353dc2 Update dependency react-native-safe-area-context to v5.5.2 (#13496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-10-29 14:00:36 +01:00
Laurent Cozic
ffb32766c1 Desktop release v3.5.6 2025-10-29 13:45:27 +01:00
Henry Heino
038908550e Chore: Desktop: Share folder dialog: Remove duplicate "refreshShares" call (#13535) 2025-10-29 13:43:56 +01:00
Henry Heino
42f59134ae Desktop: Fixes #13549: OneNote importer: Task lists: Fix checkbox sizes and accessibility (#13558) 2025-10-29 13:43:48 +01:00
Laurent Cozic
fc0014c0b5 All: Open the connection screen when a SAML session has expired 2025-10-29 13:42:11 +01:00
Laurent Cozic
42d8df3036 Desktop, Cli, Mobile: Ensure that sync process ends up properly when Joplin Server shares cannot be accessed 2025-10-29 13:42:11 +01:00
Frank Fesevur
1fad9ca1cc All: Translation: Update nl_NL.po (#13556) 2025-10-28 17:37:04 -04:00
Laurent Cozic
ae289be77a Server: Add support for DELETE_EXPIRED_SESSIONS_SCHEDULE to prevent auto-logout when using SAML login 2025-10-28 17:37:38 +01:00
Laurent Cozic
7f6bfe9c6e Doc: Clarifies that SAML does not support the API_BASE_URL 2025-10-28 17:21:57 +01:00
Laurent Cozic
ead4001b7a Revert "Server: Fix SAML routes to prevent cookie issues on redirect (#13557)"
This reverts commit a4556bf598.
2025-10-28 17:05:27 +01:00
Laurent Cozic
7b95ef72a0 Server: Fixes #13368: Cannot login with SAML when already logged in on the browser 2025-10-28 16:59:42 +01:00
Laurent Cozic
a4556bf598 Server: Fix SAML routes to prevent cookie issues on redirect (#13557) 2025-10-28 16:58:11 +01:00
mrjo118
8d6268dc92 Chore: Fix intermittent revision test failure (#13458) 2025-10-28 11:35:07 +01:00
Henry Heino
7ffcbdf60a Server: Fixes #13490: Make server less likely to generate non-unique SSO codes (#13501) 2025-10-28 11:34:22 +01:00
mrjo118
76989ddc45 Mobile: Fixes #13120: Fix truncated buttons on tag association screen (#13502) 2025-10-28 11:33:52 +01:00
mrjo118
1db1254617 Mobile: Fixes #12957: Avoid dismissing the keyboard when tapping markdown toolbar buttons with the title in focus (#13504) 2025-10-28 11:33:36 +01:00
mrjo118
9810bffddc Mobile: Fixes #11468: Ensure note list is re-ordered after updating a note opened via a search (#13506) 2025-10-28 11:28:59 +01:00
Henry Heino
b25e18107b Desktop,Mobile,Cli: Fixes #13522: Fix "cannot add an item as a child of a read-only item" error when updating share IDs (#13523) 2025-10-28 11:28:37 +01:00
Henry Heino
edc5fe5d1b Desktop: Allow adding and removing users from a share while a sync is in progress (#13529) 2025-10-28 11:26:46 +01:00
Henry Heino
7ffb44b3a4 Desktop: Fixes #13537: Fix adding a new user to a share creates an unused E2EE key (#13538) 2025-10-28 11:23:02 +01:00
Henry Heino
32f4c33140 Desktop: Disallow unsharing a folder while sharing is in progress (#13551) 2025-10-28 11:22:13 +01:00
renovate[bot]
1a7b09c91c Update dependency koa to v2.16.2 (#13554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 09:40:08 +00:00
renovate[bot]
e5bf8e0e58 Update dependency @types/node-fetch to v2.6.13 (#13553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 09:38:10 +00:00
renovate[bot]
94725c533c Update dependency @react-native-community/datetimepicker to v8.4.3 (#13547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 14:46:31 +00:00
Jozef Gaal
359c92b64f All: Translation: Update sk_SK.po (#13542) 2025-10-25 16:28:26 -04:00
renovate[bot]
8f8b8ad943 Update dependency dotenv to v16.6.1 (#13543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 14:36:57 +00:00
renovate[bot]
dd2f329fd5 Update dependency @types/serviceworker to v0.0.147 (#13541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 14:34:38 +00:00
mrjo118
813f594cb4 Mobile: Increase height of tag association screen to cater for a larger tag list area (#13521) 2025-10-25 14:13:15 +02:00
mrjo118
0e0ce49867 Mobile: Fixes #13108: Markdown toolbar overlaps with the gesture bar (#13533) 2025-10-25 14:12:37 +02:00
Henry Heino
e485d318b7 Desktop: Accessibility: Improve dialog keyboard handling (#13536) 2025-10-25 14:09:10 +02:00
renovate[bot]
4e82d81df1 Update dependency dotenv to v16.6.0 (#13539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 14:04:29 +02:00
Frank Fesevur
d5dbda201b All: Translation: Update nl_NL.po (#13519) 2025-10-23 15:29:45 -04:00
Joplin Bot
831258506b Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-23 12:52:21 +00:00
renovate[bot]
67f3329ecb Update dependency rate-limiter-flexible to v7.1.1 (#13517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 14:32:35 +02:00
renovate[bot]
ed7e6751f0 Update dependency react-native-share to v12.1.1 (#13516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 14:32:03 +02:00
mrjo118
35e69486d3 Mobile: Fixes #13457: Prevent toggling of multiline mode from clearing the title field on iOS (#13515) 2025-10-23 11:40:50 +02:00
Henry Heino
918c8830e0 Mobile: Fixes #13193: Fix Markdown toolbar (#13514) 2025-10-23 11:40:28 +02:00
renovate[bot]
c3b4a4b955 Update dependency rate-limiter-flexible to v7 (#13513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 11:38:21 +02:00
Laurent Cozic
44a14fabbd Doc: Updated sponsors 2025-10-23 10:19:53 +02:00
Frank Fesevur
49399cd1fa All: Translation: Update nl_NL.po (#13510) 2025-10-22 14:39:24 -04:00
Bartolomeo
fc4cd2e942 Server: Resolves #13147: Add LOG_LEVEL env var to control logging verbosity (#13503) 2025-10-22 12:26:02 +02:00
Arman Saga
cd6e457dc5 All: Translation: Update ru_RU.po (#13507) 2025-10-22 00:31:23 -04:00
Eric Duarte
2e9bf3a4e5 All: Translation: Update ca.po and es_ES.po (#13499) 2025-10-21 18:27:12 -04:00
Eric Duarte
547ceea4b0 All: Translation: Update es_ES.po (#13498) 2025-10-21 18:11:53 -04:00
renovate[bot]
776ff5e7ea Update dependency @fortawesome/react-fontawesome to v0.2.3 (#13500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 23:22:49 +02:00
Joplin Bot
2b3bac0d43 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-21 18:39:58 +00:00
Henry Heino
e48efe2e8d Desktop: OneNote importer: Resolve possible import failure related to unsupported formatting (#13495) 2025-10-21 17:19:56 +02:00
Laurent Cozic
5f6382fbc0 Merge branch 'release-3.4' into dev 2025-10-21 16:36:53 +02:00
Laurent Cozic
3d5d82081a iOS 13.4.4 2025-10-21 16:17:15 +02:00
Laurent Cozic
cff96b1306 iOS: Removed donation link since Apple is blocking the release because of this 2025-10-21 16:08:02 +02:00
Henry Heino
98c5a9c096 Desktop: Fixes #13481: Accessibility: Prevent sidebar header text from moving: Don't change the header icon on hover (#13482) 2025-10-21 00:46:52 +02:00
Henry Heino
e92430b3ed Desktop: Accessibility: Fix global keyboard shortcuts are ignored when the sidebar has focus (#13485) 2025-10-21 00:46:36 +02:00
renovate[bot]
848d1bfe64 Update dependency react-native-safe-area-context to v5.5.0 (#13487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 00:45:52 +02:00
Henry Heino
a386283530 Docs: Update OneNote import workflow (#13494) 2025-10-21 00:45:33 +02:00
Greg Oledzki
6101031269 Chore: Replace if with it in one of the tests (#13489) 2025-10-20 21:18:07 +02:00
Henry Heino
2fc3431f46 Web: Accessibility: Fix focus indicator is invisible for sync wizard options (#13492) 2025-10-20 21:12:06 +02:00
renovate[bot]
361fa2c768 Update dependency @types/serviceworker to v0.0.146 (#13484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 16:07:56 +00:00
Helmut K. C. Tessarek
f4a0a2466b Update translations 2025-10-19 14:42:12 -04:00
summoner
dbf225d6ad All: Translation: Update hu_HU.po (#13486) 2025-10-19 14:36:40 -04:00
Helmut K. C. Tessarek
4773a3831c fix: remove \r escape sequence from hu_HU.po 2025-10-18 15:46:49 -04:00
Mihai Vasiliu
6a19690581 All: Translation: Update ro_RO.po and ro_MD.po (#13479) 2025-10-18 15:24:06 -04:00
Arda Kılıçdağı
b7a771d58d All: Translation: Update tr_TR.po (#13478) 2025-10-18 15:23:53 -04:00
Jozef Gaal
e3daefb81a All: Translation: Update sk_SK.po (#13477) 2025-10-18 15:23:41 -04:00
Joplin Bot
b4253dace8 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-10-18 12:46:07 +00:00
renovate[bot]
fcf3be1be1 Update dependency esbuild to v0.25.8 (#13473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 12:41:05 +01:00
renovate[bot]
99aebbad81 Update dependency mermaid to v11.7.0 (#13476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 12:40:51 +01:00
Laurent Cozic
81b695a2a9 Chore: Exclude translation updates from changelog 2025-10-18 11:30:24 +01:00
Laurent Cozic
2dbba27357 Plugin Repo CLI v3.5.3 2025-10-18 10:12:33 +01:00
Laurent Cozic
97fa85a3f7 Desktop release v3.4.13 2025-10-02 09:35:36 +01:00
Laurent Cozic
defe36bba1 Server: Enable publish and share notebook for SAML login 2025-10-02 09:34:51 +01:00
Henry Heino
711d214741 Android: Fixes #13193: Fix Markdown toolbar buttons sometimes don't work (#13233) 2025-09-18 12:05:57 +01:00
pedr
0795c67354 All: Fixes #12249: Change default content-type for Webdav connector to application/octet-stream (#13053) 2025-09-13 14:13:27 +01:00
Laurent Cozic
e9a9f68568 Desktop release v3.4.12 2025-09-09 15:36:24 +01:00
200 changed files with 83314 additions and 73377 deletions

View File

@@ -701,6 +701,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
@@ -1643,6 +1644,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
packages/lib/services/synchronizer/Synchronizer.tags.test.js
packages/lib/services/synchronizer/Synchronizer.tools.test.js
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
packages/lib/services/synchronizer/handleConflictAction.test.js
packages/lib/services/synchronizer/migrations/1.js
packages/lib/services/synchronizer/migrations/2.js
packages/lib/services/synchronizer/migrations/3.js

2
.gitignore vendored
View File

@@ -674,6 +674,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
@@ -1616,6 +1617,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
packages/lib/services/synchronizer/Synchronizer.tags.test.js
packages/lib/services/synchronizer/Synchronizer.tools.test.js
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
packages/lib/services/synchronizer/handleConflictAction.test.js
packages/lib/services/synchronizer/migrations/1.js
packages/lib/services/synchronizer/migrations/2.js
packages/lib/services/synchronizer/migrations/3.js

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a> <a href="https://uk.notgamstop.com/bonuses/free-spins-no-deposit-no-gamstop/"><img title="free spins no deposit at NotGamstop" width="256" src="https://joplinapp.org/images/sponsors/NotGamStop.jpg" alt="free spins no deposit at NotGamstop"/></a> <a href="https://www.writemyessay.com/"><img title="writing service for students WriteMyEssay" width="256" src="https://joplinapp.org/images/sponsors/WriteMyEssay.png" alt="writing service for students WriteMyEssay"/></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://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a> <a href="https://uk.notgamstop.com/bonuses/free-spins-no-deposit-no-gamstop/"><img title="free spins no deposit at NotGamstop" width="256" src="https://joplinapp.org/images/sponsors/NotGamStop.jpg" alt="free spins no deposit at NotGamstop"/></a> <a href="https://www.writemyessay.com/"><img title="writing service for students WriteMyEssay" width="256" src="https://joplinapp.org/images/sponsors/WriteMyEssay.png" alt="writing service for students WriteMyEssay"/></a> <a href="https://essayservice.com/"><img title="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -76,7 +76,7 @@
"cspell": "5.21.2",
"eslint": "8.57.1",
"eslint-interactive": "10.8.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jest": "27.9.0",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-react": "7.37.5",

View File

@@ -7,6 +7,7 @@ import useKeyboardHandler from './DialogButtonRow/useKeyboardHandler';
export interface ButtonSpec {
name: string;
label: string;
disabled?: boolean;
}
export interface ClickEvent {
@@ -51,21 +52,29 @@ export default function DialogButtonRow(props: Props) {
if (props.onClick) props.onClick(event);
}, [props.onClick]);
const onKeyDown = useKeyboardHandler({ onOkButtonClick, onCancelButtonClick });
const okButtonShow = props.okButtonShow ?? true;
const cancelButtonShow = props.cancelButtonShow ?? true;
const canClickOk = okButtonShow && !props.okButtonDisabled;
const canClickCancel = cancelButtonShow && !props.cancelButtonDisabled;
const onKeyDown = useKeyboardHandler({
onOkButtonClick: canClickOk ? onOkButtonClick : null,
onCancelButtonClick: canClickCancel ? onCancelButtonClick : null,
});
const buttonComps = [];
if (props.customButtons) {
for (const b of props.customButtons) {
buttonComps.push(
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} onKeyDown={onKeyDown}>
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} disabled={b.disabled} onKeyDown={onKeyDown}>
{b.label}
</button>,
);
}
}
if (props.okButtonShow !== false) {
if (okButtonShow) {
buttonComps.push(
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={onOkButtonClick} ref={props.okButtonRef} onKeyDown={onKeyDown}>
{props.okButtonLabel ? props.okButtonLabel : _('OK')}
@@ -73,7 +82,7 @@ export default function DialogButtonRow(props: Props) {
);
}
if (props.cancelButtonShow !== false) {
if (cancelButtonShow) {
buttonComps.push(
<button disabled={props.cancelButtonDisabled} key="cancel" style={{ ...buttonStyle }} onClick={onCancelButtonClick}>
{props.cancelButtonLabel ? props.cancelButtonLabel : _('Cancel')}

View File

@@ -2,11 +2,10 @@ import * as React from 'react';
import { useEffect, useState, useRef, useCallback } from 'react';
import { isInsideContainer } from '@joplin/lib/dom';
type OnButtonClick = ()=> void;
interface Props {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onOkButtonClick: Function;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onCancelButtonClick: Function;
onOkButtonClick: null|OnButtonClick;
onCancelButtonClick: null|OnButtonClick;
}
const globalKeydownHandlers: string[] = [];
@@ -48,15 +47,17 @@ export default (props: Props) => {
if (!isTopDialog() || isInSubModal(event.target)) return;
if (event.keyCode === 13) {
if (event.keyCode === 13 && props.onOkButtonClick) {
if ('nodeName' in event.target && event.target.nodeName === 'INPUT') {
const target = event.target as HTMLInputElement;
if (target.type !== 'button' && target.type !== 'checkbox') {
event.preventDefault();
props.onOkButtonClick();
}
}
} else if (event.keyCode === 27) {
} else if (event.keyCode === 27 && props.onCancelButtonClick) {
event.preventDefault();
props.onCancelButtonClick();
}
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied

View File

@@ -172,7 +172,12 @@ export default function NoteContentPropertiesDialog(props: NoteContentProperties
<div style={{ ...labelCompStyle, marginTop: 10 }}>
{readTimeLabel}
</div>
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
<DialogButtonRow
themeId={props.themeId}
onClick={buttonRow_click}
okButtonShow={false}
cancelButtonLabel={_('Close')}
/>
</Dialog>
);
}

View File

@@ -8,7 +8,36 @@ const logger = Logger.create('useEditorSearch');
// Registers a helper CodeMirror extension to be used with
// useEditorSearchHandler.
export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulation) {
interface SetMarkersOptions {
selectedIndex: number;
searchTimestamp: number;
showEditorMarkers?: boolean;
withSelection?: boolean;
}
type Keyword = { value: string };
export type OnSetMarkers = (cm: CodeMirror5Emulation, keywords: Keyword[], options: SetMarkersOptions)=> number;
// Modified from codemirror/addons/search/search.js
const searchOverlay = (query: RegExp) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return { token: function(stream: any) {
query.lastIndex = stream.pos;
const match = query.exec(stream.string);
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1;
return 'search-marker';
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
return null;
} };
};
export default function useEditorSearchExtension() {
const [markers, setMarkers] = useState([]);
const [overlay, setOverlay] = useState(null);
@@ -48,23 +77,6 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
setOverlayTimeout(null);
}, [scrollbarMarks, overlay, overlayTimeout]);
// Modified from codemirror/addons/search/search.js
const searchOverlay = useCallback((query: RegExp) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return { token: function(stream: any) {
query.lastIndex = stream.pos;
const match = query.exec(stream.string);
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1;
return 'search-marker';
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
return null;
} };
}, []);
// Highlights the currently active found work
// It's possible to get tricky with this functions and just use findNext/findPrev
@@ -115,16 +127,17 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
};
}, []);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
CodeMirror?.defineExtension('setMarkers', function(keywords: any, options: any) {
const onSetMarkers: OnSetMarkers = (cm, keywords, options) => {
// Pass arguments in via options to allow the extension to work if multiple editors are open simultaneously
// See https://github.com/laurent22/joplin/issues/13399.
if (!options) {
options = { selectedIndex: 0, searchTimestamp: 0 };
}
if (options.showEditorMarkers === false) {
clearMarkers();
clearOverlay(this);
return;
clearOverlay(cm);
return 0;
}
clearMarkers();
@@ -145,7 +158,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
try {
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
const match = highlightSearch(cm, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
if (match) marks.push(match);
} catch (error) {
if (error.name !== 'SyntaxError') {
@@ -165,7 +178,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
// SEARCHOVERLAY
// We only want to highlight all matches when there is only 1 search term
if (keywords.length !== 1 || keywords[0].value === '') {
clearOverlay(this);
clearOverlay(cm);
const prev = keywords.length > 1 ? keywords[0].value : '';
setPreviousKeywordValue(prev);
return 0;
@@ -175,22 +188,22 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
// Determine the number of matches in the source, this is passed on
// to the NoteEditor component
const regexMatches = this.getValue().match(searchTerm);
const regexMatches = cm.getValue().match(searchTerm);
const nMatches = regexMatches ? regexMatches.length : 0;
// Don't bother clearing and re-calculating the overlay if the search term
// hasn't changed
if (keywords[0].value === previousKeywordValue) return nMatches;
clearOverlay(this);
clearOverlay(cm);
setPreviousKeywordValue(keywords[0].value);
// These operations are pretty slow, so we won't add use them until the user
// has finished typing, 500ms is probably enough time
const timeout = shim.setTimeout(() => {
const scrollMarks = this.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
const scrollMarks = cm.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
const overlay = searchOverlay(searchTerm);
this.addOverlay(overlay);
cm.addOverlay(overlay);
setOverlay(overlay);
setScrollbarMarks(scrollMarks);
}, 500);
@@ -199,5 +212,9 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
overlayTimeoutRef.current = timeout;
return nMatches;
});
};
const onSetMarkersRef = useRef(onSetMarkers);
onSetMarkersRef.current = onSetMarkers;
return { onSetMarkersRef };
}

View File

@@ -2,6 +2,8 @@ import { RefObject, useEffect, useMemo, useRef } from 'react';
import usePrevious from '../../../../hooks/usePrevious';
import { RenderedBody } from './types';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import CodeMirror5Emulation from '@joplin/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation';
import useEditorSearchExtension from './useEditorSearchExtension';
const debounce = require('debounce');
interface Props {
@@ -10,8 +12,7 @@ interface Props {
searchMarkers: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
webviewRef: RefObject<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
editorRef: RefObject<any>;
editorRef: RefObject<CodeMirror5Emulation>;
noteContent: string;
renderedBody: RenderedBody;
@@ -23,6 +24,8 @@ const useEditorSearchHandler = (props: Props) => {
webviewRef, editorRef, renderedBody, noteContent, searchMarkers, showEditorMarkers,
} = props;
const { onSetMarkersRef } = useEditorSearchExtension();
const previousContent = usePrevious(noteContent);
const previousRenderedBody = usePrevious(renderedBody);
const previousSearchMarkers = usePrevious(searchMarkers);
@@ -31,15 +34,15 @@ const useEditorSearchHandler = (props: Props) => {
// Fixes https://github.com/laurent22/joplin/issues/7565
const debouncedMarkers = useMemo(() => debounce((searchMarkers: SearchMarkers) => {
if (!editorRef.current) return;
if (!onSetMarkersRef.current) return;
if (showEditorMarkersRef.current) {
const matches = editorRef.current.setMarkers(searchMarkers.keywords, searchMarkers.options);
const matches = onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, searchMarkers.options);
props.setLocalSearchResultCount(matches);
} else {
editorRef.current.setMarkers(searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
}
}, 50), [editorRef, props.setLocalSearchResultCount]);
}, 50), [editorRef, onSetMarkersRef, props.setLocalSearchResultCount]);
useEffect(() => {
if (!searchMarkers) return () => {};
@@ -59,7 +62,7 @@ const useEditorSearchHandler = (props: Props) => {
}
return () => {};
}, [
editorRef,
onSetMarkersRef,
webviewRef,
searchMarkers,
previousSearchMarkers,
@@ -71,6 +74,10 @@ const useEditorSearchHandler = (props: Props) => {
debouncedMarkers,
]);
return {
// Returned to allow quickly setting the initial search markers just after the editor loads.
onSetInitialMarkersRef: onSetMarkersRef,
};
};
export default useEditorSearchHandler;

View File

@@ -695,7 +695,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [renderedBody, webviewReady]);
useEditorSearchHandler({
const { onSetInitialMarkersRef } = useEditorSearchHandler({
setLocalSearchResultCount: props.setLocalSearchResultCount,
searchMarkers: props.searchMarkers,
webviewRef,
@@ -737,6 +737,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
<Editor
value={props.content}
searchMarkers={props.searchMarkers}
onSetMarkersRef={onSetInitialMarkersRef}
ref={editorRef}
mode={props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML ? 'xml' : 'joplin-markdown'}
codeMirrorTheme={styles.editor.codeMirrorTheme}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef, RefObject } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import CodeMirror from 'codemirror';
@@ -16,7 +16,7 @@ import useListIdent from './utils/useListIdent';
import useScrollUtils from './utils/useScrollUtils';
import useCursorUtils from './utils/useCursorUtils';
import useLineSorting from './utils/useLineSorting';
import useEditorSearch from '../utils/useEditorSearchExtension';
import { OnSetMarkers } from '../utils/useEditorSearchExtension';
import useJoplinMode from './utils/useJoplinMode';
import useKeymap from './utils/useKeymap';
import useExternalPlugins from './utils/useExternalPlugins';
@@ -77,6 +77,7 @@ export interface EditorProps {
value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
searchMarkers: any;
onSetMarkersRef: RefObject<OnSetMarkers>;
mode: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
style: any;
@@ -119,7 +120,6 @@ function Editor(props: EditorProps, ref: any) {
useScrollUtils(CodeMirror);
useCursorUtils(CodeMirror);
useLineSorting(CodeMirror);
useEditorSearch(CodeMirror);
useJoplinMode(CodeMirror);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
@@ -228,7 +228,7 @@ function Editor(props: EditorProps, ref: any) {
// 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
// soon as the editor is ready
if (props.searchMarkers) { cm.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options); }
if (props.searchMarkers) { props.onSetMarkersRef.current(cm, props.searchMarkers.keywords, props.searchMarkers.options); }
return () => {
// Clean up codemirror

View File

@@ -11,7 +11,6 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
import { dirname } from 'path';
import useKeymap from './utils/useKeymap';
import useEditorSearch from '../utils/useEditorSearchExtension';
import CommandService from '@joplin/lib/services/CommandService';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import localisation from './utils/localisation';
@@ -44,8 +43,6 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
onLogMessageRef.current = props.onLogMessage;
}, [props.onEvent, props.onLogMessage]);
useEditorSearch(editor);
useEffect(() => {
if (!editor) {
return () => {};

View File

@@ -919,8 +919,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
editor.on('SetContent', () => {
preprocessContent();
props_onMessage.current({ channel: 'noteRenderComplete' });
});
},
});
@@ -1047,7 +1045,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
return true;
}
const { onInitialContentSet } = useCursorPositioning({
const { onRestoreCursorPosition } = useCursorPositioning({
initialCursorLocation: props.initialCursorLocation.richText as Bookmark,
onCursorUpdate: props.onCursorMotion,
editor,
@@ -1120,8 +1118,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
// times would result in an empty note.
// https://github.com/laurent22/joplin/issues/3534
editor.undoManager.reset();
// Only restore the cursor position from the global state when switching notes.
// See https://github.com/laurent22/joplin/issues/13579
onRestoreCursorPosition();
} else {
// Restore the cursor location
// Restore the cursor location from the current note
editor.selection.bookmarkManager.moveToBookmark(bookmark);
}
@@ -1130,6 +1132,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
resourceInfos: props.resourceInfos,
contentKey: props.contentKey,
};
props_onMessage.current({ channel: 'noteRenderComplete' });
}
const allAssetsOptions: NoteStyleOptions = {
@@ -1143,7 +1146,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
await loadDocumentAssets(props.themeId, editor, allAssets);
dispatchDidUpdate(editor);
onInitialContentSet();
};
void loadContent();

View File

@@ -13,7 +13,7 @@ const useCursorPositioning = ({ initialCursorLocation, editor, onCursorUpdate }:
initialCursorLocationRef.current = initialCursorLocation;
const appliedInitialCursorLocationRef = useRef(false);
const onInitialContentSet = useCallback(() => {
const onRestoreCursorPosition = useCallback(() => {
if (editor) {
if (initialCursorLocationRef.current) {
editor.selection.moveToBookmark(initialCursorLocationRef.current);
@@ -26,8 +26,6 @@ const useCursorPositioning = ({ initialCursorLocation, editor, onCursorUpdate }:
useEffect(() => {
if (!editor) return () => {};
editor.on('ContentSet', onInitialContentSet);
const onSelectionChange = () => {
// Wait until the initial cursor position has been set. This avoids resetting
// the initial cursor position to zero when the editor first loads.
@@ -44,12 +42,11 @@ const useCursorPositioning = ({ initialCursorLocation, editor, onCursorUpdate }:
editor.on('SelectionChange', onSelectionChange);
return () => {
editor.off('ContentSet', onInitialContentSet);
editor.off('SelectionChange', onSelectionChange);
};
}, [editor, onCursorUpdate, onInitialContentSet]);
}, [editor, onCursorUpdate, onRestoreCursorPosition]);
return { onInitialContentSet };
return { onRestoreCursorPosition };
};
export default useCursorPositioning;

View File

@@ -335,6 +335,7 @@ function NoteEditorContent(props: NoteEditorProps) {
selectedNoteHash: props.selectedNoteHash,
lastEditorScrollPercents: props.lastEditorScrollPercents,
editorRef,
editorName: props.bodyEditor,
});
const onMessage = useMessageHandler(scrollWhenReadyRef, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);

View File

@@ -6,21 +6,25 @@ import useNowEffect from '@joplin/lib/hooks/useNowEffect';
interface Props {
noteId: string;
editorName: string;
selectedNoteHash: string;
lastEditorScrollPercents: NoteIdToScrollPercent;
editorRef: RefObject<NoteBodyEditorRef>;
}
const useScrollWhenReadyOptions = ({ noteId, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
const useScrollWhenReadyOptions = ({ noteId, editorName, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
const scrollWhenReadyRef = useRef<ScrollOptions|null>(null);
const previousNoteId = usePrevious(noteId);
const noteIdChanged = noteId !== previousNoteId;
const previousEditor = usePrevious(editorName);
const editorChanged = editorName !== previousEditor;
const lastScrollPercentsRef = useRef<NoteIdToScrollPercent>(null);
lastScrollPercentsRef.current = lastEditorScrollPercents;
// This needs to be a nowEffect to prevent race conditions
useNowEffect(() => {
if (noteId === previousNoteId) return () => {};
if (!editorChanged && !noteIdChanged) return () => {};
if (editorRef.current) {
editorRef.current.resetScroll();
@@ -32,7 +36,7 @@ const useScrollWhenReadyOptions = ({ noteId, selectedNoteHash, lastEditorScrollP
value: selectedNoteHash ? selectedNoteHash : lastScrollPercent,
};
return () => {};
}, [noteId, previousNoteId, selectedNoteHash, editorRef]);
}, [editorChanged, noteIdChanged, noteId, selectedNoteHash, editorRef]);
const clearScrollWhenReady = useCallback(() => {
scrollWhenReadyRef.current = null;

View File

@@ -501,7 +501,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
<div role='table' aria-labelledby='note-properties-dialog-title'>
{noteComps}
</div>
<DialogButtonRow themeId={this.props.themeId} okButtonShow={!this.isReadOnly()} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
<DialogButtonRow
themeId={this.props.themeId}
okButtonShow={!this.isReadOnly()}
okButtonRef={this.okButton}
onClick={this.buttonRow_click}
/>
</Dialog>
);
}

View File

@@ -71,7 +71,7 @@ async function initialize() {
panes: Setting.value('noteVisiblePanes'),
});
InteropService.instance().document = document;
InteropService.instance().domParser = new DOMParser();
InteropService.instance().xmlSerializer = new XMLSerializer();
}

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import Dialog from '../Dialog';
import DialogButtonRow, { ClickEvent, ButtonSpec } from '../DialogButtonRow';
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
import DialogTitle from '../DialogTitle';
import { _ } from '@joplin/lib/locale';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FolderEntity } from '@joplin/lib/services/database/types';
import Folder from '@joplin/lib/models/Folder';
import ShareService, { ApiShare } from '@joplin/lib/services/share/ShareService';
@@ -129,7 +129,6 @@ function ShareFolderDialog(props: Props) {
const [share, setShare] = useState<StateShare>(null);
const [shareUsers, setShareUsers] = useState<StateShareUser[]>([]);
const [shareState, setShareState] = useState<ShareState>(ShareState.Idle);
const [customButtons, setCustomButtons] = useState<ButtonSpec[]>([]);
const [recipientsBeingUpdated, setRecipientsBeingUpdated] = useState<Record<string, boolean>>({});
async function synchronize(event: AsyncEffectEvent = null) {
@@ -163,13 +162,6 @@ function ShareFolderDialog(props: Props) {
void ShareService.instance().refreshShareUsers(share.id);
}, [share]);
useEffect(() => {
setCustomButtons(share ? [{
name: 'unshare',
label: _('Unshare'),
}] : []);
}, [share]);
useEffect(() => {
if (!share) return;
const sus = props.shareUsers[share.id];
@@ -177,10 +169,6 @@ function ShareFolderDialog(props: Props) {
setShareUsers(sus);
}, [share, props.shareUsers]);
useEffect(() => {
void ShareService.instance().refreshShares();
}, [props.folderId]);
const permissionsFromString = (p: string): SharePermissions => {
return {
can_read: 1,
@@ -269,7 +257,7 @@ function ShareFolderDialog(props: Props) {
}, []);
function renderAddRecipient() {
const disabled = shareState !== ShareState.Idle;
const disabled = shareState !== ShareState.Idle && shareState !== ShareState.Synchronizing;
const dropdown = !props.canUseSharePermissions ? null : <Dropdown className="permission-dropdown" options={permissionOptions} value={recipientPermissions} onChange={recipientPermissions_change}/>;
@@ -395,6 +383,17 @@ function ShareFolderDialog(props: Props) {
props.onClose();
}
const customButtons = useMemo(() => {
return share ? [{
name: 'unshare',
label: _('Unshare'),
// Don't allow unsharing the folder during the "create" action. Doing so might
// be able to cause issues similar to #13518 (e.g. if the "unshare" action completes while
// the "share" action is still in progress).
disabled: shareState === ShareState.Creating || shareState === ShareState.Synchronizing,
}] : [];
}, [share, shareState]);
function renderContent() {
return (
<StyledRoot className="share-folder-dialog">

View File

@@ -74,6 +74,8 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
const selectedItem = listItems[selectedIndex];
let indexChange = 0;
const ctrlAltOrMeta = event.ctrlKey || event.altKey || event.metaKey;
if (selectedItem && isToggleShortcut(event.code, selectedItem, collapsedFolderIds)) {
event.preventDefault();
@@ -111,7 +113,7 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
} else if (event.code === 'Enter' && !event.shiftKey) {
event.preventDefault();
void CommandService.instance().execute('focusElement', 'noteList');
} else if (selectedIndex && selectedIndex >= 0 && event.key.length === 1) {
} else if (selectedIndex && selectedIndex >= 0 && event.key.length === 1 && !ctrlAltOrMeta) {
const nextMatch = findNextTypeAheadMatch(selectedIndex, event.key, listItems);
if (nextMatch !== -1) {
indexChange = nextMatch - selectedIndex;

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useState } from 'react';
import { useCallback } from 'react';
import { StyledHeader, StyledHeaderIcon, StyledHeaderLabel } from '../styles';
import { HeaderId, HeaderListItem } from '../types';
import bridge from '../../../services/bridge';
@@ -25,8 +25,6 @@ const HeaderItem: React.FC<Props> = props => {
const item = props.item;
const onItemClick = item.onClick;
const itemId = item.id;
const [isHovered, setIsHovered] = useState(false);
const expanded = item.expanded;
const onClick: React.MouseEventHandler<HTMLElement> = useCallback(event => {
if (onItemClick) {
@@ -46,14 +44,6 @@ const HeaderItem: React.FC<Props> = props => {
}
}, [itemId]);
const handleMouseEnter = useCallback(() => {
setIsHovered(true);
}, []);
const handleMouseLeave = useCallback(() => {
setIsHovered(false);
}, []);
return (
<ListItemWrapper
containerRef={props.anchorRef}
@@ -70,10 +60,8 @@ const HeaderItem: React.FC<Props> = props => {
>
<StyledHeader
onClick={onClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<StyledHeaderIcon aria-hidden='true' role='img' className={isHovered ? `fas ${expanded ? 'fa-caret-down' : 'fa-caret-right'}` : item.iconName}/>
<StyledHeaderIcon aria-hidden='true' role='img' className={item.iconName}/>
<StyledHeaderLabel>{item.label}</StyledHeaderLabel>
</StyledHeader>
</ListItemWrapper>

View File

@@ -326,5 +326,16 @@ test.describe('markdownEditor', () => {
return true;
}).toBe(true);
});
test('should still support the legacy Markdown editor', async ({ electronApp, mainWindow }) => {
const mainScreen = await new MainScreen(mainWindow).setup();
await mainScreen.waitFor();
await setSettingValue(electronApp, mainWindow, 'editor.legacyMarkdown', true);
await mainScreen.createNewNote('Test');
// Should show the legacy editor
await expect(mainWindow.locator('.rli-editor .CodeMirror5')).toBeVisible();
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.5.5",
"version": "3.5.6",
"description": "Joplin for Desktop",
"main": "main.bundle.js",
"private": true,
@@ -12,7 +12,7 @@
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"start": "gulp before-start && JOPLIN_SOURCE_MAP_ENABLED=1 electron . --env dev --log-level debug --open-dev-tools --no-welcome",
"start": "gulp before-start && electron . --env dev --log-level debug --open-dev-tools --no-welcome",
"test": "jest",
"test-ui": "gulp before-start && playwright test",
"test-ci": "yarn test",
@@ -202,12 +202,12 @@
"taboverride": "4.0.3",
"tesseract.js": "6.0.1",
"tinymce": "6.8.5",
"ts-jest": "29.3.4",
"ts-jest": "29.4.1",
"ts-node": "10.9.2",
"typescript": "5.8.3"
},
"dependencies": {
"@electron/remote": "2.1.2",
"@electron/remote": "2.1.3",
"@joplin/onenote-converter": "~3.5",
"fs-extra": "11.2.0",
"keytar": "7.9.0",

View File

@@ -1,6 +1,8 @@
// source-map-support can add 1-3 seconds to the application startup
// time -- disable it unless requested:
if (process.env.JOPLIN_SOURCE_MAP_ENABLED) {
// time. In the future, it may make sense to either:
// - Use Sentry for resolving source maps: https://docs.sentry.io/platforms/javascript/guides/electron/sourcemaps/
// - Use NodeJS source map support (if https://github.com/electron/electron/issues/38875 is resolved)
if (!process.env.JOPLIN_SOURCE_MAP_DISABLED) {
require('source-map-support').install();
}

View File

@@ -132,6 +132,7 @@ const EditorToolbar: React.FC<Props> = props => {
style={styles.content}
contentContainerStyle={styles.contentContainer}
onLayout={onContainerLayout}
keyboardShouldPersistTaps="always"
>
{buttonInfos.map(renderButton)}
<View style={styles.spacer}/>

View File

@@ -48,6 +48,11 @@ const useStyles = (themeId: number) => {
invisibleHeading: {
flexGrow: 1,
},
// Use compact mode on the button and expand the padding to match the original styling, to work around an Android issue #13120
buttonStyle: {
paddingLeft: 16,
paddingRight: 16,
},
});
}, [themeId]);
};
@@ -55,15 +60,22 @@ const useStyles = (themeId: number) => {
const ModalDialog: React.FC<Props> = props => {
const styles = useStyles(props.themeId);
const theme = themeStyle(props.themeId);
const containerStyle = !props.modalProps.containerStyle ? styles.container : {
...styles.container,
...props.modalProps.containerStyle,
};
const modalProps = {
...props.modalProps,
containerStyle,
} as Partial<ModalElementProps>;
return (
<Modal
transparent={true}
visible={true}
onRequestClose={null}
containerStyle={styles.container}
backgroundColor={theme.backgroundColorTransparent2}
{...props.modalProps}
{...modalProps}
>
<View style={styles.contentWrapper}>{props.children}</View>
<View style={styles.buttonRow}>
@@ -76,8 +88,8 @@ const ModalDialog: React.FC<Props> = props => {
accessible={true}
style={styles.invisibleHeading}
/>
<Button disabled={!props.buttonBarEnabled} onPress={props.onCancelPress}>{props.cancelTitle}</Button>
<PrimaryButton disabled={!props.buttonBarEnabled} onPress={props.onOkPress}>{props.okTitle}</PrimaryButton>
<Button compact contentStyle={styles.buttonStyle} disabled={!props.buttonBarEnabled} onPress={props.onCancelPress}>{props.cancelTitle}</Button>
<PrimaryButton compact contentStyle={styles.buttonStyle} disabled={!props.buttonBarEnabled} onPress={props.onOkPress}>{props.okTitle}</PrimaryButton>
</View>
</Modal>
);

View File

@@ -0,0 +1,76 @@
import * as React from 'react';
import { describe, it, beforeEach } from '@jest/globals';
import { render, waitFor } from '../../utils/testing/testingLibrary';
import Setting from '@joplin/lib/models/Setting';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import TestProviderStack from '../testing/TestProviderStack';
import createMockReduxStore from '../../utils/testing/createMockReduxStore';
import createTestEditorProps from './testing/createTestEditorProps';
import { EditorEvent, EditorEventType } from '@joplin/editor/events';
import { RefObject, useCallback } from 'react';
import { EditorCommandType, EditorControl } from '@joplin/editor/types';
import MarkdownEditor from './MarkdownEditor';
interface WrapperProps {
ref?: RefObject<EditorControl>;
onBodyChange: (newBody: string)=> void;
noteBody: string;
}
const defaultEditorProps = createTestEditorProps();
const testStore = createMockReduxStore();
const WrappedEditor: React.FC<WrapperProps> = (
{
noteBody,
onBodyChange,
ref,
}: WrapperProps,
) => {
const onEvent = useCallback((event: EditorEvent) => {
if (event.kind === EditorEventType.Change) {
onBodyChange(event.value);
}
}, [onBodyChange]);
return <TestProviderStack store={testStore}>
<MarkdownEditor
{...defaultEditorProps}
onEditorEvent={onEvent}
initialText={noteBody}
editorRef={ref ?? defaultEditorProps.editorRef}
/>
</TestProviderStack>;
};
describe('MarkdownEditor', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(0);
await switchClient(0);
Setting.setValue('editor.codeView', true);
});
// Regression test for #13193. This verifies that the editor can be reached
// over IPC.
it('should support the "textBold" command', async () => {
let editorBody = 'test';
const editorRef = React.createRef<EditorControl|null>();
render(<WrappedEditor
ref={editorRef}
noteBody={editorBody}
onBodyChange={newValue => { editorBody = newValue; }}
/>);
// Should mark the command as supported
expect(await editorRef.current.supportsCommand(EditorCommandType.ToggleBolded));
// Command should run
await editorRef.current.execCommand(EditorCommandType.SelectAll);
await editorRef.current.execCommand(EditorCommandType.ToggleBolded);
await waitFor(() => {
expect(editorBody).toBe('**test**');
});
});
});

View File

@@ -121,6 +121,7 @@ const useStyles = (theme: Theme) => {
height: buttonSize,
backgroundColor: theme.backgroundColor4,
color: theme.color4,
margin: 2,
},
buttonText: buttonTextStyle,
activeButtonText: {
@@ -352,7 +353,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
);
const advancedLayout = (
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
<View style={{ flexDirection: 'column' }}>
<View style={{ flexDirection: 'row' }}>
{ closeButton }
{ labeledSearchInput }

View File

@@ -54,7 +54,6 @@ const useStyles = (themeId: number, headerStyle: TextStyle|undefined) => {
},
tagBoxRoot: {
flexDirection: 'column',
flexGrow: 0.5,
flexShrink: 1,
},
tagBoxScrollView: {
@@ -86,6 +85,7 @@ const useStyles = (themeId: number, headerStyle: TextStyle|undefined) => {
backgroundColor: theme.dividerColor,
},
tagSearch: {
flexGrow: 1,
flexShrink: 1,
},
noTagsLabel: {

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { Card, TouchableRipple } from 'react-native-paper';
import { useMemo } from 'react';
import { StyleSheet, View, ViewStyle } from 'react-native';
import { Platform, StyleSheet, View, ViewStyle } from 'react-native';
export enum InstallState {
NotInstalled,
@@ -20,16 +20,25 @@ interface Props {
const useStyles = (disabled: boolean) => {
return useMemo(() => {
// For the TouchableRipple to work on Android, the card needs a transparent background.
const baseCard = { backgroundColor: 'transparent' };
const borderRadius = 12;
const baseCard = { backgroundColor: 'transparent', borderRadius };
return StyleSheet.create({
cardOuterWrapper: {
margin: 0,
padding: 0,
borderRadius: 12,
borderRadius,
overflow: 'hidden',
// Accessibility: Prevent the 'overflow: hidden' from hiding the focus indicator
// on web. Only apply to web, as this causes the touchable ripple
// from being completely contained within the card on non-web platforms.
...(Platform.OS === 'web' ? {
margin: -2,
padding: 2,
} : {}),
},
cardInnerWrapper: {
width: '100%',
borderRadius,
},
card: disabled ? {
...baseCard,

View File

@@ -629,7 +629,7 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
);
}
addSettingLink('donate_link', _('Make a donation'), 'https://joplinapp.org/donate/');
if (Platform.OS !== 'ios') addSettingLink('donate_link', _('Make a donation'), 'https://joplinapp.org/donate/');
addSettingLink('website_link', _('Joplin website'), 'https://joplinapp.org/');
addSettingLink('privacy_link', _('Privacy Policy'), 'https://joplinapp.org/privacy/');

View File

@@ -515,6 +515,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
paddingLeft: theme.marginLeft,
borderBottomColor: theme.dividerColor,
borderBottomWidth: 1,
maxHeight: '40%',
};
styles.titleContainerTodo = { ...styles.titleContainer };
@@ -659,6 +660,17 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
});
}
// Reset undo/redo button state when switching to edit mode or when switching between markdown and rich text editors, since the editor is
// recreated and loses its undo/redo history
if (this.state.mode === 'edit' && (prevState.mode !== this.state.mode || prevProps.editorType !== this.props.editorType)) {
this.setState({
undoRedoButtonState: {
canUndo: false,
canRedo: false,
},
});
}
if (prevProps.noteId && this.props.noteId && prevProps.noteId !== this.props.noteId) {
// Easier to just go back, then go to the note since
// the Note screen doesn't handle reloading a different note
@@ -690,6 +702,10 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
if (prevState.note.body !== this.state.note.body) {
this.emitEditorPluginUpdate_();
}
if (prevState.multiline !== this.state.multiline && this.titleTextFieldRef.current) {
focus('Note::focusUpdate::title', this.titleTextFieldRef.current);
}
}
public componentWillUnmount() {
@@ -1703,6 +1719,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
<View style={titleContainerStyle}>
{isTodo && <Checkbox style={this.styles().checkbox} checked={!!Number(note.todo_completed)} onChange={this.todoCheckbox_change} />}
<TextInput
key={this.state.multiline ? 'multiLine' : 'singleLine'}
ref={this.titleTextFieldRef}
underlineColorAndroid="#ffffff00"
autoCapitalize="sentences"

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { View, StyleSheet, TextInput, Platform } from 'react-native';
import { View, StyleSheet, TextInput, Platform, ScrollView, Text as TextNative } from 'react-native';
import { AppState } from '../../utils/types';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import Revision from '@joplin/lib/models/Revision';
@@ -111,6 +111,7 @@ const useStyles = (themeId: number) => {
flex: 0,
flexDirection: 'row',
flexBasis: 'auto',
maxHeight: '40%',
},
titleText: {
flex: 1,
@@ -224,12 +225,25 @@ const NoteRevisionViewer: React.FC<Props> = props => {
const titleComponent = (
<View style={styles.titleViewContainer}>
<TextInput
style={styles.titleText}
value={note?.title ?? ''}
editable={false}
multiline={multiline}
/>
{
multiline ?
<ScrollView
style={{ flex: 1 }}
showsVerticalScrollIndicator={false}
>
<TextNative
selectable
style={styles.titleText}
>
{note?.title ?? ''}
</TextNative>
</ScrollView> :
<TextInput
style={styles.titleText}
value={note?.title ?? ''}
editable={false}
/>
}
{ titleToggleButton }
</View>
);

View File

@@ -9,6 +9,7 @@ import TagEditor, { TagEditorMode } from '../TagEditor';
import { _ } from '@joplin/lib/locale';
import { useCallback, useEffect, useState } from 'react';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import { ViewStyle } from 'react-native';
interface Props {
themeId: number;
@@ -22,6 +23,9 @@ const modalPropOverrides = {
// Prevent the keyboard from auto-dismissing when tapping outside the search input
keyboardShouldPersistTaps: true,
},
containerStyle: {
height: '100%',
} as ViewStyle,
};
const NoteTagsDialogComponent: React.FC<Props> = props => {

View File

@@ -88,7 +88,7 @@ const useWebViewSetup = ({
`;
const injectedJavaScript = useMemo(() => `
if (typeof markdownEditorBundle === 'undefined') {
if (typeof window.markdownEditorBundle === 'undefined') {
${shim.injectedJs('markdownEditorBundle')};
window.markdownEditorBundle = markdownEditorBundle;
markdownEditorBundle.setUpLogger();

View File

@@ -535,7 +535,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 146;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
@@ -570,7 +570,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 146;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
@@ -771,7 +771,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 146;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
@@ -814,7 +814,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 146;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;

View File

@@ -29,7 +29,7 @@
"@joplin/renderer": "~3.5",
"@joplin/utils": "~3.5",
"@react-native-clipboard/clipboard": "1.16.3",
"@react-native-community/datetimepicker": "8.4.2",
"@react-native-community/datetimepicker": "8.4.3",
"@react-native-community/geolocation": "3.4.0",
"@react-native-community/netinfo": "11.4.1",
"@react-native-community/push-notification-ios": "1.11.0",
@@ -66,9 +66,9 @@
"react-native-quick-actions": "0.3.13",
"react-native-quick-crypto": "0.7.17",
"react-native-rsa-native": "2.0.5",
"react-native-safe-area-context": "5.4.1",
"react-native-safe-area-context": "5.5.2",
"react-native-securerandom": "1.0.1",
"react-native-share": "12.1.0",
"react-native-share": "12.1.2",
"react-native-sqlite-storage": "6.0.1",
"react-native-svg": "15.13.0",
"react-native-url-polyfill": "2.0.0",
@@ -101,7 +101,7 @@
"@react-native-community/cli-platform-ios": "16.0.3",
"@react-native/babel-preset": "0.80.1",
"@react-native/metro-config": "0.79.5",
"@react-native/typescript-config": "0.79.5",
"@react-native/typescript-config": "0.80.2",
"@sqlite.org/sqlite-wasm": "3.46.0-build2",
"@testing-library/react-native": "13.2.0",
"@types/fs-extra": "11.0.4",
@@ -109,13 +109,13 @@
"@types/node": "18.19.130",
"@types/react": "19.0.14",
"@types/react-redux": "7.1.33",
"@types/serviceworker": "0.0.144",
"@types/serviceworker": "0.0.149",
"@types/tar-stream": "3.1.4",
"babel-jest": "29.7.0",
"babel-loader": "9.1.3",
"babel-plugin-module-resolver": "4.1.0",
"babel-plugin-react-native-web": "0.20.0",
"esbuild": "0.25.7",
"esbuild": "0.25.8",
"fast-deep-equal": "3.1.3",
"fs-extra": "11.2.0",
"gulp": "4.0.2",
@@ -133,7 +133,7 @@
"sharp": "0.34.3",
"sqlite3": "5.1.6",
"timers-browserify": "2.0.12",
"ts-jest": "29.3.4",
"ts-jest": "29.4.1",
"ts-loader": "9.5.2",
"ts-node": "10.9.2",
"typescript": "5.8.3",

View File

@@ -67,6 +67,11 @@ const appReducer = (state = appDefaultState, action: any) => {
newState.selectedNoteHash = '';
if (currentRoute.routeName === 'Search' && action.routeName === 'Notes') {
// Force a reload of the note list
newState.notesSource = '';
}
if (action.routeName === 'Search') {
newState.notesParentType = 'Search';
}

View File

@@ -11,7 +11,7 @@ const useSafeAreaPadding = () => {
paddingRight: safeAreaInsets.right,
paddingLeft: safeAreaInsets.left,
paddingTop: safeAreaInsets.top,
paddingBottom: 0,
paddingBottom: safeAreaInsets.bottom,
} : {
paddingTop: safeAreaInsets.top,
paddingBottom: safeAreaInsets.bottom,

View File

@@ -29,10 +29,14 @@ interface CssDecorationSpec extends DecorationRange {
id?: number;
}
interface RemoveMarkDecorationSpec {
id: number;
}
const addLineDecorationEffect = StateEffect.define<CssDecorationSpec>(mapRangeConfig);
const removeLineDecorationEffect = StateEffect.define<CssDecorationSpec>(mapRangeConfig);
const addMarkDecorationEffect = StateEffect.define<CssDecorationSpec>(mapRangeConfig);
const removeMarkDecorationEffect = StateEffect.define<CssDecorationSpec>(mapRangeConfig);
const removeMarkDecorationEffect = StateEffect.define<RemoveMarkDecorationSpec>();
const refreshOverlaysEffect = StateEffect.define();
export interface LineWidgetOptions {
@@ -190,12 +194,13 @@ export default class Decorator {
decorations = decorations.update({
add: [decoration.range(from, to)],
});
} else if (effect.is(removeLineDecorationEffect) || effect.is(removeMarkDecorationEffect)) {
} else if (effect.is(removeLineDecorationEffect)) {
const doc = transaction.state.doc;
const targetFrom = doc.lineAt(effect.value.from).from;
const targetTo = doc.lineAt(effect.value.to).to;
const { from, to } = effect.value;
// Handle the case where { from, to } point to an outdated document
const targetFrom = doc.lineAt(from).from;
const targetTo = doc.lineAt(to).to;
const targetId = effect.value.id;
const targetDecoration = this.classNameToCssDecoration(
effect.value.cssClass, effect.is(removeLineDecorationEffect),
);
@@ -203,12 +208,15 @@ export default class Decorator {
decorations = decorations.update({
// Returns true only for decorations that should be kept.
filter: (from, to, value) => {
if (targetId !== undefined) {
return value.spec.id !== effect.value.id;
}
const isInRange = from >= targetFrom && to <= targetTo;
return isInRange && value.eq(targetDecoration);
return !isInRange || !value.eq(targetDecoration);
},
});
} else if (effect.is(removeMarkDecorationEffect)) {
decorations = decorations.update({
// Returns true only for decorations that should be kept.
filter: (_from, _to, value) => {
return value.spec.id !== effect.value.id;
},
});
} else if (effect.is(addLineWidgetEffect)) {
@@ -384,9 +392,10 @@ export default class Decorator {
}
public markText(from: number, to: number, options?: MarkTextOptions) {
const id = this._nextLineWidgetId++;
const effectOptions: CssDecorationSpec = {
cssClass: options.className ?? '',
id: this._nextLineWidgetId++,
id,
from,
to,
};
@@ -398,7 +407,7 @@ export default class Decorator {
return {
clear: () => {
this.editor.dispatch({
effects: removeMarkDecorationEffect.of(effectOptions),
effects: removeMarkDecorationEffect.of({ id }),
});
},
};

View File

@@ -24,7 +24,7 @@
"@types/styled-components": "5.1.32",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"ts-jest": "29.3.4",
"ts-jest": "29.4.1",
"typescript": "5.8.3"
},
"dependencies": {
@@ -53,7 +53,7 @@
"prosemirror-history": "1.4.1",
"prosemirror-inputrules": "1.5.0",
"prosemirror-keymap": "1.2.3",
"prosemirror-model": "1.25.2",
"prosemirror-model": "1.25.3",
"prosemirror-schema-list": "1.5.1",
"prosemirror-search": "1.1.0",
"prosemirror-state": "1.4.3",

View File

@@ -52,8 +52,8 @@
"coveralls": "3.1.1",
"eslint": "8.57.1",
"jest": "29.7.0",
"prettier": "3.5.3",
"ts-jest": "29.3.4",
"prettier": "3.6.2",
"ts-jest": "29.4.1",
"typescript": "5.8.3"
},
"jest": {

View File

@@ -78,7 +78,7 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
return super.fileApi();
}
public static async checkConfig(options: FileApiOptions, syncTargetId: number = null) {
public static async checkConfig(options: FileApiOptions, syncTargetId: number = null, fileApi: FileApi|null = null) {
const output = {
ok: false,
errorMessage: '',
@@ -86,50 +86,57 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
syncTargetId = syncTargetId === null ? this.id() : syncTargetId;
let fileApi = null;
try {
fileApi = await newFileApi(syncTargetId, options);
fileApi.requestRepeatCount_ = 0;
} catch (error) {
// If there's an error it's probably an application error, but we
// can't proceed anyway, so exit.
output.errorMessage = error.message;
if (error.code) output.errorMessage += ` (Code ${error.code})`;
return output;
}
// First we try to fetch info.json. It may not be present if it's a new
// sync target but otherwise, if it is, and it's valid, we know the
// credentials are valid. We do this test first because it will work
// even if account upload is disabled. And we need such account to
// successfully login so that they can fix it by deleting extraneous
// notes or resources.
try {
const r = await fileApi.get('info.json');
if (r) {
const parsed = JSON.parse(r);
if (parsed) {
output.ok = true;
return output;
}
if (!fileApi) {
try {
fileApi = await newFileApi(syncTargetId, options);
} catch (error) {
// If there's an error it's probably an application error, but we
// can't proceed anyway, so exit.
output.errorMessage = error.message;
if (error.code) output.errorMessage += ` (Code ${error.code})`;
return output;
}
} catch (error) {
// Ignore because we'll use the next test to check for sure if it
// works or not.
staticLogger.warn('Could not fetch or parse info.json:', error);
}
// This is a more generic test, which writes a file and tries to read it
// back.
const previousRequestRepeatCount = fileApi.requestRepeatCount_;
fileApi.requestRepeatCount_ = 0;
try {
await fileApi.put('testing.txt', 'testing');
const result = await fileApi.get('testing.txt');
if (result !== 'testing') throw new Error(`Could not access data on server "${options.path()}"`);
await fileApi.delete('testing.txt');
output.ok = true;
} catch (error) {
output.errorMessage = error.message;
if (error.code) output.errorMessage += ` (Code ${error.code})`;
// First we try to fetch info.json. It may not be present if it's a new
// sync target but otherwise, if it is, and it's valid, we know the
// credentials are valid. We do this test first because it will work
// even if account upload is disabled. And we need such account to
// successfully login so that they can fix it by deleting extraneous
// notes or resources.
try {
const r = await fileApi.get('info.json');
if (r) {
const parsed = JSON.parse(r);
if (parsed) {
output.ok = true;
return output;
}
}
} catch (error) {
// Ignore because we'll use the next test to check for sure if it
// works or not.
staticLogger.warn('Could not fetch or parse info.json:', error);
}
// This is a more generic test, which writes a file and tries to read it
// back.
try {
await fileApi.put('testing.txt', 'testing');
const result = await fileApi.get('testing.txt');
if (result !== 'testing') throw new Error(`Could not access data on server "${options.path()}"`);
await fileApi.delete('testing.txt');
output.ok = true;
} catch (error) {
output.errorMessage = error.message;
if (error.code) output.errorMessage += ` (Code ${error.code})`;
}
} finally {
fileApi.requestRepeatCount_ = previousRequestRepeatCount;
}
return output;

View File

@@ -52,6 +52,9 @@ export const authenticateWithCode = async (code: string) => {
//
// Based on the regular Joplin Server sync target.
export default class SyncTargetJoplinServerSAML extends SyncTargetJoplinServer {
private lastFileApiOptions_: FileApiOptions|null = null;
public static override id() {
return 11;
}
@@ -65,7 +68,16 @@ export default class SyncTargetJoplinServerSAML extends SyncTargetJoplinServer {
}
public override async isAuthenticated() {
return Setting.value('sync.11.id') !== '';
if (!Setting.value('sync.11.id')) return false;
// We check that the file API has been initialized at least once, otherwise the below check
// will always fail and it will be impossible to login.
if (this.lastFileApiOptions_) {
const check = await SyncTargetJoplinServer.checkConfig(null, null, await this.fileApi());
return check.ok;
}
return true;
}
public static override requiresPassword() {
@@ -111,11 +123,12 @@ export default class SyncTargetJoplinServerSAML extends SyncTargetJoplinServer {
}
protected override async initFileApi() {
return initFileApi(SyncTargetJoplinServerSAML.id(), this.logger(), {
this.lastFileApiOptions_ = {
path: () => Setting.value('sync.11.path'),
userContentPath: () => Setting.value('sync.11.userContentPath'),
username: () => '',
password: () => '',
});
};
return initFileApi(SyncTargetJoplinServerSAML.id(), this.logger(), this.lastFileApiOptions_);
}
}

View File

@@ -457,30 +457,30 @@ export default class Synchronizer {
}
}
// Before synchronising make sure all share_id properties are set
// correctly so as to share/unshare the right items.
try {
await Folder.updateAllShareIds(this.resourceService(), this.shareService_ ? this.shareService_.shares : []);
if (this.shareService_) await this.shareService_.checkShareConsistency();
} catch (error) {
if (error && error.code === ErrorCode.IsReadOnly) {
// We ignore it because the functions above tried to modify a
// read-only item and failed. Normally it shouldn't happen since
// the UI should prevent, but if there's a bug in the UI or some
// other issue we don't want sync to fail because of this.
logger.error('Could not update share because an item is readonly:', error);
} else {
throw error;
}
}
const itemUploader = new ItemUploader(this.api(), this.apiCall);
let errorToThrow = null;
let syncLock = null;
let hasCaughtError = false;
try {
// Before synchronising make sure all share_id properties are set
// correctly so as to share/unshare the right items.
try {
await Folder.updateAllShareIds(this.resourceService(), this.shareService_ ? this.shareService_.shares : []);
if (this.shareService_) await this.shareService_.checkShareConsistency();
} catch (error) {
if (error && error.code === ErrorCode.IsReadOnly) {
// We ignore it because the functions above tried to modify a
// read-only item and failed. Normally it shouldn't happen since
// the UI should prevent, but if there's a bug in the UI or some
// other issue we don't want sync to fail because of this.
logger.error('Could not update share because an item is readonly:', error);
} else {
throw error;
}
}
const itemUploader = new ItemUploader(this.api(), this.apiCall);
await this.api().initialize();
this.api().setTempDirName(Dirnames.Temp);

View File

@@ -350,7 +350,7 @@ class WebDavApi {
// https://github.com/facebook/react-native/issues/30176
if (!headers['Content-Type']) {
if (method === 'PROPFIND') headers['Content-Type'] = 'text/xml';
if (method === 'PUT') headers['Content-Type'] = 'text/plain';
if (method === 'PUT') headers['Content-Type'] = 'application/octet-stream';
}
// React-native has caching enabled by at least on Android (see https://github.com/laurent22/joplin/issues/4706 and the related PR).

View File

@@ -2,6 +2,7 @@ import { utils, CommandRuntime, CommandDeclaration, CommandContext } from '../se
import { _ } from '../locale';
import { reg } from '../registry';
import Setting from '../models/Setting';
import NavService from '../services/NavService';
export const declaration: CommandDeclaration = {
name: 'synchronize',
@@ -35,7 +36,18 @@ export const runtime = (): CommandRuntime => {
return 'auth';
}
reg.logger().error('Not authenticated with sync target - please check your credentials.');
const error = new Error('Not authenticated with sync target - please check your credentials.');
utils.store.dispatch({
type: 'SYNC_REPORT_UPDATE',
report: { errors: [error] },
});
if (Setting.value('sync.target') === 11) {
await NavService.go('JoplinServerSamlLogin');
}
reg.logger().error(error);
return 'error';
}

View File

@@ -42,20 +42,6 @@ const geoipServices: Record<string, GeoipService> = {
};
},
geoplugin: async (): Promise<CurrentPositionResponse> => {
const r = await fetchJson('http://www.geoplugin.net/json.gp');
if (!('geoplugin_latitude' in r) || !('geoplugin_longitude' in r)) throw new Error(`Invalid geolocation response: ${r ? JSON.stringify(r) : '<null>'}`);
return {
timestamp: Date.now(),
coords: {
longitude: Number(r.geoplugin_longitude),
altitude: 0,
latitude: Number(r.geoplugin_latitude),
},
};
},
};
export default class {

View File

@@ -182,6 +182,9 @@
"Add body": [
"أضف الجسم"
],
"Add column": [
""
],
"Add new": [
"إضافة جديدة"
],

View File

@@ -170,6 +170,9 @@
"Add body": [
""
],
"Add column": [
""
],
"Add or remove tags:": [
"Добавяне и махане на тагове:"
],

View File

@@ -167,6 +167,9 @@
"Add body": [
"Dodaj tekst"
],
"Add column": [
""
],
"Add or remove tags:": [
"Dodajte ili uklonite oznake:"
],

View File

@@ -168,7 +168,7 @@
"[None]"
],
"A brief description of the image:": [
""
"Una breu descripció de la imatge:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"Una llista de paraules separades per comes. Es pot utilitzar per a paraules poc comunes, per ajudar a escriure la veu a escriure-les correctament."
@@ -242,6 +242,9 @@
"Add body": [
"Afegeix contingut"
],
"Add column": [
"Afegeix una columna"
],
"Add new": [
"Afegeix-ne un"
],
@@ -251,6 +254,9 @@
"Add recipient:": [
"Afegeix un destinatari:"
],
"Add row": [
"Afegeix una fila"
],
"Add tags:": [
"Afegir etiquetes:"
],
@@ -339,7 +345,7 @@
"S'ha trobat un dibuix desat automàticament. Voleu adjuntar-ne una còpia a la nota?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"S'ha produït un error en enviar la resposta. Això pot passar si l'aplicació està fora de línia o no es pot connectar al servidor.\nError: %s"
],
"An error occurred: %s": [
"S'ha produït un error: %s"
@@ -680,6 +686,9 @@
"Collapse all notebooks": [
"Redueix totes les llibretes"
],
"Collapse title": [
"Replega el títol"
],
"Collapsed": [
"Col·lapsat"
],
@@ -1018,6 +1027,9 @@
"Delete attachment \"%s\"?": [
"Voleu suprimir l'adjunt \"%s\"?"
],
"Delete column": [
"Suprimeix la columna"
],
"Delete expired sessions": [
"Suprimeix les sessions expirades"
],
@@ -1045,6 +1057,9 @@
"Delete profile \"%s\"": [
"Suprimeix el perfil \"%s\"?"
],
"Delete row": [
"Suprimeix la fila"
],
"Delete selected notes": [
"Suprimeix les notes seleccionades"
],
@@ -1178,7 +1193,7 @@
"No perdeu les contrasenyes perquè, per motius de seguretat, és l'única forma de desxifrar les dades. Per habilitar el xifrat, introduïu la vostra contrasenya."
],
"Do you find the Joplin web app useful?": [
""
"Trobes útil l'aplicació web Joplin?"
],
"Document scanner: Title template": [
"Escàner de documents: plantilla de títol"
@@ -1537,6 +1552,9 @@
"Expand all notebooks": [
"Desplegar totes les llibretes"
],
"Expand title": [
"Expandeix el títol"
],
"Expanded": [
"Expandit"
],
@@ -1601,7 +1619,7 @@
"Interruptors de funcions"
],
"Feedback": [
""
"Comentaris"
],
"Fetched items: %d/%d.": [
"Elements obtinguts: %d/%d."
@@ -2015,6 +2033,9 @@
"Joplin Server": [
"Servidor del Joplin"
],
"Joplin Server (SAML)": [
"Servidor Joplin (SAML)"
],
"Joplin Server Business": [
"Joplin Servidor d'empreses"
],
@@ -2034,7 +2055,7 @@
"Autenticació SSO de Joplin"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"Joplin admet desar la ubicació a la qual es desen o es creen les notes. Voleu habilitar-lo? Això es pot canviar en qualsevol moment a la configuració."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"El porta-retalls de webs del Joplin us permet desar pàgines web i captures de pantalla del navegador web al Joplin."
@@ -2067,7 +2088,7 @@
"Claus que requereixen actualització"
],
"Label": [
""
"Etiqueta"
],
"Landscape": [
"Paisatge"
@@ -2224,6 +2245,9 @@
"Markdown editor": [
"Editor Markdown"
],
"Markdown editor: Highlight active line": [
"Editor Markdown: ressalta la línia activa"
],
"Markdown editor: Render images": [
"Editor Markdown: Renderització d'imatges"
],
@@ -2484,7 +2508,7 @@
"No ara"
],
"Not useful": [
""
"No és útil"
],
"note": [
"nota"
@@ -2534,6 +2558,9 @@
"Note list style": [
"Estil de llista de notes"
],
"Note not published: %s": [
"Nota no publicada: %s"
],
"Note preview": [
"Vista prèvia de la nota"
],
@@ -2694,7 +2721,7 @@
"Llista ordenada"
],
"Other": [
""
"Altres"
],
"Other applications...": [
"Altres aplicacions."
@@ -2946,6 +2973,9 @@
"Publish Note": [
"Publica la nota"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"Publicar la nota \"%s\" (a la llibreta \"%s\")?"
],
"Publish note...": [
"Publica la nota..."
],
@@ -2958,8 +2988,11 @@
"Publish/unpublish": [
"Publica/despublica"
],
"Published at URL: %s": [
"Publicat a la URL: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Publica una nota a Joplin Server o Joplin Cloud"
],
"QR Code": [
"Codi QR"
@@ -3006,6 +3039,9 @@
"Recipients:": [
"Destinataris:"
],
"Recognise text:": [
"Reconeix el text:"
],
"Recognize handwritten image": [
"Reconeix la imatge manuscrita"
],
@@ -3234,6 +3270,9 @@
"Save geo-location with notes": [
"Desa la geolocalització a les notes"
],
"Save geolocation?": [
"Voleu desar la geolocalització?"
],
"Scan notebook": [
"Escaneja la llibreta"
],
@@ -3304,7 +3343,7 @@
"Selecciona la llibreta"
],
"Select one of the other supported sync targets.": [
""
"Seleccioneu un dels altres objectius de sincronització admesos."
],
"Select parent notebook": [
"Selecciona la llibreta principal"
@@ -3643,7 +3682,7 @@
"Canvia a [notebook] - totes les operacions posteriors s'aplicaran en aquesta llibreta."
],
"Sync": [
""
"Sincronitza"
],
"Sync as many devices as you want": [
"Sincronitza tants dispositius com vulguis"
@@ -3723,6 +3762,9 @@
"Take photo": [
"Fes una foto"
],
"Take survey": [
"Fer enquesta"
],
"Task \"%s\" failed with error: %s": [
"La tasca \"%s\" ha fallat amb l'error: %s"
],
@@ -3742,7 +3784,7 @@
"Ordre de l'editor de text"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"Gràcies pels comentaris!\nTens temps per fer una petita enquesta?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"El perfil actiu no es pot esborrar. Canvia a un perfil diferent i torna-ho a provar."
@@ -4142,6 +4184,9 @@
"Unable to share log data. Reason: %s": [
"No s'han pogut compartir les dades del registre. Motiu: %s"
],
"Unable to share note data. Reason: %s": [
"No s'han pogut compartir les dades de la nota. Motiu: %s"
],
"Unchecked": [
"Desmarcat"
],
@@ -4287,7 +4332,7 @@
"S'utilitza quan es necessita un tipus de lletra d'amplada fixa per a mostrar text de manera llegible (p. ex. taules, caselles de selecció, codi). Si no es troba, s'utilitza un tipus de lletra genèric monoespai (amplada fixa)."
],
"Useful": [
""
"Útil"
],
"User deletions": [
"Supressions d'usuari"
@@ -4395,7 +4440,7 @@
"En crear una tasca nova:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Quan està activat, sol·licita que les imatges de la nota es transcriguin amb un servei de transcripció al servidor de més qualitat. Requereix sincronització amb una còpia de l'aplicació d'escriptori."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Quan estigui activada, l'aplicació escanejarà els adjunts i n'extraurà el text. Això us permetrà cercar text en aquests adjunts."

View File

@@ -185,6 +185,9 @@
"Add body": [
"Přidat tělo"
],
"Add column": [
""
],
"Add new": [
"Přidat nový"
],

View File

@@ -242,6 +242,9 @@
"Add body": [
"Tilføj brødtekst"
],
"Add column": [
""
],
"Add new": [
"Tilføj ny"
],

View File

@@ -242,6 +242,9 @@
"Add body": [
"Text hinzufügen"
],
"Add column": [
""
],
"Add new": [
"Neu hinzufügen"
],

View File

@@ -200,6 +200,9 @@
"Add body": [
"Προσθήκη σώματος κειμένου"
],
"Add column": [
""
],
"Add new": [
"Προσθήκη νέου"
],

View File

@@ -242,6 +242,9 @@
"Add body": [
""
],
"Add column": [
""
],
"Add new": [
""
],
@@ -251,6 +254,9 @@
"Add recipient:": [
""
],
"Add row": [
""
],
"Add tags:": [
""
],
@@ -1021,6 +1027,9 @@
"Delete attachment \"%s\"?": [
""
],
"Delete column": [
""
],
"Delete expired sessions": [
""
],
@@ -1048,6 +1057,9 @@
"Delete profile \"%s\"": [
""
],
"Delete row": [
""
],
"Delete selected notes": [
""
],

View File

@@ -239,6 +239,9 @@
"Add body": [
""
],
"Add column": [
""
],
"Add new": [
""
],
@@ -248,6 +251,9 @@
"Add recipient:": [
""
],
"Add row": [
""
],
"Add tags:": [
""
],
@@ -1009,6 +1015,9 @@
"Delete attachment \"%s\"?": [
""
],
"Delete column": [
""
],
"Delete expired sessions": [
""
],
@@ -1036,6 +1045,9 @@
"Delete profile \"%s\"": [
""
],
"Delete row": [
""
],
"Delete selected notes": [
""
],

View File

@@ -177,6 +177,9 @@
"Add body": [
"Aldoni korpon"
],
"Add column": [
""
],
"Add or remove tags:": [
"Aldoni aŭ forigi etikedojn:"
],

View File

@@ -168,7 +168,7 @@
"[none]"
],
"A brief description of the image:": [
""
"Una breve descripción de la imagen:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"Una lista de palabras separadas por comas. Se puede usar para palabras poco comunes, para ayudar a la escritura por voz a deletrearlas correctamente."
@@ -242,6 +242,9 @@
"Add body": [
"Añadir cuerpo"
],
"Add column": [
"Añadir columna"
],
"Add new": [
"Añadir nuevo"
],
@@ -251,6 +254,9 @@
"Add recipient:": [
"Agregar destinatario:"
],
"Add row": [
"Añadir fila"
],
"Add tags:": [
"Añadir etiquetas:"
],
@@ -339,7 +345,7 @@
"Se ha encontrado un dibujo autoguardado. ¿Adjuntar una copia a la nota?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"Se ha producido un error al enviar la respuesta. Esto puede ocurrir si la aplicación no está en línea o no puede conectarse al servidor.\nError: %s"
],
"An error occurred: %s": [
"Se ha producido un error: %s"
@@ -680,11 +686,14 @@
"Collapse all notebooks": [
"Contraer todas las libretas"
],
"Collapse title": [
"Contraer título"
],
"Collapsed": [
"Colapsado"
],
"Coming alarms": [
"Alarmas próximas"
"Próximas alarmas"
],
"Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on \"Check synchronisation configuration\".": [
"Lista separada por comas de rutas de acceso a directorios desde los que cargar los certificados, o ruta de acceso a archivos de certificado individuales. Por ejemplo: /my/cert_dir, /other/custom.pem. Tenga en cuenta que si realiza cambios en la configuración de TLS, debe guardar los cambios antes de hacer clic en \"Comprobar configuración de sincronización\"."
@@ -1018,6 +1027,9 @@
"Delete attachment \"%s\"?": [
"¿Eliminar el archivo adjunto \"%s\"?"
],
"Delete column": [
"Borrar columna"
],
"Delete expired sessions": [
"Borrar sesiones expiradas"
],
@@ -1045,6 +1057,9 @@
"Delete profile \"%s\"": [
"¿Borrar perfil \"%s\"?"
],
"Delete row": [
"Borrar fila"
],
"Delete selected notes": [
"Borrar notas seleccionadas"
],
@@ -1178,7 +1193,7 @@
"¡No pierda la contraseña ya que, por motivos de seguridad, esta será la *única* forma de descifrar los datos! Para habilitar el cifrado, ingrese su contraseña a continuación."
],
"Do you find the Joplin web app useful?": [
""
"¿Le resulta útil la aplicación web de Joplin?"
],
"Document scanner: Title template": [
"Escáner de documentos: Plantilla de título"
@@ -1537,6 +1552,9 @@
"Expand all notebooks": [
"Expandir todas las libretas"
],
"Expand title": [
"Expandir título"
],
"Expanded": [
"Expandido"
],
@@ -1601,7 +1619,7 @@
"Feature flags"
],
"Feedback": [
""
"Comentarios"
],
"Fetched items: %d/%d.": [
"Elementos obtenidos: %d/%d."
@@ -2015,6 +2033,9 @@
"Joplin Server": [
"Servidor de Joplin"
],
"Joplin Server (SAML)": [
"Servidor Joplin (SAML)"
],
"Joplin Server Business": [
"Servidor Joplin para empresas"
],
@@ -2034,7 +2055,7 @@
"Autenticación SSO de Joplin"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"Joplin permite guardar la ubicación en la que se guardan o crean las notas. ¿Quieres habilitarlo? Esto se puede cambiar en cualquier momento en la configuración."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"El Web Clipper de Joplin le permite guardar páginas web y capturas de pantalla desde su navegador a la aplicación."
@@ -2067,7 +2088,7 @@
"Claves que necesitan actualizarse"
],
"Label": [
""
"Etiqueta"
],
"Landscape": [
"Apaisado"
@@ -2224,6 +2245,9 @@
"Markdown editor": [
"Editor de Markdown"
],
"Markdown editor: Highlight active line": [
"Editor de Markdown: Resaltar línea activa"
],
"Markdown editor: Render images": [
"Editor de Markdown: Renderizar imágenes"
],
@@ -2484,7 +2508,7 @@
"Ahora no"
],
"Not useful": [
""
"No es útil"
],
"note": [
"nota"
@@ -2534,6 +2558,9 @@
"Note list style": [
"Estilo de lista de nota"
],
"Note not published: %s": [
"Nota no publicada: %s"
],
"Note preview": [
"Vista previa de la nota"
],
@@ -2694,7 +2721,7 @@
"Lista ordenada"
],
"Other": [
""
"Otro"
],
"Other applications...": [
"Otras aplicaciones..."
@@ -2946,6 +2973,9 @@
"Publish Note": [
"Publicar nota"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"¿Publicar la nota \"%s\" (en la libreta \"%s\")?"
],
"Publish note...": [
"Publicar nota..."
],
@@ -2958,8 +2988,11 @@
"Publish/unpublish": [
"Publicar/despublicar"
],
"Published at URL: %s": [
"Publicado en URL: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Publica una nota en Joplin Server o Joplin Cloud"
],
"QR Code": [
"Código QR"
@@ -3006,6 +3039,9 @@
"Recipients:": [
"Destinatarios:"
],
"Recognise text:": [
"Reconocer texto:"
],
"Recognize handwritten image": [
"Reconocer imagen manuscrita"
],
@@ -3234,6 +3270,9 @@
"Save geo-location with notes": [
"Guardar geolocalización en las notas"
],
"Save geolocation?": [
"¿Guardar la geolocalización?"
],
"Scan notebook": [
"Escanear libreta"
],
@@ -3304,7 +3343,7 @@
"Seleccionar libreta"
],
"Select one of the other supported sync targets.": [
""
"Seleccione uno de los otros destinos de sincronización admitidos."
],
"Select parent notebook": [
"Seleccionar libreta principal"
@@ -3643,7 +3682,7 @@
"Cambia a [notebook] - todas las demás operaciones se realizarán en esta libreta."
],
"Sync": [
""
"Sincronizar"
],
"Sync as many devices as you want": [
"Sincronice todos los dispositivos que desee"
@@ -3723,6 +3762,9 @@
"Take photo": [
"Tomar foto"
],
"Take survey": [
"Responder encuesta"
],
"Task \"%s\" failed with error: %s": [
"Error en la tarea \"%s\" con error: %s"
],
@@ -3742,7 +3784,7 @@
"Comando de editor de texto"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"¡Gracias por los comentarios!\n¿Tienes tiempo para completar una breve encuesta?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"El perfil activo no puede eliminarse. Cambie de perfil e intente de nuevo."
@@ -4142,6 +4184,9 @@
"Unable to share log data. Reason: %s": [
"No se pueden compartir los datos de registro. Motivo: %s"
],
"Unable to share note data. Reason: %s": [
"No se pueden compartir los datos de la nota. Motivo: %s"
],
"Unchecked": [
"No marcado"
],
@@ -4287,7 +4332,7 @@
"Se utiliza donde se necesita una fuente de ancho fijo para presentar el texto de manera legible (por ejemplo, tablas, casillas de verificación, código). Si no se encuentra, se utiliza una fuente genérica monoespaciada (ancho fijo)."
],
"Useful": [
""
"Útil"
],
"User deletions": [
"Eliminaciones de usuarios"
@@ -4395,7 +4440,7 @@
"Al crear una tarea nueva:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Cuando está activada, solicita que las imágenes de la nota se transcriban con un servicio de transcripción en el servidor de mayor calidad. Requiere sincronización con una copia de la aplicación de escritorio."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Cuando está habilitado, la aplicación escaneará sus archivos adjuntos y extraerá el texto de ellos. Esto le permitirá buscar texto en estos archivos adjuntos."

View File

@@ -167,6 +167,9 @@
"Add body": [
"Lisa keha"
],
"Add column": [
""
],
"Add or remove tags:": [
"Siltide lisamine või eemaldamine:"
],

View File

@@ -162,6 +162,9 @@
"Add body": [
""
],
"Add column": [
""
],
"Add or remove tags:": [
"Gehitu edo ezabatu etiketak:"
],

View File

@@ -200,6 +200,9 @@
"Add body": [
"افزودن بدنه"
],
"Add column": [
""
],
"Add new": [
"افزودن جدید"
],

File diff suppressed because it is too large Load Diff

View File

@@ -242,6 +242,9 @@
"Add body": [
"Ajoutez le contenu"
],
"Add column": [
""
],
"Add new": [
"Ajouter"
],

View File

@@ -230,6 +230,9 @@
"Add body": [
"Engadir corpo"
],
"Add column": [
""
],
"Add new": [
"Engadir novo"
],

View File

@@ -248,6 +248,9 @@
"Add body": [
"Dodaj sadržaj"
],
"Add column": [
""
],
"Add new": [
"Dodaj novu"
],

View File

@@ -153,7 +153,7 @@
"– Kamera: lehetővé teszi a képkészítést és a képek mellékelését a jegyzetekhez."
],
"- Location: to allow attaching geo-location information to a note.": [
"– Helyszín: lehetővé teszi a földrajzi helymeghatározási információk mellékelését a jegyzetekhez."
"– Hely: lehetővé teszi a földrajzi helymeghatározási információk mellékelését a jegyzetekhez."
],
"- Storage: to allow attaching files to notes and to enable filesystem synchronisation.": [
"– Tárhely: lehetővé teszi a fájlrendszer szinkronizálását és a fájlok mellékelését a jegyzetekhez."
@@ -168,7 +168,7 @@
"[Semmi]"
],
"A brief description of the image:": [
""
"A kép rövid leírása:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"Szavak vesszővel elválasztott listája. Használható a nem túl gyakori szavak esetében, hogy segítse a hangalapú gépelést a helyesírásban."
@@ -242,6 +242,9 @@
"Add body": [
"Törzsszöveg hozzáadása"
],
"Add column": [
""
],
"Add new": [
"Új hozzáadása"
],
@@ -339,7 +342,7 @@
"Automatikusan mentett rajz megtalálva. Mellékeli a másolatát a jegyzethez?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"Hiba történt a válasz elküldésekor. Ez akkor fordulhat elő, ha az alkalmazás offline állapotban van, vagy nem tud csatlakozni a kiszolgálóhoz.\nHiba: %s"
],
"An error occurred: %s": [
"Hiba történt: %s"
@@ -558,7 +561,7 @@
"A titkosított jegyzetfüzetet nem lehet megosztani a következő címzettel: %s, mert az nem engedélyezte a végpontok közötti titkosítást. Ezt megteheti a Beállítások > Titkosítás menüben."
],
"Case sensitive": [
"Kis-nagybetű érzékeny"
"Kis- és nagybetűk megkülönböztetése"
],
"Change application layout": [
"Alkalmazás elrendezésének módosítása"
@@ -680,6 +683,9 @@
"Collapse all notebooks": [
"Összes jegyzetfüzet összecsukása"
],
"Collapse title": [
"Cím összecsukása"
],
"Collapsed": [
"Összecsukva"
],
@@ -836,7 +842,7 @@
"Az alkalmazás nem hitelesíthető:\n\n%s\n\nPróbálja újra."
],
"Could not connect to Joplin Server. Please check the Synchronisation options in the config screen. Full error was:\n\n%s": [
"Nem sikerült kapcsolódni a Joplin kiszolgálóhoz. Ellenőrizze a szinkronizálási beállításokat. A teljes hiba a következő volt:\n\n%s"
"Nem sikerült kapcsolódni a Joplin Serverhez. Ellenőrizze a szinkronizálási beállításokat. A teljes hiba a következő volt:\n\n%s"
],
"Could not connect to plugin repository.": [
"Nem sikerült kapcsolódni a bővítménytárolóhoz."
@@ -1139,7 +1145,7 @@
"Elvetés"
],
"Displays a geolocation URL for the note.": [
"Megjeleníti a jegyzethez tartozó földrajzi helyszín webcímét."
"Megjeleníti a jegyzethez tartozó földrajzi hely webcímét."
],
"Displays only the first top <num> notes.": [
"Csak az első <num> leggyakrabban használt jegyzetet jeleníti meg."
@@ -1178,7 +1184,7 @@
"Ne veszítse el a jelszót, mert biztonsági okokból ez lesz az *egyetlen* módja az adatok visszafejtésének! A titkosítás engedélyezéséhez adja meg a jelszavát alább."
],
"Do you find the Joplin web app useful?": [
""
"Hasznosnak találja a Joplin webalkalmazását?"
],
"Document scanner: Title template": [
"Dokumentumbeolvasó: Címsablon"
@@ -1537,6 +1543,9 @@
"Expand all notebooks": [
"Összes jegyzetfüzet kibontása"
],
"Expand title": [
"Cím kibontása"
],
"Expanded": [
"Kibontva"
],
@@ -1601,7 +1610,7 @@
"Funkciójelölők"
],
"Feedback": [
""
"Visszajelzés"
],
"Fetched items: %d/%d.": [
"Lekérdezett elemek: %d/%d."
@@ -2013,28 +2022,31 @@
"Joplin mobilalkalmazás"
],
"Joplin Server": [
"Joplin kiszolgáló"
"Joplin Server"
],
"Joplin Server (SAML)": [
"Joplin Server (SAML)"
],
"Joplin Server Business": [
"Üzleti Joplin kiszolgáló"
"Üzleti Joplin Server"
],
"Joplin Server email": [
"Joplin kiszolgáló e-mail-címe"
"Joplin Server e-mail-címe"
],
"Joplin Server Login": [
"Bejelentkezés a Joplin kiszolgálóba"
"Bejelentkezés a Joplin Serverbe"
],
"Joplin Server password": [
"Joplin kiszolgáló jelszava"
"Joplin Server jelszava"
],
"Joplin Server URL": [
"Joplin kiszolgáló webcíme"
"Joplin Server webcíme"
],
"Joplin SSO Authentication": [
"Joplin SSO-hitelesítés"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"A Joplin támogatja annak a helynek a mentését, amelybe a jegyzetek el lettek mentve vagy létre lettek hozva. Szeretné engedélyezni? Ez a beállítás bármikor módosítható a beállításokban."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"A Joplin Web Clipper lehetővé teszi weboldalak és képernyőképek mentését a böngészőből a Joplin alkalmazásba."
@@ -2067,7 +2079,7 @@
"Frissítésre szoruló kulcsok"
],
"Label": [
""
"Címke"
],
"Landscape": [
"Fekvő"
@@ -2156,7 +2168,7 @@
"Betöltés…"
],
"Location": [
"Helyszín"
"Hely"
],
"Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at \"%s\" and resume the operation.": [
"A zárolási fájl már használatban van. Ha biztos benne, hogy nem történik szinkronizálás, törölheti a zárolási fájlt itt: „%s”, és folytathatja a műveletet."
@@ -2224,6 +2236,9 @@
"Markdown editor": [
"Markdown szerkesztő"
],
"Markdown editor: Highlight active line": [
"Markdown szerkesztő: Aktív sor kiemelése"
],
"Markdown editor: Render images": [
"Markdown szerkesztő: képek renderelése"
],
@@ -2484,7 +2499,7 @@
"Most nem"
],
"Not useful": [
""
"Nem hasznos"
],
"note": [
"jegyzet"
@@ -2534,6 +2549,9 @@
"Note list style": [
"Jegyzetlista stílusa"
],
"Note not published: %s": [
"A jegyzet nem lett közzétéve: %s"
],
"Note preview": [
"Jegyzet előnézete"
],
@@ -2694,7 +2712,7 @@
"Számozott lista"
],
"Other": [
""
"Egyéb"
],
"Other applications...": [
"További alkalmazások…"
@@ -2946,6 +2964,9 @@
"Publish Note": [
"Jegyzet közzététele"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"Közzéteszi a(z) „%s” jegyzetet (a(z) „%s” jegyzetfüzetből)?"
],
"Publish note...": [
"Jegyzet közzététele…"
],
@@ -2958,8 +2979,11 @@
"Publish/unpublish": [
"Közzététel / Közzététel megszüntetése"
],
"Published at URL: %s": [
"Közzétéve a következő webcímen: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Megjegyzés közzététele a Joplin Serveren vagy a Joplin Cloudon"
],
"QR Code": [
"QR-kód"
@@ -3006,6 +3030,9 @@
"Recipients:": [
"Címzettek:"
],
"Recognise text:": [
"Szöveg felismerése:"
],
"Recognize handwritten image": [
"Kézzel írt kép felismerése"
],
@@ -3031,7 +3058,7 @@
"Frissítés"
],
"Regular expression": [
"Reguláris kifejezés"
"Reguláris kifejezések"
],
"Reject": [
"Elutasítás"
@@ -3232,7 +3259,10 @@
"Módosítások mentése?"
],
"Save geo-location with notes": [
"Földrajzi helyszín mentése a jegyzetekhez"
"Földrajzi hely mentése a jegyzetekhez"
],
"Save geolocation?": [
"Menti a földrajzi helyet?"
],
"Scan notebook": [
"Jegyzetfüzet beolvasása"
@@ -3304,7 +3334,7 @@
"Jegyzetfüzet kiválasztása"
],
"Select one of the other supported sync targets.": [
""
"Válasszon ki egy másik támogatott szinkronizálási célt."
],
"Select parent notebook": [
"Szülő jegyzetfüzet kijelölése"
@@ -3379,7 +3409,7 @@
"Megosztások"
],
"Shares or unshares the specified [notebook] with [user]. Requires Joplin Cloud or Joplin Server.": [
"Engedélyezi vagy megszünteti a megadott [jegyzetfüzet] megosztását a [felhasználóval]. Joplin Cloud vagy Joplin kiszolgáló szükséges hozzá."
"Engedélyezi vagy megszünteti a megadott [jegyzetfüzet] megosztását a [felhasználóval]. Joplin Cloud vagy Joplin Server szükséges hozzá."
],
"Sharing notebook...": [
"Jegyzetfüzet megosztása…"
@@ -3643,7 +3673,7 @@
"Váltás a(z) [notebook] jegyzetfüzetre – minden további művelet ebben a jegyzetfüzetben történik."
],
"Sync": [
""
"Szinkronizálás"
],
"Sync as many devices as you want": [
"Szinkronizáljon annyi eszközt, amennyit csak akar"
@@ -3723,6 +3753,9 @@
"Take photo": [
"Kép készítése"
],
"Take survey": [
"Kérdőív kitöltése"
],
"Task \"%s\" failed with error: %s": [
"A(z) „%s” feladat sikertelen volt a következő hibával: %s"
],
@@ -3742,7 +3775,7 @@
"Szövegszerkesztő-parancs"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"Köszönjük a visszajelzést!\nVan ideje kitölteni egy rövid kérdőívet?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"Az aktív profil nem törölhető. Váltson másik profilra, és próbálja újra."
@@ -4142,6 +4175,9 @@
"Unable to share log data. Reason: %s": [
"A naplóadatok megosztása nem lehetséges. Indok: %s"
],
"Unable to share note data. Reason: %s": [
"Nem lehet a jegyzet adatait megosztani. Indok: %s"
],
"Unchecked": [
"Nem ellenőrzött"
],
@@ -4287,7 +4323,7 @@
"Ott használatos, ahol a szöveg olvasható elrendezéséhez fix szélességű betűtípusra van szükség (például: táblázatok, jelölőnégyzetek, kódok). Ha nem található, akkor egy általános monospace (fix szélességű) betűtípus lesz használva."
],
"Useful": [
""
"Hasznos"
],
"User deletions": [
"Felhasználói törlések"
@@ -4395,7 +4431,7 @@
"Új teendő létrehozásakor:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Ha engedélyezve van, akkor a jegyzetben lévő képek átírása egy jobb minőségű, kiszolgálóoldali átírási szolgáltatással történik. Ehhez szinkronizáció szükséges az asztali alkalmazással."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Amikor engedélyezve van, az alkalmazás beolvassa a mellékleteket, és kivonatolja belőlük a szöveget. Ez lehetővé teszi a szöveg keresését ezekben a mellékletekben."

View File

@@ -236,6 +236,9 @@
"Add body": [
"Tambahkan isi"
],
"Add column": [
""
],
"Add new": [
"Tambah baru"
],

View File

@@ -74,7 +74,7 @@ stats['nb_NO'] = {
},
};
stats['bs_BA'] = {
percentDone: 38,
percentDone: 37,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -94,7 +94,7 @@ stats['bg_BG'] = {
},
};
stats['ca'] = {
percentDone: 98,
percentDone: 100,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -168,7 +168,7 @@ stats['en_US'] = {
},
};
stats['es_ES'] = {
percentDone: 98,
percentDone: 100,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -188,7 +188,7 @@ stats['eo'] = {
},
};
stats['fi_FI'] = {
percentDone: 77,
percentDone: 89,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -238,7 +238,7 @@ stats['it_IT'] = {
},
};
stats['hu_HU'] = {
percentDone: 98,
percentDone: 99,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -258,7 +258,7 @@ stats['nl_BE'] = {
},
};
stats['nl_NL'] = {
percentDone: 67,
percentDone: 79,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -278,7 +278,7 @@ stats['fa'] = {
},
};
stats['pl_PL'] = {
percentDone: 94,
percentDone: 93,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -308,7 +308,7 @@ stats['pt_PT'] = {
},
};
stats['ro_MD'] = {
percentDone: 98,
percentDone: 99,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -318,7 +318,7 @@ stats['ro_MD'] = {
},
};
stats['ro_RO'] = {
percentDone: 98,
percentDone: 99,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -338,7 +338,7 @@ stats['sl_SI'] = {
},
};
stats['sk_SK'] = {
percentDone: 98,
percentDone: 100,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -378,7 +378,7 @@ stats['vi'] = {
},
};
stats['tr_TR'] = {
percentDone: 98,
percentDone: 99,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -408,7 +408,7 @@ stats['el_GR'] = {
},
};
stats['ru_RU'] = {
percentDone: 86,
percentDone: 98,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;
@@ -418,7 +418,7 @@ stats['ru_RU'] = {
},
};
stats['sr_RS'] = {
percentDone: 43,
percentDone: 42,
pluralForms: function(n) {
// AUTO-GENERATED by build-translations.ts
var plural;

View File

@@ -200,6 +200,9 @@
"Add body": [
"Aggiungi corpo"
],
"Add column": [
""
],
"Add new": [
"Aggiungi nuova"
],

View File

@@ -185,6 +185,9 @@
"Add body": [
"本文を追加"
],
"Add column": [
""
],
"Add new": [
"新規追加"
],

View File

@@ -188,6 +188,9 @@
"Add body": [
"내용 추가"
],
"Add column": [
""
],
"Add new": [
"새로 추가"
],

View File

@@ -182,6 +182,9 @@
"Add body": [
"Legg til brødtekst"
],
"Add column": [
""
],
"Add or remove tags:": [
"Legge til eller fjern merkelapper:"
],

View File

@@ -203,6 +203,9 @@
"Add body": [
"Inhoud toevoegen"
],
"Add column": [
""
],
"Add new": [
"Nieuw toevoegen"
],

File diff suppressed because it is too large Load Diff

View File

@@ -248,6 +248,9 @@
"Add body": [
"Dodaj treść"
],
"Add column": [
""
],
"Add new": [
"Dodaj nowy"
],

View File

@@ -230,6 +230,9 @@
"Add body": [
"Adicionar corpo"
],
"Add column": [
""
],
"Add new": [
"Adicionar"
],

View File

@@ -200,6 +200,9 @@
"Add body": [
"Adicionar corpo"
],
"Add column": [
""
],
"Add new": [
"Adicionar novo"
],

View File

@@ -174,7 +174,7 @@
"[niciuna]"
],
"A brief description of the image:": [
""
"O descriere sumară a imaginii:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"O listă de cuvinte separate de virgule. Poate fi folosită pentru cuvinte mai puțin comune, pentru a ajuta transcrierea prin dictare să le scrie corect."
@@ -248,6 +248,9 @@
"Add body": [
"Adaugă corp"
],
"Add column": [
""
],
"Add new": [
"Adaugă un nou"
],
@@ -345,7 +348,7 @@
"A fost găsit un desen salvat automat. Atașezi o copie a acestuia la notiță?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"A apărut o eroare la trimiterea răspunsului. Acest lucru se poate întîmpla dacă aplicația este offline sau nu se poate conecta la server.\nEroare: %s"
],
"An error occurred: %s": [
"S-a produs o eroare: %s"
@@ -686,6 +689,9 @@
"Collapse all notebooks": [
"Restrînge toate caietele"
],
"Collapse title": [
"Restrînge titlul"
],
"Collapsed": [
"Restrîns"
],
@@ -1186,7 +1192,7 @@
"Nu pierde parola, deoarece din motive de securitate, aceasta este *unica* modalitate de decriptare a datelor! Pentru a activa criptarea, te rog să întroduci parola mai jos."
],
"Do you find the Joplin web app useful?": [
""
"Consideri că aplicația web Joplin este utilă?"
],
"Document scanner: Title template": [
"Scanner documente: Șablon titlu"
@@ -1545,6 +1551,9 @@
"Expand all notebooks": [
"Extinde toate caietele"
],
"Expand title": [
"Extinde titlul"
],
"Expanded": [
"Extins"
],
@@ -1609,7 +1618,7 @@
"Feature Flags"
],
"Feedback": [
""
"Feedback"
],
"Fetched items: %d/%d.": [
"Elemente preluate: %d/%d."
@@ -2024,6 +2033,9 @@
"Joplin Server": [
"Server Joplin"
],
"Joplin Server (SAML)": [
"Server Joplin (SAML)"
],
"Joplin Server Business": [
"Server Joplin Business"
],
@@ -2043,7 +2055,7 @@
"Autentificare SSO Joplin"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"Joplin suportă salvarea locației în care notițele sînt salvate sau create. Dorești să o activezi? Acest lucru poate fi modificat oricînd din setări."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"Joplin Web Clipper permite salvarea paginilor web și a capturilor de ecran din navigatorul tău web în Joplin."
@@ -2076,7 +2088,7 @@
"Chei care trebuie actualizate"
],
"Label": [
""
"Etichetă"
],
"Landscape": [
"Peisaj"
@@ -2235,6 +2247,9 @@
"Markdown editor": [
"Editor Markdown"
],
"Markdown editor: Highlight active line": [
"Editor Markdown: Evidențiază linia activă"
],
"Markdown editor: Render images": [
"Editor Markdown: Redă imaginile"
],
@@ -2496,7 +2511,7 @@
"Nu acum"
],
"Not useful": [
""
"Nu este utilă"
],
"note": [
"notiță"
@@ -2546,6 +2561,9 @@
"Note list style": [
"Stilul listei de notițe"
],
"Note not published: %s": [
"Notița nu a fost publicată: %s"
],
"Note preview": [
"Previzualizare notiță"
],
@@ -2706,7 +2724,7 @@
"Listă ordonată"
],
"Other": [
""
"Alta"
],
"Other applications...": [
"Alte aplicații…"
@@ -2959,6 +2977,9 @@
"Publish Note": [
"Publică notița"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"Publici notița „%s” (în caietul „%s”)?"
],
"Publish note...": [
"Publică notița…"
],
@@ -2971,8 +2992,11 @@
"Publish/unpublish": [
"Publică/Retrage"
],
"Published at URL: %s": [
"Publicat la URL: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Publică o notiță pe un server Joplin sau în Cloud-ul Joplin"
],
"QR Code": [
"Cod QR"
@@ -3019,6 +3043,9 @@
"Recipients:": [
"Destinatari:"
],
"Recognise text:": [
"Recunoaște textul:"
],
"Recognize handwritten image": [
"Recunoaște imagine cu text scris de mînă"
],
@@ -3247,6 +3274,9 @@
"Save geo-location with notes": [
"Salvează geo-locația în notițe"
],
"Save geolocation?": [
"Salvezi geo-locația?"
],
"Scan notebook": [
"Scanează un caiet"
],
@@ -3317,7 +3347,7 @@
"Selectează caiet"
],
"Select one of the other supported sync targets.": [
""
"Selectează una din celelalte destinații de sincronizare suportate."
],
"Select parent notebook": [
"Selectează caietul părinte"
@@ -3656,7 +3686,7 @@
"Comută la [notebook] - toate operațiunile ulterioare vor avea loc în acest caiet."
],
"Sync": [
""
"Sincronizează"
],
"Sync as many devices as you want": [
"Sincronizează cît de multe dispozitive dorești"
@@ -3736,6 +3766,9 @@
"Take photo": [
"Fotografiază"
],
"Take survey": [
"Completează chestionarul"
],
"Task \"%s\" failed with error: %s": [
"Sarcina „%s” a eșuat cu eroarea: %s"
],
@@ -3755,7 +3788,7 @@
"Comandă editor de text"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"Mulțumim pentru feedback!\nAi timp să completezi un scurt chestionar?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"Profilul activ nu poate fi șters. Comută la un alt profil și încearcă din nou."
@@ -4158,7 +4191,10 @@
"Nu se poate edita resursa de tip %s"
],
"Unable to share log data. Reason: %s": [
"Imposibilitatea de a partaja datele din jurnal. Motivul: %s"
"Imposibil de a partaja datele din jurnal. Motivul: %s"
],
"Unable to share note data. Reason: %s": [
"Imposibil de a partaja datele din notiță. Motivul: %s"
],
"Unchecked": [
"Debifat"
@@ -4305,7 +4341,7 @@
"Se utilizează atunci cînd este necesar un font cu lățime fixă pentru a prezenta textul în mod lizibil (de exemplu, tabele, căsuțe de selectare, coduri). Dacă nu se găsește, se utilizează un font generic monospace (lățime fixă)."
],
"Useful": [
""
"Utilă"
],
"User deletions": [
"Ștergeri de utilizatori"
@@ -4413,7 +4449,7 @@
"Cînd este creată o nouă listă de făcut:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Cînd este activat, solicită ca imaginile din notiță să fie transcrise cu un serviciu de calitate mai bună de pe server. Necesită sincronizare cu aplicația desktop."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Atunci cînd este activată, aplicația va scana atașamentele și va extrage textul din ele. Acest lucru îți va permite să cauți text în atașamente."

View File

@@ -174,7 +174,7 @@
"[niciuna]"
],
"A brief description of the image:": [
""
"O descriere sumară a imaginii:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"O listă de cuvinte separate de virgule. Poate fi folosită pentru cuvinte mai puțin comune, pentru a ajuta transcrierea prin dictare să le scrie corect."
@@ -248,6 +248,9 @@
"Add body": [
"Adaugă corp"
],
"Add column": [
""
],
"Add new": [
"Adaugă un nou"
],
@@ -345,7 +348,7 @@
"A fost găsit un desen salvat automat. Atașezi o copie a acestuia la notiță?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"A apărut o eroare la trimiterea răspunsului. Acest lucru se poate întâmpla dacă aplicația este offline sau nu se poate conecta la server.\nEroare: %s"
],
"An error occurred: %s": [
"S-a produs o eroare: %s"
@@ -686,6 +689,9 @@
"Collapse all notebooks": [
"Restrânge toate caietele"
],
"Collapse title": [
"Restrânge titlul"
],
"Collapsed": [
"Restrâns"
],
@@ -1186,7 +1192,7 @@
"Nu pierde parola, deoarece din motive de securitate, aceasta este *unica* modalitate de decriptare a datelor! Pentru a activa criptarea, te rog să introduci parola mai jos."
],
"Do you find the Joplin web app useful?": [
""
"Consideri că aplicația web Joplin este utilă?"
],
"Document scanner: Title template": [
"Scanner documente: Șablon titlu"
@@ -1545,6 +1551,9 @@
"Expand all notebooks": [
"Extinde toate caietele"
],
"Expand title": [
"Extinde titlul"
],
"Expanded": [
"Extins"
],
@@ -1609,7 +1618,7 @@
"Feature Flags"
],
"Feedback": [
""
"Feedback"
],
"Fetched items: %d/%d.": [
"Elemente preluate: %d/%d."
@@ -2024,6 +2033,9 @@
"Joplin Server": [
"Server Joplin"
],
"Joplin Server (SAML)": [
"Server Joplin (SAML)"
],
"Joplin Server Business": [
"Server Joplin Business"
],
@@ -2043,7 +2055,7 @@
"Autentificare SSO Joplin"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"Joplin suportă salvarea locației în care notițele sunt salvate sau create. Dorești să o activezi? Acest lucru poate fi modificat oricând din setări."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"Joplin Web Clipper permite salvarea paginilor web și a capturilor de ecran din navigatorul tău web în Joplin."
@@ -2076,7 +2088,7 @@
"Chei care trebuie actualizate"
],
"Label": [
""
"Etichetă"
],
"Landscape": [
"Peisaj"
@@ -2235,6 +2247,9 @@
"Markdown editor": [
"Editor Markdown"
],
"Markdown editor: Highlight active line": [
"Editor Markdown: Evidențiază linia activă"
],
"Markdown editor: Render images": [
"Editor Markdown: Redă imaginile"
],
@@ -2496,7 +2511,7 @@
"Nu acum"
],
"Not useful": [
""
"Nu este utilă"
],
"note": [
"notiță"
@@ -2546,6 +2561,9 @@
"Note list style": [
"Stilul listei de notițe"
],
"Note not published: %s": [
"Notița nu a fost publicată: %s"
],
"Note preview": [
"Previzualizare notiță"
],
@@ -2706,7 +2724,7 @@
"Listă ordonată"
],
"Other": [
""
"Alta"
],
"Other applications...": [
"Alte aplicații…"
@@ -2959,6 +2977,9 @@
"Publish Note": [
"Publică notița"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"Publici notița „%s” (în caietul „%s”)?"
],
"Publish note...": [
"Publică notița…"
],
@@ -2971,8 +2992,11 @@
"Publish/unpublish": [
"Publică/Retrage"
],
"Published at URL: %s": [
"Publicat la URL: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Publică o notiță pe un server Joplin sau în Cloud-ul Joplin"
],
"QR Code": [
"Cod QR"
@@ -3019,6 +3043,9 @@
"Recipients:": [
"Destinatari:"
],
"Recognise text:": [
"Recunoaște textul:"
],
"Recognize handwritten image": [
"Recunoaște imagine cu text scris de mână"
],
@@ -3247,6 +3274,9 @@
"Save geo-location with notes": [
"Salvează geo-locația în notițe"
],
"Save geolocation?": [
"Salvezi geo-locația?"
],
"Scan notebook": [
"Scanează un caiet"
],
@@ -3317,7 +3347,7 @@
"Selectează caiet"
],
"Select one of the other supported sync targets.": [
""
"Selectează una din celelalte destinații de sincronizare suportate."
],
"Select parent notebook": [
"Selectează caietul părinte"
@@ -3656,7 +3686,7 @@
"Comută la [notebook] - toate operațiunile ulterioare vor avea loc în acest caiet."
],
"Sync": [
""
"Sincronizează"
],
"Sync as many devices as you want": [
"Sincronizează cât de multe dispozitive dorești"
@@ -3736,6 +3766,9 @@
"Take photo": [
"Fotografiază"
],
"Take survey": [
"Completează chestionarul"
],
"Task \"%s\" failed with error: %s": [
"Sarcina „%s” a eșuat cu eroarea: %s"
],
@@ -3755,7 +3788,7 @@
"Comandă editor de text"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"Mulțumim pentru feedback!\nAi timp să completezi un scurt chestionar?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"Profilul activ nu poate fi șters. Comută la un alt profil și încearcă din nou."
@@ -4158,7 +4191,10 @@
"Nu se poate edita resursa de tip %s"
],
"Unable to share log data. Reason: %s": [
"Imposibilitatea de a partaja datele din jurnal. Motivul: %s"
"Imposibil de a partaja datele din jurnal. Motivul: %s"
],
"Unable to share note data. Reason: %s": [
"Imposibil de a partaja datele din notiță. Motivul: %s"
],
"Unchecked": [
"Debifat"
@@ -4305,7 +4341,7 @@
"Se utilizează atunci când este necesar un font cu lățime fixă pentru a prezenta textul în mod lizibil (de exemplu, tabele, căsuțe de selectare, coduri). Dacă nu se găsește, se utilizează un font generic monospace (lățime fixă)."
],
"Useful": [
""
"Utilă"
],
"User deletions": [
"Ștergeri de utilizatori"
@@ -4413,7 +4449,7 @@
"Când este creată o nouă listă de făcut:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Când este activat, solicită ca imaginile din notiță să fie transcrise cu un serviciu de calitate mai bună de pe server. Necesită sincronizare cu aplicația desktop."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Atunci când este activată, aplicația va scana atașamentele și va extrage textul din ele. Acest lucru îți va permite să cauți text în atașamente."

File diff suppressed because it is too large Load Diff

View File

@@ -174,7 +174,7 @@
"[Žiadny]"
],
"A brief description of the image:": [
""
"Stručný popis obrázku:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"Zoznam slov oddelených čiarkami. Môže sa použiť pre nezvyčajné slová, aby sa pomohlo pri ich správnom hlasovom zadávaní."
@@ -248,6 +248,9 @@
"Add body": [
"Pridať telo"
],
"Add column": [
"Pridať stĺpec"
],
"Add new": [
"Pridať novú"
],
@@ -257,6 +260,9 @@
"Add recipient:": [
"Pridať príjemcu:"
],
"Add row": [
"Pridať riadok"
],
"Add tags:": [
"Pridať štítky:"
],
@@ -345,7 +351,7 @@
"Bol nájdený automaticky uložený náčrt. Pripojiť jeho kópiu k poznámke?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"Pri odosielaní odpovede došlo k chybe. K tejto chybe môže dôjsť, ak je aplikácia offline alebo sa nemôže pripojiť k serveru.\nChyba: %s"
],
"An error occurred: %s": [
"Vyskytla sa chyba: %s"
@@ -686,6 +692,9 @@
"Collapse all notebooks": [
"Zbaliť všetky zápisníky"
],
"Collapse title": [
"Zbaliť nadpis"
],
"Collapsed": [
"Zbalené"
],
@@ -1026,6 +1035,9 @@
"Delete attachment \"%s\"?": [
"Vymazať prílohu \"%s\"?"
],
"Delete column": [
"Vymazať stĺpec"
],
"Delete expired sessions": [
"Vymazať relácie s uplynutou platnosťou"
],
@@ -1053,6 +1065,9 @@
"Delete profile \"%s\"": [
"Vymazať profil \"%s\""
],
"Delete row": [
"Vymazať riadok"
],
"Delete selected notes": [
"Vymazať vybrané poznámky"
],
@@ -1186,7 +1201,7 @@
"Nestraťte heslo, pretože z bezpečnostných dôvodov je to *jediný* spôsob, ako dešifrovať údaje! Ak chcete zapnúť šifrovanie, zadajte heslo nižšie."
],
"Do you find the Joplin web app useful?": [
""
"Považujete webovú aplikáciu Joplin za užitočnú?"
],
"Document scanner: Title template": [
"Skener dokumentov: Šablóna názvu"
@@ -1545,6 +1560,9 @@
"Expand all notebooks": [
"Rozbaliť všetky zápisníky"
],
"Expand title": [
"Rozbaliť nadpis"
],
"Expanded": [
"Rozbalené"
],
@@ -1609,7 +1627,7 @@
"Príznaky funkcií"
],
"Feedback": [
""
"Spätná väzba"
],
"Fetched items: %d/%d.": [
"Načítané položky: %d/%d."
@@ -2024,6 +2042,9 @@
"Joplin Server": [
"Joplin Server"
],
"Joplin Server (SAML)": [
"Joplin Server (SAML)"
],
"Joplin Server Business": [
"Joplin Server Business"
],
@@ -2043,7 +2064,7 @@
"Joplin SSO overenie"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"Joplin podporuje ukladanie polohy, pri ktorej sa poznámky ukladajú alebo vytvárajú. Chcete túto funkciu povoliť? Túto voľbu môžete kedykoľvek zmeniť v nastaveniach."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"Joplin Web Clipper umožňuje ukladanie webových stránok a snímok obrazovky z vášho prehliadača do aplikácie Joplin."
@@ -2076,7 +2097,7 @@
"Kľúče, ktoré si vyžadujú aktualizáciu"
],
"Label": [
""
"Označenie"
],
"Landscape": [
"Na šírku"
@@ -2235,6 +2256,9 @@
"Markdown editor": [
"Editor Markdown"
],
"Markdown editor: Highlight active line": [
"Editor Markdown: Zvýrazniť aktívny riadok"
],
"Markdown editor: Render images": [
"Editor Markdown: Vykresliť obrázky"
],
@@ -2496,7 +2520,7 @@
"Teraz nie"
],
"Not useful": [
""
"Nie je užitočné"
],
"note": [
"pozn"
@@ -2546,6 +2570,9 @@
"Note list style": [
"Štýl zoznamu poznámok"
],
"Note not published: %s": [
"Poznámka neuverejnená: %s"
],
"Note preview": [
"Náhľad poznámky"
],
@@ -2706,7 +2733,7 @@
"Usporiadaný zoznam"
],
"Other": [
""
"Iné"
],
"Other applications...": [
"Iné aplikácie..."
@@ -2959,6 +2986,9 @@
"Publish Note": [
"Zverejniť poznámku"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"Zverejniť poznámku „%s“ (v zápisníku „%s“)?"
],
"Publish note...": [
"Zverejniť poznámku..."
],
@@ -2971,8 +3001,11 @@
"Publish/unpublish": [
"Zverejniť/Zrušiť zverejnenie"
],
"Published at URL: %s": [
"Uverejnené na URL adrese: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Uverejní poznámku na serveri Joplin alebo v cloude Joplin"
],
"QR Code": [
"QR kód"
@@ -3019,6 +3052,9 @@
"Recipients:": [
"Príjemcovia:"
],
"Recognise text:": [
"Rozpoznať text:"
],
"Recognize handwritten image": [
"Rozpoznať obrázok rukopisu"
],
@@ -3247,6 +3283,9 @@
"Save geo-location with notes": [
"Uložiť geografickú polohu s poznámkami"
],
"Save geolocation?": [
"Uložiť geografickú polohu?"
],
"Scan notebook": [
"Skenovať zápisník"
],
@@ -3317,7 +3356,7 @@
"Vybrať zápisník"
],
"Select one of the other supported sync targets.": [
""
"Vyberte jeden z ďalších podporovaných cieľov synchronizácie."
],
"Select parent notebook": [
"Vybrať nadradený zápisník"
@@ -3656,7 +3695,7 @@
"Prepne na [zápisník] – všetky ďalšie operácie budú prebiehať v tomto zápisníku."
],
"Sync": [
""
"Synchronizovať"
],
"Sync as many devices as you want": [
"Synchronizujte toľko zariadení, koľko len chcete"
@@ -3736,6 +3775,9 @@
"Take photo": [
"Odfotiť"
],
"Take survey": [
"Zúčastniť sa prieskumu"
],
"Task \"%s\" failed with error: %s": [
"Úloha \"%s\" zlyhala s chybou: %s"
],
@@ -3755,7 +3797,7 @@
"Príkaz textového editora"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"Ďakujeme za spätnú väzbu!\nMáte čas vyplniť krátky dotazník?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"Aktívny profil nie je možné odstrániť. Prepnite na iný profil a skúste to znova."
@@ -4160,6 +4202,9 @@
"Unable to share log data. Reason: %s": [
"Nie je možné zdieľať údaje záznamu. Dôvod: %s"
],
"Unable to share note data. Reason: %s": [
"Nie je možné zdieľať údaje poznámky. Dôvod: %s"
],
"Unchecked": [
"Neoznačené"
],
@@ -4305,7 +4350,7 @@
"Používa sa v prípadoch, keď je na čitateľné rozvrhnutie textu potrebná pevná šírka písma (napr. tabuľky, označovacie políčka, kód). Ak sa nenájde, použije sa všeobecné monospace písmo (s pevnou šírkou)."
],
"Useful": [
""
"Užitočné"
],
"User deletions": [
"Vymazania používateľa"
@@ -4413,7 +4458,7 @@
"Pri vytváraní novej úlohy:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Ak je táto funkcia povolená, požiada o prepis obrázkov v poznámke pomocou kvalitnejšej služby prepisovania na serveri. Vyžaduje synchronizáciu s kópiou aplikácie pre počítače."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Ak je táto funkcia zapnutá, aplikácia bude skenovať prílohy a extrahovať z nich text. To vám umožní vyhľadávať text v týchto prílohách."

View File

@@ -182,6 +182,9 @@
"Add body": [
"Dodaj telo"
],
"Add column": [
""
],
"Add or remove tags:": [
"Dodaj ali odstrani oznako:"
],

View File

@@ -176,6 +176,9 @@
"Add body": [
"Додај тело"
],
"Add column": [
""
],
"Add or remove tags:": [
"Додај или уклони ознаке:"
],

View File

@@ -242,6 +242,9 @@
"Add body": [
"Lägg till brödtext"
],
"Add column": [
""
],
"Add new": [
"Lägg till ny"
],

View File

@@ -170,6 +170,9 @@
"Add body": [
"เพิ่มเนื้อความ"
],
"Add column": [
""
],
"Add or remove tags:": [
"เพิ่มหรือลบแท็ก:"
],

View File

@@ -83,7 +83,7 @@
"%s pek çok dosyanın senkronu için optimize bir değil, bu sebeple ilk senkronizasyon zaman alabilir."
],
"%s must be a valid whole number": [
"%s geçerli bir telefon numarası olmalıdır"
"%s geçerli bir tamsayı olmalıdır"
],
"%s tab opened": [
"%s sekme açık"
@@ -150,7 +150,7 @@
"(Ayarlardan bu istemi kapatabilirsiniz)"
],
"- Camera: to allow taking a picture and attaching it to a note.": [
"-Kamera: fotoğraf çekimi ve fotoğrafın nota eklenebilmesi için."
"- Kamera: fotoğraf çekimi ve fotoğrafın nota eklenebilmesi için."
],
"- Location: to allow attaching geo-location information to a note.": [
"- Konum: coğrafi konum bilgilerinin bir nota eklenmesine izin vermek için."
@@ -168,7 +168,7 @@
"[None]"
],
"A brief description of the image:": [
""
"Resmin kısa bir açıklaması:"
],
"A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.": [
"Virgülle ayrılmış bir kelime listesi. Sesli yazımın nadir kelimeleri doğru şekilde hecelemesine yardımcı olmak için kullanılabilir."
@@ -242,6 +242,9 @@
"Add body": [
"Gövde Ekle"
],
"Add column": [
""
],
"Add new": [
"Yeni bir şey ekle"
],
@@ -339,7 +342,7 @@
"Otomatik kaydedilmiş bir çizim bulundu. Not'a bunun kopyasını iliştirmek ister misiniz?"
],
"An error occurred while sending the response. This can happen if the app is offline or cannot connect to the server.\nError: %s": [
""
"Cevap gönderilirken bir hata oluştu. Bu durum uygulama çevrimdışıyken, veya sunucuya bağlanılamadığı zamanlarda olabilir\nHata: %s"
],
"An error occurred: %s": [
"Bir hata oluştu: %s"
@@ -381,7 +384,7 @@
"Aritim Dark"
],
"Associated tags:": [
"Ekli etiketler"
"Tanımlı etiketler:"
],
"At present, Joplin Web can only be open in one tab at a time. Please close the other instance of Joplin.": [
"Joplin Web şimdilik sadece aynı anda bir tane sekmede açık durabilir. Lütfen Joplin'in açık diğer sekmelerini kapatın."
@@ -680,6 +683,9 @@
"Collapse all notebooks": [
"Tüm not defterlerini küçült"
],
"Collapse title": [
"Başlığı daralt"
],
"Collapsed": [
"Daraltılmış"
],
@@ -1178,7 +1184,7 @@
"Şifrenizi kaybetmeyin, verilerinizin şifresini çözmenin \"*tek yolu* bu şifreyi girmek olacaktır. Şifrelemeyi etkinleştirmek için lütfen parolanızı aşağıya girin."
],
"Do you find the Joplin web app useful?": [
""
"Joplin web uygulamasını faydalı buldunuz mu?"
],
"Document scanner: Title template": [
"Belge tarayıcı: Başlık şablonu"
@@ -1241,7 +1247,7 @@
"Dropbox Girişi"
],
"Due": [
"Şu tarihe kadar: "
"Şu tarihe kadar:"
],
"due date": [
"şu tarihe kadar"
@@ -1537,6 +1543,9 @@
"Expand all notebooks": [
"Tüm not defterini genişlet"
],
"Expand title": [
"Başlığı genişlet"
],
"Expanded": [
"Genişletilmiş"
],
@@ -1601,7 +1610,7 @@
"Özellik bilgileri"
],
"Feedback": [
""
"Geribildirim"
],
"Fetched items: %d/%d.": [
"Alınan öğeler: %d/%d."
@@ -2015,6 +2024,9 @@
"Joplin Server": [
"Joplin Sunucusu"
],
"Joplin Server (SAML)": [
"Joplin Server (SAML)"
],
"Joplin Server Business": [
"Joplin Sunucusu Business"
],
@@ -2034,7 +2046,7 @@
"Joplin SSO Kimlik Doğrulaması"
],
"Joplin supports saving the location at which notes are saved or created. Do you want to enable it? This can be changed at any time in settings.": [
""
"Joplin notların oluşturulduğu veya güncellemelerin kaydedildiği konumları saklayabilme özelliğine sahip. Bu özelliği aktifleştirmek ister misiniz? Bu özelliği istediğiniz zaman ayarlardan açıp kapatabilirsiniz."
],
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": [
"Joplin Web Alıntılama, tarayıcınızdaki web sayfalarını ve ekran görüntülerini Joplin'e kaydetmenizi sağlar."
@@ -2067,7 +2079,7 @@
"Güncellenmesi gereken anahtarlar"
],
"Label": [
""
"Etiket"
],
"Landscape": [
"Yatay"
@@ -2224,6 +2236,9 @@
"Markdown editor": [
"Markdown düzenleyici"
],
"Markdown editor: Highlight active line": [
"Markdown düzenleyici: Mevcut satırı rengini değiştirerek vurgula"
],
"Markdown editor: Render images": [
"Markdown düzenleyici: Resimleri görüntüle"
],
@@ -2373,7 +2388,7 @@
"Yeni not defteri \"%s\" oluşturulacak ve \"%s\" dosyası onun içine aktarılacak"
],
"New notebook title": [
"Yeni not defteri başlığı:"
"Yeni not defteri başlığı"
],
"New photo": [
"Yeni fotoğraf"
@@ -2484,7 +2499,7 @@
"Şimdi değil"
],
"Not useful": [
""
"Kullanışsız"
],
"note": [
"not"
@@ -2534,6 +2549,9 @@
"Note list style": [
"Not listeleme biçimi"
],
"Note not published: %s": [
"Not paylaşılamadı: %s"
],
"Note preview": [
"Not önizleme"
],
@@ -2694,7 +2712,7 @@
"Dizilmiş liste"
],
"Other": [
""
"Diğer"
],
"Other applications...": [
"Diğer uygulamalar..."
@@ -2946,6 +2964,9 @@
"Publish Note": [
"Notu Yayımla"
],
"Publish note \"%s\" (in notebook \"%s\")?": [
"“%s” notu (“%s” not defterinde) yayımlansın mı?"
],
"Publish note...": [
"Notu yayımla…"
],
@@ -2958,8 +2979,11 @@
"Publish/unpublish": [
"Yayımla/yayından kaldır"
],
"Published at URL: %s": [
"Şu URL’de yayımlandı: %s"
],
"Publishes a note to Joplin Server or Joplin Cloud": [
""
"Notu Joplin Server veya Joplin Cloud’a yayımlar"
],
"QR Code": [
"Kare Kod"
@@ -3006,6 +3030,9 @@
"Recipients:": [
"Alıcılar:"
],
"Recognise text:": [
"Metni algıla:"
],
"Recognize handwritten image": [
"El yazısı olan resimleri algıla"
],
@@ -3234,6 +3261,9 @@
"Save geo-location with notes": [
"Coğrafi konumu notlarla kaydedin"
],
"Save geolocation?": [
"Coğrafi konumu kaydedilsin mi?"
],
"Scan notebook": [
"Not defterini tara"
],
@@ -3304,7 +3334,7 @@
"Not defterini seç"
],
"Select one of the other supported sync targets.": [
""
"Destekleyen diğer senkronizasyon hedeflerinden birini seçin."
],
"Select parent notebook": [
"Ana not defterini seç"
@@ -3643,7 +3673,7 @@
"[notebook] not defterine geçer - daha sonraki tüm işlemler bu not defterinde gerçekleşecektir."
],
"Sync": [
""
"Senkronizasyon"
],
"Sync as many devices as you want": [
"İstediğin kadar cihazı senkronize et"
@@ -3723,6 +3753,9 @@
"Take photo": [
"Fotoğraf çek"
],
"Take survey": [
"Ankete katıl"
],
"Task \"%s\" failed with error: %s": [
"\"%s\" görevi başarısız. Hata mesajı: %s"
],
@@ -3742,7 +3775,7 @@
"Metin editörü komutu"
],
"Thank you for the feedback!\nDo you have time to complete a short survey?": [
""
"Geribildirim için teşekkür ederiz!\nKısa bir anketimiz var, bunu doldurmaya vaktiniz var mı?"
],
"The active profile cannot be deleted. Switch to a different profile and try again.": [
"Profil aktifken silinemez. Bir başka profile geçip yeniden deneyin."
@@ -3818,7 +3851,7 @@
"“%s” notu başarılı bir şekilde “%s” not defterine geri yüklendi."
],
"The note has been converted to Markdown and the original note has been moved to the trash": [
"Not Markdown'a dönüştürüldü ve orijinal not çöp kutusuna taşındı."
"Not Markdown'a dönüştürüldü ve orijinal not çöp kutusuna taşındı"
],
"The note was successfully moved to the trash.": [
"Not başarıyla çöp kutusuna taşındı.",
@@ -4142,6 +4175,9 @@
"Unable to share log data. Reason: %s": [
"Loglar dışarı aktarılamadı. Sebep: %s"
],
"Unable to share note data. Reason: %s": [
"Not verisi paylaşılamadı. Sebep: %s"
],
"Unchecked": [
"İşaretlenmemiş"
],
@@ -4287,7 +4323,7 @@
"Sabit bir metin uzunluğu gerektiren yerlerde (örn: tablolar, doğrulama kutucukları, kod vs.) kullanılır. Eğer bulunamazsa jenerik ve monospace olan bir font kullanılacak."
],
"Useful": [
""
"Kullanuşlı"
],
"User deletions": [
"Kullancı silmeleri"
@@ -4395,7 +4431,7 @@
"Yeni bir yapılacak oluşturulurken:"
],
"When enabled, requests that the images in the note be transcribed with a higher-quality on-server transcription service. Requires sync with a copy of the desktop app.": [
""
"Etkinleştirildiğinde, nottaki resimlerin sunucu tabanlı daha yüksek kaliteli transkripsiyon servisi ile çözümlenmesini ister. Masaüstü uygulamasının bir kopyasıyla senkronizasyon gerektirir."
],
"When enabled, the application will scan your attachments and extract the text from it. This will allow you to search for text in these attachments.": [
"Eğer bu özellik aktifleştirilirse, uygulama iliştirilen ek dosyaları tarayarak içindeki metinleri ayıklayacak. Bu sayede bu metinler üzerinden de arama yapabileceksiniz."

View File

@@ -203,6 +203,9 @@
"Add body": [
"Додати тіло"
],
"Add column": [
""
],
"Add new": [
"Додати нове"
],

View File

@@ -182,6 +182,9 @@
"Add body": [
"Điền phần thân"
],
"Add column": [
""
],
"Add or remove tags:": [
"Gắn hoặc gỡ nhãn:"
],

View File

@@ -236,6 +236,9 @@
"Add body": [
"添加内容"
],
"Add column": [
""
],
"Add new": [
"新增"
],

View File

@@ -200,6 +200,9 @@
"Add body": [
"新增內文"
],
"Add column": [
""
],
"Add new": [
"新增"
],

View File

@@ -1,3 +1,4 @@
import { Second } from '@joplin/utils/time';
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, syncTargetId, synchronizerStart, msleep } from '../testing/test-utils';
import BaseItem from './BaseItem';
import Folder from './Folder';
@@ -42,6 +43,19 @@ describe('BaseItem', () => {
expect(unserialized2.title).toBe(folder2.title);
});
it.each([
'',
'\n\na\nb\nc\nç\nTest!\n Testing. \n',
'Test! ☺',
'Test! ☺\n\n\n',
])('should not modify body when unserializing (body: %j)', async (body) => {
const note = await Note.save({ title: 'note1', body });
expect(await Note.unserialize(await Note.serialize(note))).toMatchObject({
body,
});
});
it('should correctly unserialize note timestamps', async () => {
const folder = await Folder.save({ title: 'folder' });
const note = await Note.save({ title: 'note', parent_id: folder.id });
@@ -55,6 +69,22 @@ describe('BaseItem', () => {
expect(unserialized.user_updated_time).toEqual(note.user_updated_time);
});
it('should unserialize a very large note quickly', async () => {
const folder = await Folder.save({ title: 'folder' });
const note = await Note.save({ title: 'note', parent_id: folder.id });
const serialized = await Note.serialize({
...note,
// 2 MiB
body: '\n.'.repeat(1 * 1024 * 1024),
});
const start = performance.now();
await Note.unserialize(serialized);
// Locally, this passes in in < 2s, so 30s should be a safe upper bound.
expect(performance.now() - start).toBeLessThan(30 * Second);
});
it('should serialize geolocation fields', async () => {
const folder = await Folder.save({ title: 'folder' });
let note = await Note.save({ title: 'note', parent_id: folder.id });

View File

@@ -578,28 +578,24 @@ export default class BaseItem extends BaseModel {
const lines = content.split('\n');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
let output: any = {};
let state = 'readingProps';
const body: string[] = [];
let body: string[] = [];
for (let i = lines.length - 1; i >= 0; i--) {
let line = lines[i];
if (state === 'readingProps') {
line = line.trim();
line = line.trim();
if (line === '') {
state = 'readingBody';
continue;
}
const p = line.indexOf(':');
if (p < 0) throw new Error(`Invalid property format: ${line}: ${content}`);
const key = line.substr(0, p).trim();
const value = line.substr(p + 1).trim();
output[key] = value;
} else if (state === 'readingBody') {
body.splice(0, 0, line);
// Props are separated from the body by a single blank line
if (line === '') {
body = lines.slice(0, i);
break;
}
const p = line.indexOf(':');
if (p < 0) throw new Error(`Invalid property format: ${line}: ${content}`);
const key = line.substr(0, p).trim();
const value = line.substr(p + 1).trim();
output[key] = value;
}
if (!output.type_) throw new Error(`Missing required property: type_: ${content}`);
@@ -1007,7 +1003,7 @@ export default class BaseItem extends BaseModel {
const isNew = this.isNew(o, options);
if (needsShareReadOnlyChecks(this.modelType(), options.changeSource, this.syncShareCache)) {
if (needsShareReadOnlyChecks(this.modelType(), options.changeSource, this.syncShareCache, options.disableReadOnlyCheck)) {
if (!isNew) {
const previousItem = await this.loadItemByTypeAndId(this.modelType(), o.id, { fields: ['id', 'share_id'] });
checkIfItemCanBeChanged(this.modelType(), options.changeSource, previousItem, this.syncShareCache);

View File

@@ -1,4 +1,4 @@
import { setupDatabaseAndSynchronizer, switchClient, createFolderTree, supportDir, msleep, resourceService } from '../testing/test-utils';
import { setupDatabaseAndSynchronizer, switchClient, createFolderTree, supportDir, msleep, resourceService, simulateReadOnlyShareEnv } from '../testing/test-utils';
import Folder from '../models/Folder';
import { allNotesFolders } from '../testing/test-utils-synchronizer';
import Note from '../models/Note';
@@ -181,6 +181,21 @@ describe('models/Folder.sharing', () => {
}
}));
it('should not fail to update share IDs when an outdated share ID is contained in a read-only folder', async () => {
const shareId = 'abcd1234';
const root = await Folder.save({ title: 'read-only', share_id: shareId });
// Save a child with a different share ID
const child = await Note.save({ title: 'Test', parent_id: root.id, share_id: `${shareId}-different` });
const reset = simulateReadOnlyShareEnv([shareId]);
try {
await Folder.updateAllShareIds(resourceService(), []);
expect(await Note.load(child.id)).toMatchObject({ share_id: shareId });
} finally {
reset();
}
});
it('should unshare a subfolder of a shared folder when it is moved to the root', (async () => {
let folder1 = await createFolderTree('', [
{

View File

@@ -556,7 +556,10 @@ export default class Folder extends BaseItem {
share_id: row.share_id || '',
parent_id: row.parent_id,
updated_time: Date.now(),
}, { autoTimestamp: false });
}, {
autoTimestamp: false,
disableReadOnlyCheck: true,
});
}
}

View File

@@ -65,7 +65,7 @@
"fast-deep-equal": "3.1.3",
"fast-xml-parser": "3.21.1",
"file-type": "16.5.4",
"follow-redirects": "1.15.6",
"follow-redirects": "1.15.11",
"form-data": "4.0.4",
"fs-extra": "11.2.0",
"hpagent": "1.2.0",

View File

@@ -125,7 +125,7 @@ describe('services_KeymapService', () => {
expect(keymapService.getAccelerator('textBold')).toEqual('Ctrl+B');
});
if ('should throw when an invalid command is requested', () => {
it('should throw when an invalid command is requested', () => {
expect(() => keymapService.getAccelerator('totallyNonExistentCommand')).toThrow();
});
});

View File

@@ -39,6 +39,7 @@ describe('services/RevisionService', () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
Setting.setValue('revisionService.intervalBetweenRevisions', 0);
Setting.setValue('revisionService.oldNoteInterval', 1000 * 60 * 60 * 24 * 7);
jest.useFakeTimers({ advanceTimers: true });
});
@@ -619,6 +620,7 @@ describe('services/RevisionService', () => {
jest.advanceTimersByTime(100);
await Note.save({ id: note.id, title: 'test', body: 'StartA' });
await ItemChange.waitForAllSaved();
await Note.save({ id: note.id, title: 'test', body: 'StartAB' });
await Note.save({ id: note.id, title: 'test', body: 'StartABC' }); // REV 2
await revisionService().collectRevisions(); // Create revisions for old and new content
@@ -726,8 +728,9 @@ describe('services/RevisionService', () => {
Setting.setValue('revisionService.oldNoteInterval', 50);
await Note.save({ id: note.id, title: 'test', body: 'ABCDEF' });
await ItemChange.waitForAllSaved();
await Note.save({ id: note.id, title: 'test', body: 'ABCDEFG' }); // REV 2
await revisionService().collectRevisions();
await revisionService().collectRevisions(); // Create revisions for old and new content
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, note.id);
expect(revisions.length).toBe(3);
@@ -763,8 +766,9 @@ describe('services/RevisionService', () => {
Setting.setValue('revisionService.oldNoteInterval', 50);
await Note.save({ id: note.id, title: 'test', body: 'ABCDEF' });
await ItemChange.waitForAllSaved();
await Note.save({ id: note.id, title: 'test', body: 'ABCDE' }); // REV 1
await revisionService().collectRevisions(); // Content is the same, create just one revision instead of two
await revisionService().collectRevisions(); // Content is the same for old and new content, so create just one revision instead of two
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, note.id);
expect(revisions.length).toBe(2);

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