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

Compare commits

...

148 Commits

Author SHA1 Message Date
Laurent Cozic
b519d55abf Merge branch 'dev' into table_editor 2022-06-14 00:43:23 +01:00
Laurent Cozic
c7697b65ca Add resourceService for debugging 2022-06-13 18:08:20 +01:00
Laurent Cozic
3bb00956fe Desktop: Fixes #6506: App can crash with certain combinations of plugins 2022-06-13 18:08:20 +01:00
Arda Kılıçdağı
e3695c6a80 Turkish translations updated (#6573) 2022-06-10 09:45:37 +01:00
Kenichi Kobayashi
40bc63e7ea Suppress redundant NoteEditor re-rendering by removing non-changing updates of state.selectedNoteTags (#6470) 2022-06-08 10:34:08 +01:00
Kenichi Kobayashi
c320d2364e Performance: suppresses redundant SideBar re-rendering on state.tags (#6451) 2022-06-08 10:33:53 +01:00
Kenichi Kobayashi
fb9e78d6c1 Desktop: Fixes #5178: Allow styling note list items using custom CSS (#6542) 2022-06-08 10:33:06 +01:00
Laurent Cozic
5a862443d8 Merge branch 'dev' into table_editor 2022-06-07 18:30:40 +01:00
alexmo1997
27ef917350 Desktop: Update to Electron 18 (#6496) 2022-06-07 18:23:16 +01:00
Kenichi Kobayashi
2dedede5c3 Removes unnecessary trivial dependencies that causes re-rendering (#6471) 2022-06-07 18:21:55 +01:00
Kenichi Kobayashi
443e049022 Performance: fixes false dependencies in MainScreen (#6444) 2022-06-07 18:09:50 +01:00
Xavi Ivars
b4c3ba249d Update ca.po (#6563) 2022-06-07 17:32:43 +01:00
Perkolator
46f146309d Doc: Added links to mobile changelogs in the about section. (#6562) 2022-06-07 17:32:23 +01:00
ScriptInfra
541203f919 Update README.md (#6560) 2022-06-07 17:31:17 +01:00
ScriptInfra
8495045ada Update README.md (#6559) 2022-06-07 17:30:53 +01:00
ScriptInfra
64c3784a8b Update README.md (#6558)
Added a space between "word `office`".
2022-06-07 17:30:35 +01:00
Joplin Bot
ff897236f7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2022-06-06 18:16:48 +00:00
Laurent Cozic
53206d5488 Clipper release v2.8.1 2022-06-06 16:13:54 +01:00
Laurent Cozic
1eeefb942d Fix clipper build 2022-06-06 16:13:03 +01:00
Laurent Cozic
87296a616a Fix clipper build 2022-06-06 16:10:21 +01:00
Laurent Cozic
6c0f3c3578 Tools: Better joplinbot message 2022-06-06 16:07:47 +01:00
Laurent Cozic
c1fb9fb3f5 iOS 12.8.1 2022-06-06 16:06:09 +01:00
Joplin Bot
466a534fcd Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-06-06 12:23:06 +00:00
Laurent Cozic
9375fe1323 Doc: Release 2.8 announcement 2022-06-06 11:54:11 +01:00
Laurent Cozic
19b8191712 Doc: Fix RSS dates 2022-06-03 14:03:29 +01:00
Joplin Bot
7111d31813 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-06-03 12:21:36 +00:00
Joplin Bot
d97064979f Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-06-03 11:42:42 +00:00
Laurent Cozic
232e0a3a72 Doc: Auto-generate RSS feed from news 2022-06-03 12:32:24 +01:00
Joscha Schmiedt
40d9ccf183 Doc: Fix broken link to Plugin API (#6545) 2022-05-31 11:22:04 +01:00
Laurent Cozic
f75a9d4a64 Chore: Fixed tests 2022-05-27 12:02:31 +01:00
Laurent Cozic
7168e0dc90 Chore: Optimize highlight.js package size 2022-05-26 16:46:56 +01:00
Laurent Cozic
91df23e959 Chore: Removed lodash package to save space 2022-05-26 15:57:44 +01:00
Laurent Cozic
63b3115d3b word list 2022-05-24 18:09:45 +01:00
Laurent Cozic
8f0763ed45 Doc: Add GTM tags 2022-05-23 11:34:55 +01:00
Joplin Bot
94ac82d7d9 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-22 13:08:41 +00:00
Laurent Cozic
7c2bb02f9e Doc: Add news item 2022-05-22 13:58:07 +01:00
Joplin Bot
832d1a560a Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-19 12:21:15 +00:00
Joplin Bot
020fd4ad9a Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-19 06:17:25 +00:00
Laurent Cozic
e4b150a4cd Android 2.8.1 2022-05-18 14:41:57 +01:00
Laurent Cozic
9ac3b353e7 lock file 2022-05-18 14:31:37 +01:00
Laurent Cozic
84d7f0fd60 Doc: Add yearly-monthly plan A-B testing 2022-05-18 14:16:54 +01:00
Laurent Cozic
d4c209e638 CLI v2.8.1 2022-05-18 14:03:43 +01:00
Laurent Cozic
2cbe7be16e Releasing sub-packages 2022-05-18 13:59:47 +01:00
Joplin Bot
d2bb2e9df4 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-17 18:18:25 +00:00
Laurent Cozic
c2a6a13eb4 Desktop release v2.8.8 2022-05-17 14:13:27 +01:00
Laurent Cozic
6ba5a896e8 Update translations 2022-05-17 14:12:42 +01:00
Daeraxa
6937066c60 Docs: Update plugin readmes (#6502) 2022-05-17 14:10:58 +01:00
Retrove
64ef5ebde4 Desktop: Fixes #6503: Dropbox login button is not visible in dark mode (#6513) 2022-05-17 14:10:31 +01:00
Laurent Cozic
7ec3a7be9f Desktop: Remove plugin backoff handler for now 2022-05-17 14:08:54 +01:00
Joplin Bot
81ea66f4ad Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-17 12:23:02 +00:00
Laurent Cozic
075c4053a8 Doc: Added sponsor 2022-05-17 10:09:22 +01:00
Máté Molnár
17256873dc Updated translations (#6516) 2022-05-14 22:49:33 +01:00
Joplin Bot
6f1a806e5c Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-12 00:40:30 +00:00
Joplin Bot
41e95e81de Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-11 18:18:05 +00:00
Joplin Bot
f406d112bd Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-11 12:23:05 +00:00
Laurent Cozic
2151a626cd Doc: Disable linkify to prevent text from being incorrectly converted to a link 2022-05-10 16:01:30 +01:00
kik0220
0fc65acfa2 Translation: Update ja_JP.po (#6504) 2022-05-10 14:41:30 +01:00
Tolulope Malomo
2eddd40667 Mobile: Fixes #6328: Error when pressing undo or redo button while editor is closed (#6426) 2022-05-10 10:23:36 +01:00
Mr-Kanister
a8723ea512 Update de_DE.po (#6501) 2022-05-10 09:59:57 +01:00
Laurent Cozic
a6c3fbb0ed Revert "Merge branch 'dev' into dev"
This reverts commit 5e0c171099, reversing
changes made to b8aa7d0735.

Was accidentally pushed to dev.
2022-05-09 12:41:55 +01:00
Daeraxa
5e0c171099 Merge branch 'dev' into dev 2022-05-09 11:25:35 +01:00
Joplin Bot
b8aa7d0735 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-06 12:20:41 +00:00
Laurent Cozic
91d786ddbe Desktop release v2.8.7 2022-05-06 10:39:57 +01:00
Laurent Cozic
1fc0fb39d3 Merge branch 'dev' into release-2.8 2022-05-06 10:39:37 +01:00
Laurent Cozic
afdc7ef141 Update translations 2022-05-06 10:37:39 +01:00
Milo Ivir
adf317e16c Update Croatian translation (#6489) 2022-05-05 22:37:31 +01:00
Laurent Cozic
191c49c0cd Desktop: Fixes #6459: Sync config was lost when switching profiles 2022-05-05 18:15:10 +01:00
Laurent Cozic
27118c2d00 Update translations 2022-05-05 17:06:00 +01:00
Laurent Cozic
766d7ebb65 Tools: Add spellchecker to pre-commit hook 2022-05-05 16:36:28 +01:00
Laurent Cozic
d99312e06c Desktop: Fixes #6435: Make undo/redo menu items translatable 2022-05-05 16:36:28 +01:00
Joplin Bot
b28f069f70 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-05 12:21:22 +00:00
Laurent Cozic
876a67d9b0 Chore: Spell checker 2022-05-05 13:19:57 +01:00
Laurent Cozic
814a5a0cd8 Desktop, Mobile: Sort sync target options 2022-05-05 13:19:23 +01:00
Laurent Cozic
5adee9085c Doc: Added E2EE flowcharts 2022-05-04 17:24:50 +01:00
Joplin Bot
c86e7f5eed Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-05-03 12:21:04 +00:00
Laurent Cozic
c7444e563b Chore: Removed "canvas" package to fix build using node 18 2022-05-03 11:19:14 +01:00
Laurent Cozic
2486c787a4 Desktop release v2.8.6 2022-05-02 16:04:28 +01:00
Laurent Cozic
e518d45719 spell checker 2022-05-02 16:01:27 +01:00
Laurent Cozic
47c3ee0f2c Desktop: Disable crash detection handler for now 2022-05-02 16:01:26 +01:00
Daeraxa
f7b5ed9871 Doc: Stale bot - Add link to forum (#6473) 2022-05-02 15:38:22 +01:00
Laurent Cozic
015562c86a Tools: Added cspell config 2022-05-01 19:38:00 +01:00
Daeraxa
7f3e8c5455 test 2022-05-01 16:16:04 +01:00
Mats Estensen
cef3eac372 translation: update nb_NO (#6463) 2022-05-01 13:39:26 +01:00
Joplin Bot
d97a7bc7ab Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-27 18:17:41 +00:00
Laurent Cozic
9806e3319a Desktop release v2.8.5 2022-04-27 13:21:51 +01:00
Laurent Cozic
22ae50c126 Desktop: More permissive plugin back-off rules 2022-04-27 13:20:25 +01:00
Hieu-Thi Luong
0d4cb5c16a Desktop: Fixed color of links within list in Markdown editor (#6447) 2022-04-27 13:10:28 +01:00
Milo Ivir
0d8c69123b Update Croatian translation (#6441) 2022-04-27 13:08:56 +01:00
Joplin Bot
ae5b810bed Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-26 18:17:04 +00:00
Laurent Cozic
c95367fbbb Doc: Minor fix on API doc 2022-04-26 17:03:58 +01:00
Laurent Cozic
22c83214be Chore: Disable crash detection in dev mode 2022-04-26 13:26:51 +01:00
Laurent Cozic
86179bd74c Desktop: Fixed getting geolocation for new notes 2022-04-26 13:22:43 +01:00
Laurent Cozic
8abdbfe2bd Fixed MdToHtml regression 2022-04-26 12:52:48 +01:00
Daeraxa
790c98ca40 Docs: Update terminal commands (#6457) 2022-04-25 19:35:50 +01:00
Laurent Cozic
774c20772b Security: Fixed disallowed tag XSS 2022-04-25 17:18:27 +01:00
Laurent Cozic
d9a4a9cb30 Desktop: Ask to start in safe mode when the application has crashed 2022-04-25 17:18:26 +01:00
Joplin Bot
bd917ae09c Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-21 00:41:59 +00:00
Joplin Bot
a7c0e926cf Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-20 18:14:32 +00:00
Laurent Cozic
8a65bb5818 Tools: Add commit links to changelog 2022-04-20 17:01:40 +01:00
Laurent Cozic
aa503aee3e Plugin Generator release v2.8.1 2022-04-20 16:31:49 +01:00
Laurent Cozic
373cd805dc Chore: Updated plugin types 2022-04-20 16:30:28 +01:00
Laurent Cozic
fc095986b0 Plugins: Add support for file and directory selector in Settings API 2022-04-20 16:21:57 +01:00
Joplin Bot
8e5d862c36 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-19 18:18:20 +00:00
Laurent Cozic
2399ca6321 Tools: Added precommit hook script to check lib paths 2022-04-19 18:57:59 +01:00
Laurent Cozic
889aecc8d4 Desktop release v2.8.4 2022-04-19 18:03:06 +01:00
Laurent Cozic
36e75310bf fix paths 2022-04-19 18:02:31 +01:00
Laurent Cozic
d3d944d52b Desktop release v2.8.3 2022-04-19 15:55:00 +01:00
Laurent Cozic
89a498b886 Merge branch 'dev' into release-2.8 2022-04-19 15:54:35 +01:00
Laurent Cozic
b7167552ec Desktop: Enabled plugin throttling logic to prevent certain plugins from freezing the app 2022-04-19 15:53:13 +01:00
Joplin Bot
627b16728d Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-19 06:17:18 +00:00
Mr-Kanister
18a7b1a69f Update de_DE.po (#6425) 2022-04-18 10:36:04 +01:00
Joplin Bot
1b1e060226 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-17 18:18:32 +00:00
Laurent Cozic
ebbb1d6d37 Doc: Add new sponsor 2022-04-17 18:07:57 +01:00
Laurent Cozic
dabd0e1d3b Chore: Fixed yarn.lock 2022-04-17 17:54:36 +01:00
Laurent Cozic
64377cd3c9 Tools: Also update mobile package number when creating a new release 2022-04-17 16:42:12 +01:00
asrient
be8001857e Desktop: Fixes #6126: Editor context menu was broken (regression) (#6422) 2022-04-17 16:20:47 +01:00
Laurent
30e191663d Merge branch 'dev' into table_editor 2022-04-17 12:42:46 +01:00
Kenichi Kobayashi
5d9b43ee31 Desktop: Resolves #5770: Performance improvement when switching note while plugins are running (#6409) 2022-04-17 12:41:27 +01:00
Laurent Cozic
1797e847aa Desktop: Multi-profiles: Automatically restart the app when switching profiles on Linux 2022-04-17 12:26:58 +01:00
Laurent Cozic
fd9fe5c931 Desktop: Multi-profiles: Added profile ID in CSS root class so that different profiles can have different styles 2022-04-16 15:13:17 +01:00
Laurent Cozic
b4a6e17090 Desktop: Multi-profiles: Assign an ID to profiles and remove path 2022-04-16 15:04:06 +01:00
Laurent Cozic
0cdef66816 Desktop: Multi-profiles: Share custom CSS between profiles 2022-04-16 13:51:17 +01:00
Laurent Cozic
a111531810 Desktop: Multi-profiles: Share UI layout between profiles 2022-04-16 13:37:04 +01:00
Laurent Cozic
510df43d8d Desktop: Multi-profiles: Share plugins between profiles 2022-04-16 13:36:14 +01:00
Laurent Cozic
845ac19f0d Desktop: Fixes #6411: Locale was not being preserved when creating a new profile 2022-04-16 13:29:14 +01:00
Elaborendum
01ee178566 Update es_ES.po (#6418) 2022-04-16 11:51:47 +01:00
mrkaato0
3f4e7ff74e Update Finnish translation (#6415) 2022-04-16 11:51:19 +01:00
Laurent Cozic
65ad6543a2 fixed ignored files 2022-04-15 17:49:44 +01:00
Laurent Cozic
f27d15a5a7 Desktop: Fixes #6145: Opening a file with ctrl-click in the editor results in a 'network error' dialogue 2022-04-15 17:48:01 +01:00
Laurent Cozic
7c619df2ce Merge branch 'release-2.7' into dev 2022-04-15 15:04:44 +01:00
Laurent Cozic
546447b09b iOS 12.7.2 2022-04-15 12:07:54 +01:00
Laurent Cozic
7ef9d87e76 iOS: Fixes #6375: iOS and Dropbox synchronisation not working on iOS 15 2022-04-15 12:06:24 +01:00
Laurent Cozic
8019306247 Tools: Add React Native HTTP debugging function 2022-04-15 12:04:40 +01:00
Laurent Cozic
818ad62fbe iOS: Fixes #6375: iOS and Dropbox synchronisation not working on iOS 15 2022-04-15 12:03:48 +01:00
Laurent Cozic
671077e1bb Chore: Fixed mobile dropbown regression 2022-04-15 12:02:10 +01:00
Laurent
1b043d856d Chore: Refactor note list on desktop using React Hooks (#6410) 2022-04-14 16:50:42 +01:00
Joplin Bot
7e1ee40333 Doc: Updated Markdown files
Auto-updated using release-website.sh
2022-04-14 12:21:57 +00:00
Laurent Cozic
343b81ad09 Desktop: Resolves #6394: Improve performance when switching notes, when multiple plugins are loaded
By preventing the menu bar to needlessly re-render
2022-04-14 12:27:19 +01:00
Laurent
70cd2395fb Merge branch 'dev' into table_editor 2022-04-12 23:31:35 +01:00
Laurent
2f1b6fbee1 Merge branch 'dev' into table_editor 2022-04-12 15:25:01 +01:00
Laurent
8c0d4a0f71 Merge branch 'dev' into table_editor 2022-04-11 20:04:24 +01:00
Laurent
bc08c6dcc3 Merge branch 'dev' into table_editor 2022-04-05 19:17:37 +01:00
Laurent Cozic
8352e23bdf Desktop release v2.7.15 2022-03-12 16:27:06 +00:00
Laurent Cozic
96137d74f0 Merge branch 'dev' into release-2.7 2022-03-12 16:25:11 +00:00
Laurent Cozic
a06365039d table editor init 2022-03-07 17:17:41 +00:00
Laurent Cozic
75cc9d7f9b Desktop release v2.7.14 2022-02-27 10:51:37 +00:00
Laurent Cozic
693e52a1fd Merge branch 'dev' into release-2.7 2022-02-27 10:51:12 +00:00
Laurent Cozic
5b5165dbcd Desktop release v2.7.13 2022-02-24 15:10:49 +00:00
218 changed files with 25507 additions and 18298 deletions

View File

@@ -421,6 +421,9 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/styles/index.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/tables.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/tables.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/tables.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js.map
@@ -562,6 +565,9 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
packages/app-desktop/gui/NoteList/commands/index.d.ts
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/commands/index.js.map
packages/app-desktop/gui/NoteList/types.d.ts
packages/app-desktop/gui/NoteList/types.js
packages/app-desktop/gui/NoteList/types.js.map
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
@@ -685,6 +691,9 @@ packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js.map
packages/app-desktop/gui/SyncWizard/Dialog.d.ts
packages/app-desktop/gui/SyncWizard/Dialog.js
packages/app-desktop/gui/SyncWizard/Dialog.js.map
packages/app-desktop/gui/TableEditorDialog/Dialog.d.ts
packages/app-desktop/gui/TableEditorDialog/Dialog.js
packages/app-desktop/gui/TableEditorDialog/Dialog.js.map
packages/app-desktop/gui/TagList.d.ts
packages/app-desktop/gui/TagList.js
packages/app-desktop/gui/TagList.js.map
@@ -805,6 +814,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/restart.d.ts
packages/app-desktop/services/restart.js
packages/app-desktop/services/restart.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.js.map
@@ -961,6 +973,9 @@ packages/generator-joplin/generators/app/templates/src/index.js.map
packages/htmlpack/src/index.d.ts
packages/htmlpack/src/index.js
packages/htmlpack/src/index.js.map
packages/lib/ArrayUtils.d.ts
packages/lib/ArrayUtils.js
packages/lib/ArrayUtils.js.map
packages/lib/AsyncActionQueue.d.ts
packages/lib/AsyncActionQueue.js
packages/lib/AsyncActionQueue.js.map
@@ -1096,6 +1111,9 @@ packages/lib/fs-driver-node.js.map
packages/lib/fsDriver.test.d.ts
packages/lib/fsDriver.test.js
packages/lib/fsDriver.test.js.map
packages/lib/geolocation-node.d.ts
packages/lib/geolocation-node.js
packages/lib/geolocation-node.js.map
packages/lib/hooks/useAsyncEffect.d.ts
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useAsyncEffect.js.map
@@ -1987,6 +2005,9 @@ packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/headerAnchor.d.ts
packages/renderer/headerAnchor.js
packages/renderer/headerAnchor.js.map
packages/renderer/highlight.d.ts
packages/renderer/highlight.js
packages/renderer/highlight.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
@@ -2011,6 +2032,12 @@ packages/tools/buildServerDocker.js.map
packages/tools/buildServerDocker.test.d.ts
packages/tools/buildServerDocker.test.js
packages/tools/buildServerDocker.test.js.map
packages/tools/checkLibPaths.d.ts
packages/tools/checkLibPaths.js
packages/tools/checkLibPaths.js.map
packages/tools/checkLibPaths.test.d.ts
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.test.js.map
packages/tools/convertThemesToCss.d.ts
packages/tools/convertThemesToCss.js
packages/tools/convertThemesToCss.js.map
@@ -2047,6 +2074,9 @@ packages/tools/release-server.js.map
packages/tools/setupNewRelease.d.ts
packages/tools/setupNewRelease.js
packages/tools/setupNewRelease.js.map
packages/tools/spellcheck.d.ts
packages/tools/spellcheck.js
packages/tools/spellcheck.js.map
packages/tools/tagServerLatest.d.ts
packages/tools/tagServerLatest.js
packages/tools/tagServerLatest.js.map
@@ -2074,6 +2104,9 @@ packages/tools/website/updateNews.js.map
packages/tools/website/utils/frontMatter.d.ts
packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/frontMatter.js.map
packages/tools/website/utils/news.d.ts
packages/tools/website/utils/news.js
packages/tools/website/utils/news.js.map
packages/tools/website/utils/openGraph.d.ts
packages/tools/website/utils/openGraph.js
packages/tools/website/utils/openGraph.js.map

View File

@@ -12,7 +12,7 @@ jobs:
with:
# Use this to do a dry run from a pull request
# debug-only: true
stale-issue-message: "Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may comment on the issue and I will leave it open. Thank you for your contributions."
stale-issue-message: "Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? If you require support or are requesting an enhancement or feature then please create a topic on the [Joplin forum](https://discourse.joplinapp.org/). This issue may be closed if no further activity occurs. You may comment on the issue and I will leave it open. Thank you for your contributions."
days-before-stale: 30
days-before-close: 7
operations-per-run: 1000

33
.gitignore vendored
View File

@@ -411,6 +411,9 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/styles/index.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/tables.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/tables.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/tables.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js.map
@@ -552,6 +555,9 @@ packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js.map
packages/app-desktop/gui/NoteList/commands/index.d.ts
packages/app-desktop/gui/NoteList/commands/index.js
packages/app-desktop/gui/NoteList/commands/index.js.map
packages/app-desktop/gui/NoteList/types.d.ts
packages/app-desktop/gui/NoteList/types.js
packages/app-desktop/gui/NoteList/types.js.map
packages/app-desktop/gui/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
@@ -675,6 +681,9 @@ packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js.map
packages/app-desktop/gui/SyncWizard/Dialog.d.ts
packages/app-desktop/gui/SyncWizard/Dialog.js
packages/app-desktop/gui/SyncWizard/Dialog.js.map
packages/app-desktop/gui/TableEditorDialog/Dialog.d.ts
packages/app-desktop/gui/TableEditorDialog/Dialog.js
packages/app-desktop/gui/TableEditorDialog/Dialog.js.map
packages/app-desktop/gui/TagList.d.ts
packages/app-desktop/gui/TagList.js
packages/app-desktop/gui/TagList.js.map
@@ -795,6 +804,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/restart.d.ts
packages/app-desktop/services/restart.js
packages/app-desktop/services/restart.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.js.map
@@ -951,6 +963,9 @@ packages/generator-joplin/generators/app/templates/src/index.js.map
packages/htmlpack/src/index.d.ts
packages/htmlpack/src/index.js
packages/htmlpack/src/index.js.map
packages/lib/ArrayUtils.d.ts
packages/lib/ArrayUtils.js
packages/lib/ArrayUtils.js.map
packages/lib/AsyncActionQueue.d.ts
packages/lib/AsyncActionQueue.js
packages/lib/AsyncActionQueue.js.map
@@ -1086,6 +1101,9 @@ packages/lib/fs-driver-node.js.map
packages/lib/fsDriver.test.d.ts
packages/lib/fsDriver.test.js
packages/lib/fsDriver.test.js.map
packages/lib/geolocation-node.d.ts
packages/lib/geolocation-node.js
packages/lib/geolocation-node.js.map
packages/lib/hooks/useAsyncEffect.d.ts
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useAsyncEffect.js.map
@@ -1977,6 +1995,9 @@ packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/headerAnchor.d.ts
packages/renderer/headerAnchor.js
packages/renderer/headerAnchor.js.map
packages/renderer/highlight.d.ts
packages/renderer/highlight.js
packages/renderer/highlight.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
@@ -2001,6 +2022,12 @@ packages/tools/buildServerDocker.js.map
packages/tools/buildServerDocker.test.d.ts
packages/tools/buildServerDocker.test.js
packages/tools/buildServerDocker.test.js.map
packages/tools/checkLibPaths.d.ts
packages/tools/checkLibPaths.js
packages/tools/checkLibPaths.js.map
packages/tools/checkLibPaths.test.d.ts
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.test.js.map
packages/tools/convertThemesToCss.d.ts
packages/tools/convertThemesToCss.js
packages/tools/convertThemesToCss.js.map
@@ -2037,6 +2064,9 @@ packages/tools/release-server.js.map
packages/tools/setupNewRelease.d.ts
packages/tools/setupNewRelease.js
packages/tools/setupNewRelease.js.map
packages/tools/spellcheck.d.ts
packages/tools/spellcheck.js
packages/tools/spellcheck.js.map
packages/tools/tagServerLatest.d.ts
packages/tools/tagServerLatest.js
packages/tools/tagServerLatest.js.map
@@ -2064,6 +2094,9 @@ packages/tools/website/updateNews.js.map
packages/tools/website/utils/frontMatter.d.ts
packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/frontMatter.js.map
packages/tools/website/utils/news.d.ts
packages/tools/website/utils/news.js
packages/tools/website/utils/news.js.map
packages/tools/website/utils/openGraph.d.ts
packages/tools/website/utils/openGraph.js
packages/tools/website/utils/openGraph.js.map

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 06 Jun 2022 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 06 Jun 2022 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin 2.8 is available!]]></title><description><![CDATA[<p>As always a lot of changes and new features in this new version available on both desktop and mobile.</p>
<h1>Multiple profile support<a name="multiple-profile-support" href="#multiple-profile-support" class="heading-anchor">🔗</a></h1>
<p>Perhaps the most visible change in this version is the support for multiple profiles. You can now create as many application profile as you wish, each with their own settings, and easily switch from one to another. The main use case is to support for example a &quot;work&quot; profile and a &quot;personal&quot; profile, to allow you to keep things independent, and each profile can sync with a different sync target.</p>
<p>To create a new profile, open <strong>File &gt; Switch profile</strong> and select <strong>Create new profile</strong>, enter the profile name and press OK. The app will automatically switch to this new profile, which you can now configure.</p>
<p>To switch back to the previous profile, again open <strong>File &gt; Switch profile</strong> and select <strong>Default</strong>.</p>
<p>Note that profiles all share certain settings, such as language, font size, theme, etc. This is done so that you don't have reconfigure every details when switching profiles. Other settings such as sync configuration is per profile.</p>
<p>The feature is available on desktop only for now, and should be ported to mobile relatively soon.</p>
<h1>Save Mermaid graph as PNG/SVG<a name="save-mermaid-graph-as-png-svg" href="#save-mermaid-graph-as-png-svg" class="heading-anchor">🔗</a></h1>
<p>This convenient feature allows exporting a Mermaid graph as a PNG or SVG image, or allows copying the image as a DataUrl, which can then be pasted in any compatible text editor. Thanks Asrient for implementing this!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20220606-mermaid-as-png.png" alt=""></p>
<h1>Publish a mini-website using Joplin Cloud<a name="publish-a-mini-website-using-joplin-cloud" href="#publish-a-mini-website-using-joplin-cloud" class="heading-anchor">🔗</a></h1>
<p>Joplin Cloud now supports publishing a note &quot;recursively&quot;, which means the notes and all the notes it is linked to. This allows easily publishing a simple website made of multiples and images.</p>
<p>To make use of this feature, simply select <strong>Also publish linked notes</strong> when publishing a note.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20220606-publish-website.png" alt=""></p>
<h1>And more!<a name="and-more" href="#and-more" class="heading-anchor">🔗</a></h1>
<p>In total there are 38 changes to improve the app reliability, security and usability. Full changelog is at <a href="https://joplinapp.org/changelog/">https://joplinapp.org/changelog/</a></p>
]]></description><link>https://joplinapp.org/news/20220606-release-2-8/</link><guid isPermaLink="false">20220606-release-2-8</guid><pubDate>Mon, 06 Jun 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin received 6 Contributor Projects for GSoC 2022!]]></title><description><![CDATA[<p>We are glad to announce that Google allocated us six projects this year for Google Summer of Code! So this is six contributors who will be working on various parts of the apps, both desktop and mobile, over the summer.</p>
<p>Over the next few weeks, till 13 June, will be the Community Bonding Period during which GSoC contributors get to know mentors, read documentation, and get up to speed to begin working on their projects.</p>
<p>Here's the full list of projects, contributors and mentors.</p>
<table class="table">
<thead>
<tr>
<th>Project Title</th>
<th>Contributor</th>
<th>Assigned Mentor(s)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Improve PDF previewer of Joplin</td>
<td>asrient</td>
<td>Roman, JackGruber</td>
</tr>
<tr>
<td>Implement default plugins on desktop application</td>
<td>mak2002</td>
<td>CalebJohn, Laurent</td>
</tr>
<tr>
<td>Mobile — Easier Editing</td>
<td>Henry H</td>
<td>Daeraxa, CalebJohn</td>
</tr>
<tr>
<td>Improve plugin search and discoverability</td>
<td>Retr0ve</td>
<td>JackGruber, Stefan</td>
</tr>
<tr>
<td>Tablet Layout Project</td>
<td>Tolu-Mals</td>
<td>Laurent, Daeraxa</td>
</tr>
<tr>
<td>Email Plugin</td>
<td>Bishoy Magdy Adeeb</td>
<td>Stefan, Roman</td>
</tr>
</tbody>
</table>
]]></description><link>https://joplinapp.org/news/20220522-gsoc-contributors/</link><guid isPermaLink="false">20220522-gsoc-contributors</guid><pubDate>Sun, 22 May 2022 00:00:00 GMT</pubDate><twitter-text>Joplin received 6 Contributor Projects for GSoC 2022! Welcome to our new contributors who will be working on these projects over summer! #GSoC2022</twitter-text></item><item><title><![CDATA[GSoC "Contributor Proposals" phase is starting now!]]></title><description><![CDATA[<p>The &quot;Contributor Proposals&quot; phase of GSoC 2022 is starting today! If you would like to be a contributor, now is the time to choose your project idea, write your proposal, and upload it to <a href="https://summerofcode.withgoogle.com/">https://summerofcode.withgoogle.com/</a></p>
<p>When it's done, please also let us know by posting an update on your forum introduction post.</p>
<p>If you haven't created a pull request yet, it's still time to create one. Doing so will greatly increase your chances of being selected!</p>
]]></description><link>https://joplinapp.org/news/20220405-gsoc-contributor-proposals/</link><guid isPermaLink="false">20220405-gsoc-contributor-proposals</guid><pubDate>Tue, 05 Apr 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin participates in Google Summer of Code 2022!]]></title><description><![CDATA[<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20220308-gsoc-banner.png" alt=""></p>
<p>For the third year, Joplin has been selected as a <strong>Google Summer of Code</strong> mentor organisation! We look forward to start working with the contributors on some great new projects. This year's main themes are:</p>
<ul>
<li><strong>Mobile and tablet development</strong> - we want to improve the mobile/tablet application on iOS and Android.</li>
<li><strong>Plugin and external apps</strong> - leverage the Joplin API to create plugins and external apps.</li>
<li>And of course contributors are welcome to suggest their own ideas.</li>
</ul>
<p>Our full idea list is available here: <a href="https://joplinapp.org/gsoc2022/ideas/">GSoC 2022 idea list</a></p>
<p>In the coming month (<strong>March 7 - April 3</strong>), contributors will start getting involved in the forum and start discussing project ideas with the mentors and community. It's also a good time to start looking at Joplin's source code, perhaps work on fixing bugs or implement small features to get familiar with the source code, and to show us your skills.</p>
<p>One difference with previous years is that anyone, not just students, are allowed to participate.</p>
<p>Additionally, last year Google only allowed smaller projects, while this year they allow again small and large projects, so we've indicated this in the idea list - the small ones are <strong>175 hours</strong>, and the large ones <strong>350 hours</strong>.</p>
]]></description><link>https://joplinapp.org/news/20220308-gsoc2022-start/</link><guid isPermaLink="false">20220308-gsoc2022-start</guid><pubDate>Tue, 08 Mar 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin 2.7 is available!]]></title><description><![CDATA[<p>This new release is largely focused on bug fixing and optimising various parts of the apps. There's about 26 improvements and 25 bugs and security fixes included - as always many of these apply to both the mobile and desktop app (see the <a href="https://joplinapp.org/changelog/">desktop changelog</a> and <a href="https://joplinapp.org/changelog_android/">mobile changelog</a>).</p>
<p>Many thanks to all the contributors who helped create this release!</p>
<p>Below are some of the more noticeable changes:</p>
<h1>Notebook custom icons<a name="notebook-custom-icons" href="#notebook-custom-icons" class="heading-anchor">🔗</a></h1>
<p>Since version 2.6 it was possible to assign an emoji icon to a notebook, and with this new version it's now possible to assign any custom icon. The icon may be a PNG or JPG file of any size. The app will then import the file and resize it to the correct size. To use a custom icon, follow these steps:</p>
<p>Right-click on a notebook, and select &quot;Edit&quot;:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20220224-edit-notebook.png" alt=""></p>
<p>In the &quot;Edit notebook&quot; dialog, click &quot;Select file...&quot; and browse to your icon image:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20220224-edit-dialog.png" alt=""></p>
<p>Click &quot;OK&quot; and the icon will now appear next to the notebook:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20220224-notebook-icon.png" alt=""></p>
<p>The icon can be changed only from the desktop application at the moment, but it will sync and be displayed correctly on the mobile app too.</p>
<h1>Plugin API improvements<a name="plugin-api-improvements" href="#plugin-api-improvements" class="heading-anchor">🔗</a></h1>
<p>This version also includes a number of improvements to the plugin API, in particular it is now easier to customise the editor context menu from a plugin and dynamically add items to it depending on the context. For example, with the Rich Markdown plugin it will be possible to right-click on an image and open it, or copy it to the clipboard.</p>
<p>A few additional functions have also been added to make plugin development simpler - in particular a command to open any item, whether it's a notebook, note, tag or attachement; and functions to work with attachements, in particular to reveal an attachement in the system file explorer, and to track changes to an attachement.</p>
]]></description><link>https://joplinapp.org/news/20220224-release-2-7/</link><guid isPermaLink="false">20220224-release-2-7</guid><pubDate>Thu, 24 Feb 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Automatic deletion of disabled accounts on Joplin Cloud]]></title><description><![CDATA[<p>As of 15 Feb 2022, disabled accounts on Joplin Cloud will be automatically deleted after 90 days. A disabled account is one where the Stripe subscription has been cancelled either by the user or automatically (eg for unpaid invoices).</p>
<p>Although it is an automated system, I will manually verify each account that's queued for deletion over the next few days for additional safety (for now everything's working as expected).</p>
<p>When an account is queued for deletion, all notes, notebooks, tags, etc are removed from the system within 2 days, and permanently deleted within 7 days. User information, in particular email and full name will be removed from the system within 2 days, but archived for an additional 90 days for legal reasons, after which they will be deleted too.</p>
]]></description><link>https://joplinapp.org/news/20220215-142000/</link><guid isPermaLink="false">20220215-142000</guid><pubDate>Tue, 15 Feb 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin 2.6 is available!]]></title><description><![CDATA[<p>Many changes in this new release, available on mobile, desktop and CLI:</p>
<p><strong>Per-notebook sort order and sort buttons</strong></p>
<p>This new feature adds a number of changes to the way notes are sorted. The most visible one is the addition of a sort button above the note list - it allows sorting by modification date, creation date, title or by custom order, in either ascending or descending order:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_0.png" alt=""></p>
<p>By default, this sort order is going to apply to all notebooks, however you can now also assign a per-notebook sort order. In this case, any sort order will be apply to that notebook only. To enable this behaviour, simply right-click on a notebook and select &quot;Toggle own sort order&quot;:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_1.png" alt=""></p>
<p>Thanks to Kenichi Kobayashi for developing this feature!</p>
<p><strong>Support for notebook icons</strong></p>
<p>It is now possible to associate icons with notebooks no both the desktop and mobile applications. To do so, right-click on a notebook and selected &quot;Edit&quot;.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_2.png" alt=""></p>
<p>This will open the new notebook dialog from which can change the title and assign an icon. For now the icons are emojis but perhaps custom icons could be supported later on.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211217-120324_3.png" alt=""></p>
<p><strong>Allow collaborating on encrypted notebooks using Joplin Cloud</strong></p>
<p>Thanks to the encryption improvements in the previous Joplin versions it is now possible to share and collaborated on encrypted notebooks, when synchronising with Joplin Cloud (or Joplin Server).</p>
<p>To get this working, you and the recipient will need to have Joplin 2.6 and the person who shares will need to have encryption enabled. After that most of the process is handled automatically by the apps - in particular it will automatically generate and share the required encryption keys for each users.</p>
<p><strong>Improved synchronisation startup speed</strong></p>
<p>Synchronisation is also a bit faster in this release due to an optimisation on the startup process. When syncing, the app needs to acquire a lock, which may be time consuming since it requires making multiple requests. This has now been optimised so that less requests are necessary and also each request consumes less resources. This will have a postive impact on Joplin Cloud in particular, but you should also see improvements with Joplin Server and smaller improvements with the other sync targets.</p>
<p><strong>Improved Markdown editor split view scrolling</strong></p>
<p>Kenichi Kobayashi made some great improvements to the Markdown editor scrolling in this release. The issue before was that the editor on the left and the viewer on the right would often not be in sync, in particular if the note contains several images and other media.</p>
<p>With Kenichi's change the editor and viewer stay nicely in sync, regardless of the note content. In fact it looks a bit like magic when you scroll through large notes - notice in particular how each side appear to wait for the other or speed up in order to make sure both sides are aligned as well possible. Kenichi provides a nice technical documentation about the feature <a href="https://github.com/laurent22/joplin/pull/5512#issuecomment-931277022">here</a>.</p>
<p><a href="https://www.youtube.com/watch?v=Wbs5XZR0oeU">https://www.youtube.com/watch?v=Wbs5XZR0oeU</a></p>
<p><strong>Improved and optimised S3 synchronisation</strong></p>
<p>Thanks to the efforts of Lee Matos, synchronisation with S3 is now more reliable and errors are also better handled. The underlying S3 SDK has also been upgraded from v2 to v3 which results in a smaller executable size (about 3-5 MB depending on the operating system)</p>
<p><strong>Export notes as self-contained HTML files</strong></p>
<p>Exporting a single note as HTML is now more user friendly as all images, scripts, styles and other attachments are all packed into a single HTML file (Previously it would create multiples files and directories). This makes it easier to share the complete note with someone who doesn't have Joplin.</p>
<p><strong>Other changes and bug fixes</strong></p>
<p>This release includes a total of 19 new features and improvements and 16 bug fixes. See the 2.6.x changelogs for more details:</p>
<p><a href="https://joplinapp.org/changelog/">https://joplinapp.org/changelog/</a></p>
<p><a href="https://joplinapp.org/changelog_android/">https://joplinapp.org/changelog_android/</a></p>
<p><a href="https://joplinapp.org/changelog_ios/">https://joplinapp.org/changelog_ios/</a></p>
<p><a href="https://joplinapp.org/changelog_cli/">https://joplinapp.org/changelog_cli/</a></p>
]]></description><link>https://joplinapp.org/news/20211217-120324/</link><guid isPermaLink="false">20211217-120324</guid><pubDate>Fri, 17 Dec 2021 12:03:24 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Potential breaking change in next Joplin Server update (2.5.10)]]></title><description><![CDATA[<p>Just a head up that the next Joplin Server update could potentially include a breaking change, depending on your data.</p>
<p>One of the database migration is going to add an &quot;owner_id&quot; column to the &quot;items&quot; table (where all notes, notebooks, etc. are stored), and automatically populate it. Normally that shouldn't take too long but you might want to make sure you won't need the server right away when you process this.</p>
<p>The second database migration will add a unique constraint on items.name and items.owner_id and that's where the breaking change might be. Normally this data is already unique because that's enforced by the application but in some rare cases, due a race condition, there could be duplicate data in there. If that happens the migration will fail and the server will not start.</p>
<p>If that happens, you'll need to decide what to do with the data, as it's not possible to automatically decide. You can find all duplicates using this query:</p>
<p><code><strong>select</strong> count(<em>), name, owner_id<br>
<strong>from</strong> items <strong>group</strong> <strong>by</strong> name, owner_id<br>
<strong>having</strong> count(</em>) &gt; 1;</code></p>
<p>Once you have the list of IDs you have a few options:</p>
<ul>
<li>Find the corresponding item in Joplin (it can unfortunately be anything - a note, resource, folder, etc.), then delete it and sync.</li>
<li>Or, just delete the data directly in the database. You'll want to delete the corresponding item_id from the user_items table too.</li>
</ul>
<p>But really in most cases you should be fine. Especially if you don't have that many notes it's unlikely you have duplicates.</p>
]]></description><link>https://joplinapp.org/news/20211102-150403/</link><guid isPermaLink="false">20211102-150403</guid><pubDate>Tue, 02 Nov 2021 15:04:03 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin v2.5 is available for desktop and mobile!]]></title><description><![CDATA[<p><a href="https://joplinapp.org/download/">Joplin v2.5</a> is now available for desktop, mobile and CLI! Here's an overview of the changes:</p>
<h3>Support for Markdown + Front Matter<a name="support-for-markdown-front-matter" href="#support-for-markdown-front-matter" class="heading-anchor">🔗</a></h3>
<p>Markdown + Front Matter is a format that allows attaching metadata, such as tags, creation date, or geolocation to a Markdown file. This is done by adding a block of YAML code (a &quot;front matter&quot;) at the top of the file.</p>
<p>Thansk to Caleb John's efforts the Joplin desktop and CLI applications now support importing and exporting these files. When exporting, we try to preserve as much metadata as possible, while still keeping it the formatting user friendly.</p>
<p>Here's an example, with the Front Matter at the top, delimited by &quot;---&quot;, and the text below.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211031-115215_0.png" alt=""></p>
<p>Markdown + Front Matter is an excellent way to share notes with someone who doesn't have Joplin, to backup notes in a durable format (since no third-party application is needed to read it), and also to export notes to other applications, or to import them.</p>
<p>As with the regular Markdown exporter, the images and attachments are also exported.</p>
<h3>Add support for callback URLs<a name="add-support-for-callback-urls" href="#add-support-for-callback-urls" class="heading-anchor">🔗</a></h3>
<p>Callback URLs is a semi-standard that defines how certain resources in an application can be accessed via URLs. Either to view the resource, or to perform certain actions, such as deletion, creation, etc.</p>
<p>Joplin now support callback URLs to open notes, notebooks and folders. To do so, right click on a note and select &quot;Copy external link&quot;:</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20211031-115215_1.png" alt=""></p>
<p>That would give you a URL such as this:</p>
<blockquote>
<p>joplin://x-callback-url/openNote?id=b7a7b93281f54d928612eea550f33a7f</p>
</blockquote>
<p>Then if you click it from outside the app, the app will open and select this particular note. In practice such a feature allows third-party application to interact with Joplin by creating links that can be opened from outside. For example, you may use a different application for project planning, then link to the individual notes for more details about each task.</p>
<p>Many thanks to Roman Musin for adding the feature!</p>
<h3>Improved end-to-end encryption support<a name="improved-end-to-end-encryption-support" href="#improved-end-to-end-encryption-support" class="heading-anchor">🔗</a></h3>
<p>The series of quiet but major changes to the end-to-end encryption support continue in this new verison. One goal is still to allow sharing notebooks while encryption is enabled.</p>
<p>To that end, v2.5 includes support for RSA public-private key pairs. If you have encryption enabled, they will be automatically generated when you synchronise by the mobile, desktop or CLI applications. Later on, these keys will be used to allow sharing encrypted notebooks.</p>
<p>The second goal of these E2EE changes is to simplify the system enough that it can be enabled by default. To that end, the master password dialog and encryption screen have been improved. An option to reset the master password is now also available.</p>
<h3>Various other improvements and bug fixes<a name="various-other-improvements-and-bug-fixes" href="#various-other-improvements-and-bug-fixes" class="heading-anchor">🔗</a></h3>
<p>In total this release includes about 11 other bug fixes and improvements. There was in particular several improvements to the share features. It is now also possible for a share recipient to leave the shared notebook.</p>
<h3>Mobile app update<a name="mobile-app-update" href="#mobile-app-update" class="heading-anchor">🔗</a></h3>
<p>As always the mobile apps (to be released soon) benefit from several of the above changes since they share the same codebase as the desktop app.</p>
<p>Specific to the mobile version 2.5 are some improvements to the beta editor - in particular the layout has been cleaned up, and the first word of sentences is now automatically capitalised, which makes typing notes easier. If you haven't tried the beta editor yet, you can enable it from the Configuration screen.</p>
<p>The full changelog is available there: <a href="https://joplinapp.org/changelog/">https://joplinapp.org/changelog/</a></p>
]]></description><link>https://joplinapp.org/news/20211031-115215/</link><guid isPermaLink="false">20211031-115215</guid><pubDate>Sun, 31 Oct 2021 11:52:15 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA["Certificate has expired" error with Joplin Cloud, and workaround]]></title><description><![CDATA[<p>Some of you might be experiencing an error &quot;Certificate has expired&quot; when synchronising with Joplin Cloud (and possibly other services) when using the desktop application.</p>
<p>This is due to Let's Encrypt root certificate that expired on 30 September, and the new method they are using is not compatible with the Joplin desktop application.</p>
<p>This actually affects thousands of applications, not just Joplin, so various solutions are being considered right now and hopefully a fix will be available relatively soon.</p>
<p>For now, as a workaround, you can simply check &quot;<strong>Ignore TLS certificate errors</strong>&quot; in <strong>Configuration &gt; Synchronisation &gt; Advanced Options</strong></p>
<p>I will let you know as soon as a fix is available so that you can clear that option.</p>
<p>More info:</p>
<p>- <a href="https://community.letsencrypt.org/t/issues-with-electron-and-expired-root/160991">Issue with Electron and expired root</a> on Let's Encrypt</p>
<p>- <a href="https://github.com/electron/electron/issues/31212">Let's Encrypt root CA isn't working properly</a> on Electron GitHub repository</p>
<p><strong>Update:</strong> I have implemented a temporary fix on Joplin Cloud which should solve the issue for now. If you're still having some issues please let me know. An updated desktop app will be available later on with a more permanent fix.</p>
]]></description><link>https://joplinapp.org/news/20210930-163458/</link><guid isPermaLink="false">20210930-163458</guid><pubDate>Thu, 30 Sep 2021 16:34:58 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin 2.4 is available!]]></title><description><![CDATA[<p>Joplin 2.4 is now available on desktop, mobile and CLI. Here's what's new in this release:</p>
<h3>Sync Wizard Dialog<a name="sync-wizard-dialog" href="#sync-wizard-dialog" class="heading-anchor">🔗</a></h3>
<p>A new Sync Wizard Dialog has been added to simplify setting up sync on new clients.</p>
<p>The dialog shows the main sync targets, their differences, and makes it easy to choose one and start synchronising. This is mostly aimed at new users or those perhaps less technical. Those who are self hosting or using complex setups will still easily find what they need from a link on that dialog (or in Config &gt; Synchronisation like before).</p>
<p>Sync setup on mobile has been slightly improved too - now on a new client, instead of asking you to sync with Dropbox directly (which may not be what you want), it jumps to the Config &gt; Synchronisation section where you can select the sync target</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210929-144036_0.png" alt=""></p>
<h3>Disable synchronisation<a name="disable-synchronisation" href="#disable-synchronisation" class="heading-anchor">🔗</a></h3>
<p>It's a small change but something that's been asked many time - it's now possible to disable synchronisation entirely by selecting &quot;None&quot; as a sync target. Previously that could be done in a hacky way, by selecting a non-configured sync target. Now it's clearer and easier to do.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210929-144036_1.png" alt=""></p>
<h3>Add back support for deprecated plugins<a name="add-back-support-for-deprecated-plugins" href="#add-back-support-for-deprecated-plugins" class="heading-anchor">🔗</a></h3>
<p>Recently some plugins stopped working because deprecated plugin APIs had been removed. It had been planned for a long time but I suspect the warnings weren't visible enough so plugin developers didn't act on them, and as a result many plugins stopped working.</p>
<p>This is now fixed in the latest version. A selected number of plugins will have access to these old deprecated APIs, which means they will start working again. This was mainly affecting ambrt's plugins such as &quot;Convert Text To New Note&quot; or the popular &quot;Embed Search&quot; plugin.</p>
<h3>Add support for recommended plugins<a name="add-support-for-recommended-plugins" href="#add-support-for-recommended-plugins" class="heading-anchor">🔗</a></h3>
<p>As mentioned in an earlier post, we now support <a href="https://www.patreon.com/posts/introducing-in-55618802">recommended plugins</a>. These recommended plugins appear on top when searching and are identified by a small crown.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210929-144036_2.png" alt=""></p>
<h3>End to End Encryption improvements<a name="end-to-end-encryption-improvements" href="#end-to-end-encryption-improvements" class="heading-anchor">🔗</a></h3>
<p>Like most recent releases, v2.4 includes a few improvement to the End to End Encryption (E2EE) system. The goal is to make it easier to use, to make it more reliable and to support the future use case of sharing encrypted notebooks or notes.</p>
<p>One important change is the support for a master password. This single password will be responsible to encrypt various keys, including some that will be automatically generated. Thanks to this, it won't be necessary to ask to enter a new password every time a key needs to be encrypted, since the master password can be used. It will also be easier to manage since you'll only have one password to remember instead of a different one for each notebook you might have shared.</p>
<p>Finally, it's now possible to disable a master key. What it means is that it will no longer show up in the list of master keys, and will also no longer generate a warning asking you to enter the password. In some case you might have forgotten it and no longer need it key, so you can now disable it.</p>
<h3>Custom CSS<a name="custom-css" href="#custom-css" class="heading-anchor">🔗</a></h3>
<p>This version also introduces a few internal change to better support custom CSS. In particular the colours now come from a CSS file, which could potentially be overridden, and new UI elements are styled using stylesheets, which likewise could be overridden.</p>
<p>Those are just first steps, but eventually these changes will make it easier to style the UI and create new themes.</p>
<h3>Bug fixes<a name="bug-fixes" href="#bug-fixes" class="heading-anchor">🔗</a></h3>
<p>This release also includes about 30 various bug fixes and improvements.</p>
<p>A notable one is a fix for GotoAnything, which recently wasn't working on first try.</p>
<p>The plugin screen has also been improved so that search works even when GitHub is down or blocked, as it is in China in particular.</p>
]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing recommended plugins in the next Joplin version]]></title><description><![CDATA[<p>A common request from new users is how to know which plugin is safe to install or not. In fact probably all of them are safe but as a new user that's not necessarily easy to know. So to help with this, the next version of Joplin will support recommended plugins - those will be plugins that meet our standards of quality and performance, and they will be indicated by a small crown tag inside the plugin box. Recommended plugins will also appear on top when searching.</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210901-113415_0.png" alt=""></p>
<p>For now, since we don't have a review process, the recommended plugins are those developed by the Joplin team and frequent contributors, because we know those are safe to use.</p>
<p>Later we might have a review process and add more recommended plugins. That being said, in the meantime even if a plugin is not marked as recommended, there's a good chance it is still safe and have good performance too. Often you can search for it on the forum and if it's active with many users commenting, you're most likely good to go.</p>
<p>But if there's any doubt, the recommended tag is a good way to be sure.</p>
]]></description><link>https://joplinapp.org/news/20210901-113415/</link><guid isPermaLink="false">20210901-113415</guid><pubDate>Wed, 01 Sep 2021 11:34:15 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin Cloud is officially production ready!]]></title><description><![CDATA[<p><a href="https://joplinapp.org/plans/">Joplin Cloud</a> has been out of beta for a few weeks now and since then it has been quietly running without any troubles. There is no known bugs and the service is running smoothly so it's now safe to say that it is production ready!</p>
<p>As a reminder, Joplin Cloud is meant to provide a more seamless Joplin experience - if you want to quickly get started, it's as easy as downloading the app and getting a Joplin Cloud account. Besides improved sync performance, that will give you the ability to collaborate on notebooks with others, as well as publishing and sharing notes.</p>
<p>Of course Joplin still supports other sync options such as Nextcloud, Dropbox and OneDrive or AWS S3. You can also self host using Joplin Server. The advantage of Joplin Cloud being that you don't need to maintain a server yourself - for a small fee you'll get that taken care of.</p>
<p>Additionally, subscribing to Joplin Cloud is a great way to support the project as a whole, including the open source applications. Such support is needed in the long term to provide bug and security fixes, add new features, and provide support.</p>
<p>At some level it is also an experiment, to see if such a service is financially viable and can allow me to work full time on the project. This is certainly something I would like, and perhaps Joplin Cloud combined with your donations will allow that.</p>
]]></description><link>https://joplinapp.org/news/20210831-154354/</link><guid isPermaLink="false">20210831-154354</guid><pubDate>Tue, 31 Aug 2021 15:43:54 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[How to start your subscription if you have a free Joplin Cloud Beta account]]></title><description><![CDATA[<p>For anyone with a beta account, if you would like to keep using it after the end of the trial period, there is now a button to do this from the Joplin Cloud home page:</p>
<img height="222" src="https://aws1.discourse-cdn.com/standard14/uploads/cozic/optimized/2X/e/e2b54352d0e401e692a75817f6faa0432322c405_2_517x222.png" width="517">
<p>If you click on it you will be sent to the Plans page via a special link. Then once you click on &quot;Buy now&quot; you will be sent to the Stripe page where you can start the subscription.</p>
<p>As mentioned in the message, the process takes into account your remaining beta trial days. So for example, if your beta account expires in 60 days, the subscription will have a free 60 days trial period. This is so you don't lose any of the beta trial days no matter when you start the subscription.</p>
<p>If you have any question about it, please let me know.</p>
]]></description><link>https://joplinapp.org/news/20210804-085003/</link><guid isPermaLink="false">20210804-085003</guid><pubDate>Wed, 04 Aug 2021 08:50:03 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[New beta editor for the mobile app]]></title><description><![CDATA[<p>The <a href="https://github.com/laurent22/joplin-android/releases">latest Android pre-release 24</a> features an improved beta editor, which I hope could become a replacement for the very basic editor we have at the moment.</p>
<p>It's still experimental because it's based on the equally experimental CodeMirror 6, however for simple editing tasks it seems to work fine. At the moment the improvements are:</p>
<p>- Syntax highlighting for various tags such as bold, italic and headings.</p>
<p>- List continuation for ordered and unordered lists (I didn't try checklists but I assume it doesn't work)</p>
<p>- Improved undo/redo</p>
<p>- Maybe better handling of large documents? CodeMirror 6 has a demo that loads a document with millions of lines, so maybe that will solve the performance issues that some users were having</p>
<p>If everything works well, later on we should be able to add things like a toolbar, spellchecking and other features that are impossible with the current editor.</p>
<p>If you find any bug, feel free to report here. Also make sure you backup your notes regularly in case there's an issue!</p>
]]></description><link>https://joplinapp.org/news/20210729-103234/</link><guid isPermaLink="false">20210729-103234</guid><pubDate>Thu, 29 Jul 2021 10:32:34 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[The Jopin Cloud beta is now closed]]></title><description><![CDATA[<p>The beta program helped narrow down a few issues and should make Joplin Cloud (and Joplin Server) more reliable. More precisely:</p>
<ul>
<li>
<p>About 7 bugs have been fixed, including two major ones regarding sharing, and one security issue.</p>
</li>
<li>
<p>About a dozen improvements, new features and optimisations have been added following your feedback.</p>
</li>
</ul>
<p>As promised if you have a beta account you can keep using it and it will remain free for the three months after the account was created. After that, you will receive a link to start the Stripe subscription if you wish to keep using the account.</p>
<p>If you have sent me an email before the end of the beta and I didn't reply yet, I will do so soon, and will send you the confirmation email.</p>
<p>Thanks everyone for participating!</p>
]]></description><link>https://joplinapp.org/news/20210718-103538/</link><guid isPermaLink="false">20210718-103538</guid><pubDate>Sun, 18 Jul 2021 10:35:38 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[New website is ready!]]></title><description><![CDATA[<p>The new website is finally ready at <a href="https://joplinapp.org">https://joplinapp.org</a></p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210711-095626_0.png" alt=""></p>
<p>The previous website had been built organically over the past few years. It had a lot of useful content but finding your way was tricky and, for new users, it wasn't clear what Joplin was about. Finding out how to install the app wasn't obvious since the download buttons were lost in the clutter of information.</p>
<p>So the new website includes a front page with clear goals:</p>
<ul>
<li>
<p>Allows people to easily download the app - for that there's a large Download button at the top and bottom of the page. It redirects to a page that automatically picks the version based on your operating system.</p>
</li>
<li>
<p>Showcase the application key features. The <a href="https://discourse.joplinapp.org/t/what-are-the-key-features-of-joplin/5837">key features post</a> on the forum helped narrow down what Joplin is about, so there are sections about the web clipper, the open source nature of the app, encryption, synchronisation, customisation and the ability to create multimedia notes.</p>
</li>
<li>
<p>The top screenshots have also been updated (the previous one was showing a dev version from 2016, before the app was even released). As a nod to Scott Joplin, the screenshot shows an imaginary plan to open a vintage piano store, with various tasks, tables, documents and images attached, to showcase Joplin features.</p>
</li>
<li>
<p>Finally there's a Press section, which includes extracts from some cool articles that have been written about the app.</p>
</li>
</ul>
<p>Also many thanks to everyone who voted and contributed to the tagline discussion! It helped narrow down what the tagline should be, along with the equally important description below. If you have any question or notice any issue with the website let me know!</p>
]]></description><link>https://joplinapp.org/news/20210711-095626/</link><guid isPermaLink="false">20210711-095626</guid><pubDate>Sun, 11 Jul 2021 09:56:26 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Poll: What should Joplin tagline be?]]></title><description><![CDATA[<p>Thanks everyone for your tagline suggestions - there were lots of good ideas in there. I've compiled a few of them and create a poll in the forum, so please cast your vote! And if you have any other suggestions on what would make a good tagline, feel free to post over there or here.</p>
<p><a href="https://discourse.joplinapp.org/t/poll-what-should-joplin-tagline-be/18487">https://discourse.joplinapp.org/t/poll-what-should-joplin-tagline-be/18487</a></p>
]]></description><link>https://joplinapp.org/news/20210706-140228/</link><guid isPermaLink="false">20210706-140228</guid><pubDate>Tue, 06 Jul 2021 14:02:28 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Any ideas for a Joplin tagline?]]></title><description><![CDATA[<p>I'm going to update the website front page to better showcase the application. I have most of the sections right, but the part I'm still not sure about is the top tagline, so I'm wondering if anyone had any suggestion about it?</p>
<p>From what I can see on Google Keep or Evernote for example it should be something like &quot;Use our app to get X or Y benefit&quot;, it should be a sentence that directly speaks to the user essentially.</p>
<p>So far I have &quot;Your notes, anywhere you are&quot; but I'm not certain that's particularly inspiring. Any other idea about what tagline could be used?</p>
]]></description><link>https://joplinapp.org/news/20210705-094247/</link><guid isPermaLink="false">20210705-094247</guid><pubDate>Mon, 05 Jul 2021 09:42:47 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Poll: What's the size of your note collection?]]></title><description><![CDATA[<p>Poll is on the forum:</p>
<p><a href="https://discourse.joplinapp.org/t/poll-whats-the-size-of-your-note-collection/18191">https://discourse.joplinapp.org/t/poll-whats-the-size-of-your-note-collection/18191</a></p>
]]></description><link>https://joplinapp.org/news/20210624-171844/</link><guid isPermaLink="false">20210624-171844</guid><pubDate>Thu, 24 Jun 2021 17:18:44 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{> gtmHead}}
{{> gaOptimize}}
<meta
charset="utf-8"
@@ -12,6 +13,7 @@
<meta name="theme-color" content="#000000" />
<link rel="stylesheet" href="{{{assetUrls.css.fontawesome}}}">
{{> openGraphTags}}
{{> rssFeedLink}}
<link
rel="stylesheet"
href="{{cssBaseUrl}}/bootstrap5.0.2.min.css"
@@ -33,6 +35,8 @@
<title>Joplin</title>
</head>
<body class="front-page website-env-{{env}}">
{{> gtmBody}}
<div class="container-fluid" id="main-container">
{{#navbar}}

View File

@@ -14,6 +14,7 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
-->
<head>
{{> gtmHead}}
{{> gaOptimize}}
<meta
charset="utf-8"
@@ -24,6 +25,7 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
{{> openGraphTags}}
{{> rssFeedLink}}
<link
rel="stylesheet"
href="{{cssBaseUrl}}/bootstrap5.0.2.min.css"
@@ -47,6 +49,8 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
></script>
</head>
<body class="website-env-{{env}}">
{{> gtmBody}}
<div class="container-fluid generic-template {{pageName}}-page" id="main-container">
{{#navbar}}

View File

@@ -1,5 +1,7 @@
<!-- Donate button A/B testing -->
<!-- Monthly/Yearly plan A/B testing -->
<script src="https://www.googleoptimize.com/optimize.js?id=OPT-PW3ZPK3"></script>
<!-- Donate button A/B testing -->
<!--
<script async src="https://www.googleoptimize.com/optimize.js?id=OPT-PW3ZPK3"></script>
-->

View File

@@ -0,0 +1,4 @@
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-579DTGX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

View File

@@ -0,0 +1,7 @@
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-579DTGX');</script>
<!-- End Google Tag Manager -->

View File

@@ -0,0 +1 @@
<link rel="alternate" type="application/rss+xml" title="Joplin RSS feed" href="https://joplinapp.org/rss.xml" />

107
README.md
View File

@@ -28,11 +28,11 @@ Three types of applications are available: for **desktop** (Windows, macOS and L
Operating System | Download
---|---
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-Setup-2.7.15.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-2.7.15.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-2.7.15.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.8.8/Joplin-Setup-2.8.8.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.8.8/Joplin-2.8.8.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.8.8/Joplin-2.8.8.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.8.8/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Linux**, the recommended way is to use the following installation script as it will handle the desktop icon too:
@@ -42,7 +42,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/J
Operating System | Download | Alt. Download
---|---|---
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.7.2/joplin-v2.7.2.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.7.2/joplin-v2.7.2-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.8.1/joplin-v2.8.1.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.8.1/joplin-v2.8.1-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -70,7 +70,7 @@ A community maintained list of these distributions can be found here: [Unofficia
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-github&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a>
<!-- SPONSORS-ORG -->
* * *
@@ -83,8 +83,8 @@ A community maintained list of these distributions can be found here: [Unofficia
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/77214738?s=96&v=4"/></br>[Polymathic-Company](https://github.com/Polymathic-Company) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | |
<!-- SPONSORS-GITHUB -->
<!-- TOC -->
@@ -132,13 +132,14 @@ A community maintained list of these distributions can be found here: [Unofficia
- [How to build the apps](https://github.com/laurent22/joplin/blob/dev/BUILD.md)
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
- [End-to-end encryption spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
- [Search Sorting spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/search_sorting.md)
- [E2EE: Technical spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [E2EE: Workflow](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee/workflow.md)
- [Server: File URL Format](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_file_url_format.md)
- [Server: Delta Sync](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_delta_sync.md)
- [Server: Sharing](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_sharing.md)
@@ -152,6 +153,8 @@ A community maintained list of these distributions can be found here: [Unofficia
- About
- [Changelog (Desktop App)](https://github.com/laurent22/joplin/blob/dev/readme/changelog.md)
- [Changelog (Android)](https://github.com/laurent22/joplin/blob/dev/readme/changelog_android.md)
- [Changelog (iOS)](https://github.com/laurent22/joplin/blob/dev/readme/changelog_ios.md)
- [Changelog (CLI App)](https://github.com/laurent22/joplin/blob/dev/readme/changelog_cli.md)
- [Changelog (Server)](https://github.com/laurent22/joplin/blob/dev/readme/changelog_server.md)
- [Stats](https://github.com/laurent22/joplin/blob/dev/readme/stats.md)
@@ -184,6 +187,7 @@ A community maintained list of these distributions can be found here: [Unofficia
- Custom CSS support for customisation of both the rendered markdown and overall user interface.
- Customisable layout allows toggling, movement and sizing of various elements.
- Keyboard shortcuts are editable and allow binding of most Joplin commands with export/import functionality.
- Multiple profile support.
# Importing
@@ -416,9 +420,10 @@ Important: userstyle.css and userchrome.css are provided for your convenience, b
# Plugins
The **desktop app** has the ability to extend beyond its standard functionality by the way of plugins. These plugins adhere to the Joplin plugin API and can be installed & configured within the application via the `Plugins` page in the [Configuration screen](https://github.com/laurent22/joplin/blob/dev/readme/config_screen.md). This menu allows the manual installation of the plugin using the single 'Joplin Plugin Archive' (*.jpl) file. Once the application is reloaded the plugins will appear within the plugins menu where they can be toggled on/off or removed entirely.
The **desktop app** has the ability to extend beyond its standard functionality by the way of plugins. These plugins adhere to the Joplin [plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html) and can be installed & configured within the application via the `Plugins` page of the [Configuration screen](https://github.com/laurent22/joplin/blob/dev/readme/config_screen.md).
Plugins are currently maintained by the community in the [Joplin Discourse 'plugins' category](https://discourse.joplinapp.org/c/plugins/18).
From this menu you can search for plugins uploaded to the [Joplin plugins](https://github.com/joplin/plugins) repository as well as manual installation of plugins using a 'Joplin Plugin Archive' (*.jpl) file.
Once the application is reloaded the plugins will appear within the plugins menu where they can be toggled on/off or removed entirely.
For more information see [Plugins](https://github.com/laurent22/joplin/blob/dev/readme/plugins.md)
@@ -448,7 +453,7 @@ You can also use search filters to further restrict the search.
| Operator | Description | Example |
| --- | --- | --- |
|**-**|If placed before a text term, it excludes the notes that contain that term. You can also place it before a filter to negate it. |`-spam` searches for all notes without the word `spam`.<br>`office -trash` searches for all notes with the word`office` and without the word `trash`.|
|**-**|If placed before a text term, it excludes the notes that contain that term. You can also place it before a filter to negate it. |`-spam` searches for all notes without the word `spam`.<br>`office -trash` searches for all notes with the word `office` and without the word `trash`.|
|**any:**|Return notes that satisfy any/all of the required conditions. `any:0` is the default, which means all conditions must be satisfied.|`any:1 cat dog` will return notes that have the word `cat` or `dog`.<br>`any:0 cat dog` will return notes with both the words `cat` and `dog`. |
| **title:** <br> **body:**|Restrict your search to just the title or the body field.|`title:"hello world"` searches for notes whose title contains `hello` and `world`.<br>`title:hello -body:world` searches for notes whose title contains `hello` and body does not contain `world`.
| **tag:** |Restrict the search to the notes with the specified tags.|`tag:office` searches for all notes having tag office.<br>`tag:office tag:important` searches for all notes having both office and important tags.<br>`tag:office -tag:spam` searches for notes having tag `office` which do not have tag `spam`.<br>`any:1 tag:office tag:spam` searches for notes having tag `office` or tag `spam`.<br>`tag:be*ful` does a search with wildcards.<br>`tag:*` returns all notes with tags.<br>`-tag:*` returns all notes without tags.|
@@ -480,6 +485,16 @@ Notes are sorted by "relevance". Currently it means the notes that contain the r
In the desktop application, press <kbd>Ctrl+P</kbd> or <kbd>Cmd+P</kbd> and type a note title or part of its content to jump to it. Or type <kbd>#</kbd> followed by a tag name, or <kbd>@</kbd> followed by a notebook name.
# Multiple profile support
To create a new profile, open File > Switch profile and select Create new profile, enter the profile name and press OK. The app will automatically switch to this new profile, which you can now configure.
To switch back to the previous profile, again open File > Switch profile and select Default.
Note that profiles all share certain settings, such as language, font size, theme, etc. This is done so that you don't have reconfigure every details when switching profiles. Other settings such as sync configuration is per profile.
The feature is available on desktop only for now, and should be ported to mobile relatively soon.
# Donations
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
@@ -520,47 +535,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 90%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 26%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 65%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 51%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 89%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 25%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 64%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 50%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 86%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pthrp_bnsrv@aleeas.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 50%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 49%
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 29%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | miucci | 92%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 98%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 28%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 33%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 76%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 83%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | Alexander Dawson | 88%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 32%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 86%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 86%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 62%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Felipe Viggiano](mailto:felipeviggiano@gmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 57%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 41%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [horaceyoung](mailto:yonghaoharry@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 80%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 72%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [horaceyoung](mailto:paventyang@gmail.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 86%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

953
cspell.json Normal file
View File

@@ -0,0 +1,953 @@
{
"version": "0.2",
"language": "en_GB",
"ignorePaths": [
"**/*.min.*",
"**/*.svg",
"/_mydocs",
"/_releases",
"/.git",
"/.yarn",
"/Assets",
"/packages/app-cli/app/fuzzing.js",
"/packages/app-cli/build",
"/packages/app-cli/tests/support",
"/packages/app-cli/tests/test data",
"/packages/app-cli/tests/tmp",
"/packages/app-clipper/content_scripts/JSDOMParser.js",
"/packages/app-clipper/content_scripts/Readability-readerable.js",
"/packages/app-clipper/content_scripts/Readability.js",
"/packages/app-clipper/popup/build/js/0.chunk.js",
"/packages/app-clipper/popup/build/js/bundle.js",
"/packages/app-clipper/popup/build/js/main.chunk.js",
"/packages/app-clipper/popup/build/js/main.chunk.js",
"/packages/app-clipper/popup/config",
"/packages/app-desktop/vendor/",
"/packages/app-mobile/pluginAssets",
"/packages/fork-sax/examples",
"/packages/fork-sax/lib/sax.js",
"/packages/fork-sax/test",
"/packages/fork-uslug",
"/packages/lib/locales",
"/packages/lib/mime-utils-types.js",
"/packages/lib/parameters.js",
"/packages/lib/plugin_types",
"/packages/lib/resourceUtils.js",
"/packages/lib/services/joplinServer/personalizedUserContentBaseUrl.ts",
"/packages/lib/vendor",
"/packages/lib/welcomeAssets.js",
"/packages/turndown-plugin-gfm/config",
"/packages/turndown/config",
"node_modules"
],
"words": [
"aàáâãäåāą",
"AÀÁÂÃÄÅĀĄ",
"abbrev",
"ABCDEFGHIJ",
"Abhishek",
"Abkhazian",
"accel",
"accum",
"actualkeyword",
"adata",
"advlist",
"AGSFE",
"Aland",
"Åland",
"alertbanner",
"Allaire",
"alse",
"altool",
"aman",
"ambrt",
"Amharic",
"amothc",
"andrejilderda",
"anki",
"Antarctique",
"antarctiques",
"Antártico",
"anymore",
"apidoc",
"appiconset",
"applewebkit",
"approot",
"arableague",
"Aragonés",
"ARITIM",
"armeabi",
"asterix",
"atest",
"atestb",
"attribname",
"attribvalue",
"authcode",
"autocompleteitem",
"autocompleter",
"Autocompleter",
"AUTOEXEC",
"autohide",
"Avenir",
"Ayiti",
"azamah",
"Azərbaycan",
"backoff",
"Bangla",
"Bêafrîka",
"beforeinput",
"België",
"Belgien",
"Belgique",
"Bénin",
"Bhutani",
"bibtex",
"Bihari",
"Bislama",
"blabla",
"blablablabla",
"boohay",
"Bosna",
"Bouvet",
"Bouvetøya",
"browserslist",
"bthqu",
"btns",
"Bulibiya",
"bullist",
"bulma",
"Byelorussian",
"calebjohn",
"Calédonie",
"Caligraphic",
"callsites",
"Cameroun",
"cantdothat",
"Capslock",
"cardcontainer",
"cardimage",
"cardmenuitem",
"cardtext",
"Caribisch",
"CAUTOEXEC",
"cçćč",
"CÇĆČ",
"cdataend",
"cdatastart",
"cdot",
"ceaf",
"centrafricaine",
"Centrafrican",
"Česká",
"changedtitle",
"charcodes",
"checkboxclick",
"checkmark",
"chemfive",
"choiceitem",
"chromedriver",
"chromeframe",
"chromeos",
"Città",
"Cmds",
"codepoint",
"colorinput",
"colorpicker",
"colorswatch",
"colspan",
"committerdate",
"commmmmand",
"commonmark",
"COMMONMARK",
"Comores",
"compositionend",
"compositionstart",
"compositionupdate",
"conflicter",
"contenteditable",
"contextform",
"contextformbutton",
"contextformtogglebutton",
"contextkey",
"contexttoolbar",
"continuelist",
"Contrl",
"Conv",
"convo",
"copytags",
"cozic",
"Cozic",
"createdb",
"Creds",
"Crna",
"cronspec",
"cros",
"crypted",
"Curaçao",
"curso",
"customeditor",
"customkeymap",
"cyingfan",
"d'Ivoire",
"Danmark",
"Dansk",
"dataimg",
"datauri",
"Datauri",
"datetime",
"Datetime",
"davris",
"dbuuid",
"DDTHH",
"deflist",
"deinit",
"Démocratique",
"deselector",
"deuxième",
"dflt",
"dialogbox",
"dialogs",
"Dialogs",
"DIALOGS",
"Distill",
"dists",
"docid",
"docsize",
"doctypes",
"doesnotwork",
"doesntexist",
"doesntlookright",
"domelementtype",
"domhandler",
"Dominicana",
"domutils",
"DONATELINKS",
"downarrow",
"dragdrop",
"draggesture",
"dünn",
"dylib",
"dynamiclib",
"ecuatorial",
"eèéêëěēę",
"EÈÉÊËĚĒĘ",
"Eesti",
"effet",
"efgh",
"égalité",
"Éire",
"elem",
"elementpath",
"elems",
"ellipsize",
"ELOCKED",
"encryptable",
"endregion",
"enex",
"Enex",
"ENEX",
"enumber",
"eqeqeq",
"équatoriale",
"Erro",
"errorish",
"escapeplus",
"eslintignore",
"España",
"étiquette",
"EUNSPECIFIED",
"eventname",
"evermeet",
"evernote",
"Evernote",
"execa",
"expando",
"expirable",
"Expirable",
"expval",
"Færøerne",
"Fahrräder",
"FAILSAFE",
"fallbacks",
"fancymenuitem",
"fancytype",
"favorites",
"Fiber",
"filepicker",
"folderid",
"foldl",
"fontawesome",
"fontface",
"forall",
"forcewake",
"Føroyar",
"fortawesome",
"française",
"françaises",
"Gabuutih",
"gedit",
"geoip",
"Geoip",
"geoloc",
"geoplugin",
"getlastmodified",
"gettext",
"githubusercontent",
"Gora",
"gotchas",
"gradlew",
"Grønland",
"grouptoolbarbutton",
"Gruber",
"gsoc",
"gttest",
"Guåhån",
"guarentee",
"guarentees",
"Guiena",
"Guiné",
"Guinée",
"gulpfile",
"Guyane",
"gvim",
"Haïti",
"hanlder",
"Hausa",
"headerless",
"Heisenbug",
"Hercegovina",
"hift",
"highjack",
"highlited",
"historyhas",
"HMRKG",
"hoge",
"homenote",
"hotfolder",
"Howver",
"Hrvatska",
"htmlentities",
"htmlfile",
"htmlpack",
"htmlpanel",
"ʻĀirani",
"icns",
"iconset",
"iconutil",
"Iforgot",
"iframes",
"ihack",
"iife",
"iìíîïī",
"IÌÍÎÏĪ",
"ijkl",
"imagelink",
"imagetools",
"immer",
"iname",
"Incl",
"infint",
"inflim",
"infty",
"inputi",
"inserttable",
"Interlingue",
"Interp",
"interupting",
"Inteval",
"Inuktitut",
"Inupiak",
"Invididual",
"IOERR",
"Ionicons",
"IPHONEOS",
"ipify",
"ipwhois",
"iscompleted",
"Ísland",
"Italiano",
"Itoophiyaa",
"itsgone",
"itunes",
"Jabuuti",
"jackgruber",
"joeattardi",
"jopext",
"joplinapp",
"JOPLINAPP",
"joplincloud",
"joplindev",
"JOPLINMOD",
"joplintest",
"jsbundles",
"justtesting",
"Kalaallit",
"kalba",
"kanban",
"Kashmiri",
"katex",
"keychain",
"keycodes",
"keymaps",
"keytar",
"Kibris",
"Kinyarwanda",
"Kirundi",
"Ködörösêse",
"Komori",
"Kpck",
"Kūki",
"Laothian",
"lastmod",
"Latvija",
"lcov",
"leaft",
"leftarrow",
"leftequilibrium",
"leftrightarrow",
"Lettish",
"Lëtzebuerg",
"Levithan",
"Liban",
"libz",
"Lietuva",
"Lietuvių",
"lineheight",
"Lingala",
"linkg",
"linkurl",
"listbox",
"listfile",
"listpreview",
"loglevel",
"longclick",
"longpress",
"longpresscancel",
"looooooong",
"ltrim",
"Luxemburg",
"Maarten",
"Madagasikara",
"Magyarország",
"majax",
"Mardown",
"markdowncalc",
"Maroc",
"MASTERKEY",
"matchinfo",
"mathchoice",
"mathjax",
"Mathjax",
"MATHJAX",
"mathllap",
"mathml",
"mathrlap",
"mathrm",
"Mauritanie",
"Maxiumm",
"Mayen",
"mchem",
"mechanim",
"mediumtext",
"menubutton",
"mergeff",
"Metadatas",
"México",
"mhchem",
"middlewares",
"migth",
"mkbook",
"MKCOL",
"mkdirp",
"mknote",
"mktodo",
"MMYY",
"modifié",
"monokai",
"MONOSPACE",
"msgctxt",
"msgfmt",
"msgmerge",
"msgstr",
"msleep",
"mtext",
"mult",
"multicursor",
"multimarkdown",
"multimd",
"multistatus",
"multitable",
"mybucket",
"mydir",
"myfile",
"mynote",
"myplugin",
"mytag",
"mytaga",
"mytagb",
"mytagc",
"mytagd",
"mytest",
"mytoken",
"myvalue",
"nanoid",
"Neaus",
"Nederlands",
"nestedmenuitem",
"newone",
"Nextcloud",
"njstrace",
"nñňń",
"NÑŇŃ",
"NOCASE",
"nodechange",
"nodir",
"noexpand",
"nojs",
"nolongershared",
"nonlatin",
"NONLATIN",
"Noreg",
"Norge",
"nospecialcharacters",
"notanumber",
"notarization",
"notetags",
"Notif",
"notindexed",
"notthere",
"nounce",
"Nounce",
"Nounces",
"npmignore",
"numadd",
"numbersareok",
"numdec",
"numdiv",
"numlist",
"Numlock",
"nummult",
"numsub",
"Nunaat",
"obelix",
"odata",
"ohno",
"OHNO",
"oldppk",
"onattribdata",
"onattribend",
"onattribname",
"onattribute",
"oncdata",
"oncdataend",
"oncdatastart",
"onclosetag",
"oncomment",
"oncommentend",
"ondeclaration",
"onedrive",
"onelink",
"onformat",
"onmatch",
"onopentag",
"onopentagend",
"onopentagname",
"onparserinit",
"onprocessinginstruction",
"onselfclosingtag",
"ontext",
"oòóôõöøō",
"OÒÓÔÕÖØŌ",
"opentag",
"opentagname",
"Opptionn",
"orignal",
"Oromo",
"Österreich",
"otherpackage",
"outdented",
"overidding",
"overriden",
"padd",
"pandoc",
"paperclip",
"passthrough",
"Päth",
"Pbuild",
"pbxproj",
"pcmag",
"pcnalx",
"pddv",
"Pehr",
"Percents",
"père",
"Perú",
"pfff",
"PGPASSWORD",
"pidfile",
"PLUGINLEGACY",
"pocount",
"Polska",
"Polski",
"Polynésie",
"Português",
"Potoczny's",
"powerpoint",
"Prakash",
"precommit",
"pred",
"preg",
"prerelease",
"Prerelease",
"presigner",
"prettycron",
"pricetag",
"Príncipe",
"privkey",
"processinginstruction",
"programatically",
"propfind",
"PROPFIND",
"propname",
"propstat",
"protcol",
"pseudoclass",
"pseudos",
"Pushto",
"quot",
"qwer",
"raisebox",
"rbga",
"readerable",
"Readerable",
"READERABLE",
"Redownload",
"reencrypt",
"reencrypted",
"Reencrypting",
"reencrypts",
"regexes",
"Regexs",
"Relavent",
"relayouted",
"rels",
"renamings",
"renderered",
"Renderered",
"República",
"republika",
"République",
"requestheaders",
"resourcetype",
"resynced",
"Rhaeto",
"rightarrow",
"rightequilibrium",
"rightleftarrows",
"rightleftharpoons",
"rmbook",
"rmnote",
"rmusin",
"rnfs",
"RNFS",
"robocopy",
"Roboto",
"România",
"roule",
"rowid",
"ROWID",
"ROWIDs",
"rowspan",
"rseidelsohn",
"rtrim",
"safeext",
"salut",
"Sangho",
"sasss",
"SAVEPOINT",
"schtroumpf",
"Schweiz",
"Scrolllock",
"scrollmap",
"seafdav",
"Seafile",
"searchengine",
"searchlimit",
"SEARCHOVERLAY",
"securerandom",
"segdir",
"selectbox",
"Sénégal",
"Serializers",
"setext",
"settingschema",
"shantanugoel",
"sharee",
"Shiftt",
"Shoft",
"shouldntendwithit",
"shouldstartwiththis",
"Shqip",
"Shqipëria",
"Sicen",
"simplemath",
"Siswati",
"sizeinput",
"SJCL",
"Slovenčina",
"Slovenija",
"Slovensko",
"softbreaks",
"Solarised",
"SOLARIZED",
"someid",
"somewhereelse",
"sourceurl",
"SPACEBAR",
"spaceno",
"Spacify",
"spdfgh",
"spellfix",
"sphemy",
"splitbutton",
"sprintf",
"sqlts",
"srcfolder",
"SSSZ",
"starttls",
"Starttls",
"stepsize",
"stevenlevithan",
"stex",
"stilltryingtohack",
"strack",
"Stringifiable",
"subdir",
"subl",
"Suomi",
"Sūriyya",
"Svenska",
"Sverige",
"svgs",
"Svizra",
"Svizzera",
"synclock",
"synclog",
"syswide",
"syswidecas",
"taboverride",
"tabpanel",
"taga",
"tagb",
"tagc",
"Tajik",
"takesover",
"targetfolder",
"Tchad",
"Teardown",
"termi",
"termutils",
"Terres",
"Tessarek",
"tessus",
"Testb",
"testcreate",
"testexportfolder",
"testingconnection",
"testingkeychain",
"testunit",
"texify",
"Texify",
"textareas",
"textexportnote",
"textstyle",
"thaaaaaaan",
"thatsok",
"thatsreallylongthatsreallylongthatsreallylongthats",
"thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong",
"Thevenard",
"thisisfine",
"Thmmss",
"Tiếng",
"Tigrinya",
"tiiitlllle",
"tinymce",
"titi",
"titletitle",
"tkwidget",
"tkwidgets",
"Todos",
"togglebutton",
"togglemenuitem",
"toolip",
"tooshort",
"treymo",
"tripledash",
"tsmerge",
"Tsonga",
"tttest",
"Tunisie",
"Türkçe",
"Türkiye",
"Türkmenistan",
"turndown",
"Turndown",
"TWCO",
"typeahead",
"Typora",
"tzip",
"uastring",
"uglifycss",
"uglifyjs",
"Uighur",
"unconflicted",
"underbrace",
"Undos",
"unescaping",
"unhighlighted",
"unixlike",
"unmocked",
"unserialize",
"unserialized",
"unserializes",
"unserializing",
"unshares",
"unsharing",
"unusued",
"uparrow",
"uphy",
"urlconverter",
"urlinput",
"userchrome",
"usercontent",
"userstyle",
"uslug",
"Ustd",
"utems",
"uuidgen",
"uuidv",
"uùúûüůū",
"UÙÚÛÜŮŪ",
"valign",
"Valign",
"vars",
"Vars",
"Vaticano",
"vers",
"verylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongidverylongid",
"veryverylongclientidveryverylongclientidveryverylongclientidveryverylongclientid",
"veryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitleveryverylongtitle",
"Việt",
"viewbox",
"Volapuk",
"Volívia",
"votearrow",
"webclipper",
"webdav",
"webfonts",
"whitespaces",
"widder",
"Wifi",
"Withflags",
"Wolof",
"WONTFIX",
"wrongclienttype",
"wrongpassword",
"wrongtype",
"Wuliwya",
"wxyz",
"xcallbackurl",
"xcassets",
"xcodeproj",
"xcrun",
"xgettext",
"xlink",
"xzvf",
"yarg",
"yosay",
"yufzkns",
"YYYYMMDDTHH",
"zxcvbn",
"zžżź",
"ZŽŻŹ",
"Ελλάδα",
"Ελληνικά",
"Κύπρος",
"Агентство",
"Антарктике",
"Беларусь",
"България",
"Гора",
"језик",
"Казахстан",
"Киргизия",
"Книги",
"Кыргызстан",
"Қазақстан",
"Македонија",
"Молдавия",
"Монгол",
"номер",
"рейтер",
"Рейтер",
"Россия",
"Русский",
"Северна",
"сообщило",
"СООБЩИЛО",
"Србија",
"српски",
"Україна",
"Црна",
"საქართველო",
"Հայաստան",
"ישראל",
"עיברית",
"إرتريا",
"اسلامي",
"اسلامی",
"افغانستان",
"الأُرْدُن",
"الإمارات",
"البحرين",
"الجزائر",
"السعودية",
"السودان",
"الصومال",
"العراق",
"العربيّة",
"ﺍﻟﻘﻤﺮي",
"الكويت",
"المتّحدة",
"المغرب",
"اليَمَن",
"ایران",
"پاکستان",
"تشاد",
"تونس",
"جمهوری",
"جيبوتي",
"دولة",
"دولتدولت",
"سلطنة",
"سوريا",
"عُمان",
"لبنان",
"ليبيا",
"موريتانيا",
"ⵍⵎⵖⵔⵉⴱ",
"ኢትዮጵያ",
"ኤርትራ",
"भारत",
"গণপ্রজাতন্ত্রী",
"লাদেশ",
"இலங்கை",
"ලංකා",
"คือค",
"คือคนไทย",
"ประเทศไทย",
"ປະຊາຊົນລາວ",
"မြန်မာ",
"កម្ពុជា"
]
}

View File

@@ -12,6 +12,8 @@ module.exports = {
// '**/*.ts?(x)': () => 'npm run tsc',
'*.{js,jsx,ts,tsx}': [
'yarn run linter-precommit',
'yarn run checkLibPaths',
// 'yarn run spellcheck',
'git add',
],
};

View File

@@ -22,6 +22,7 @@
"buildSettingJsonSchema": "yarn workspace joplin start settingschema ../../../joplin-website/docs/schema/settings.json",
"buildTranslations": "node packages/tools/build-translation.js",
"buildWebsite": "node ./packages/tools/website/build.js && yarn run buildPluginDoc && yarn run buildSettingJsonSchema",
"checkLibPaths": "node ./packages/tools/checkLibPaths.js",
"circularDependencyCheck": "madge --warning --circular --extensions js ./",
"clean": "npm run clean --workspaces --if-present && node packages/tools/clean && yarn cache clean",
"dependencyTree": "madge",
@@ -41,6 +42,8 @@
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
"releasePluginRepoCli": "node packages/tools/release-plugin-repo-cli.js",
"releaseServer": "node packages/tools/release-server.js",
"cspell": "cspell",
"spellcheck": "node packages/tools/spellcheck.js",
"tagServerLatest": "node packages/tools/tagServerLatest.js",
"buildServerDocker": "node packages/tools/buildServerDocker.js",
"setupNewRelease": "node ./packages/tools/setupNewRelease",
@@ -60,6 +63,7 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.6.0",
"cspell": "^5.20.0",
"eslint": "^7.6.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-react": "^7.18.0",

View File

@@ -317,12 +317,12 @@ async function fetchAllNotes() {
lines.push('');
lines.push('\tcurl -X PUT -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my modified title"}\' http://localhost:41184/resources/8fe1417d7b184324bf6b0122b76c4696');
lines.push('');
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
lines.push('');
lines.push('Or if you only need to update the resource properties (title, etc.), without changing the content, you can make a regular PUT request:');
lines.push('');
lines.push('\tcurl -X PUT --data \'{"title": "My new title"}\' http://localhost:41184/resources/8fe1417d7b184324bf6b0122b76c4696');
lines.push('');
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
lines.push('');
lines.push('**From a plugin** the syntax to create a resource is also a bit special:');
lines.push('');
lines.push('```javascript');

View File

@@ -33,7 +33,7 @@
],
"owner": "Laurent Cozic"
},
"version": "2.8.0",
"version": "2.8.1",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"

View File

@@ -60,16 +60,20 @@ describe('MdToHtml', function() {
actualHtml = actualHtml.replace(/\r?\n/g, '\n');
if (actualHtml !== expectedHtml) {
console.info('');
console.info(`Error converting file: ${mdFilename}`);
console.info('--------------------------------- Got:');
console.info(actualHtml);
console.info('--------------------------------- Raw:');
console.info(actualHtml.split('\n'));
console.info('--------------------------------- Expected:');
console.info(expectedHtml.split('\n'));
console.info('--------------------------------------------');
console.info('');
const msg: string[] = [
'',
`Error converting file: ${mdFilename}`,
'--------------------------------- Got:',
actualHtml,
'--------------------------------- Raw:',
actualHtml.split('\n'),
'--------------------------------- Expected:',
expectedHtml.split('\n'),
'--------------------------------------------',
'',
];
console.info(msg.join('\n'));
expect(false).toBe(true);
// return;

View File

@@ -0,0 +1 @@
<iframe src=""><svg><style><img src="" onerror=this.onerror=confirm('vulnerable_to_XSS')

View File

@@ -334,6 +334,12 @@ export enum SettingItemType {
Button = 6,
}
export enum SettingItemSubType {
FilePathAndArgs = 'file_path_and_args',
FilePath = 'file_path', // Not supported on mobile!
DirectoryPath = 'directory_path', // Not supported on mobile!
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
@@ -350,6 +356,7 @@ export enum SettingStorage {
export interface SettingItem {
value: any;
type: SettingItemType;
subType?: SettingItemSubType;
label: string;
description?: string;

View File

@@ -1,5 +1,5 @@
import joplin from 'api';
import { SettingItemType, ToolbarButtonLocation } from 'api/types';
import { SettingItemSubType, SettingItemType, ToolbarButtonLocation } from 'api/types';
joplin.plugins.register({
onStart: async function() {
@@ -49,6 +49,33 @@ joplin.plugins.register({
description: 'This setting will be saved to settings.json',
['storage' as any]: 2, // Should be `storage: SettingStorage.File`
},
'myFilePathAndArgs': {
value: '',
type: SettingItemType.String,
subType: SettingItemSubType.FilePathAndArgs,
section: 'myCustomSection',
public: true,
label: 'File path and args',
},
'myFilePathOnly': {
value: '',
type: SettingItemType.String,
subType: SettingItemSubType.FilePath,
section: 'myCustomSection',
public: true,
label: 'File path',
},
'myDirectory': {
value: '',
type: SettingItemType.String,
subType: SettingItemSubType.DirectoryPath,
section: 'myCustomSection',
public: true,
label: 'Directory path',
},
});
await joplin.commands.register({

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper [DEV]",
"version": "2.8.0",
"version": "2.8.1",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",

View File

@@ -192,7 +192,7 @@ export default class ElectronAppWrapper {
// We got the response from the renderer process:
// save the response and try quit again.
this.rendererProcessQuitReply_ = args;
this.electronApp_.quit();
this.quit();
}
});
@@ -253,7 +253,7 @@ export default class ElectronAppWrapper {
});
}
async quit() {
quit() {
this.electronApp_.quit();
}
@@ -325,7 +325,7 @@ export default class ElectronAppWrapper {
if (!gotTheLock) {
// Another instance is already running - exit
this.electronApp_.quit();
this.quit();
return true;
}
@@ -362,7 +362,7 @@ export default class ElectronAppWrapper {
});
this.electronApp_.on('window-all-closed', () => {
this.electronApp_.quit();
this.quit();
});
this.electronApp_.on('activate', () => {

View File

@@ -241,7 +241,7 @@ class Application extends BaseApplication {
const files = await shim.fsDriver().readDirStats(templatesDir);
for (const file of files) {
if (file.path.endsWith('.md')) {
// There is atleast one template.
// There is at least one template.
this.store().dispatch({
type: 'CONTAINS_LEGACY_TEMPLATES',
});
@@ -324,6 +324,18 @@ class Application extends BaseApplication {
}, 500);
}
public crashDetectionHandler() {
// This handler conflicts with the single instance behaviour, so it's
// not used for now.
// https://discourse.joplinapp.org/t/pre-release-v2-8-is-now-available-updated-27-april/25158/56?u=laurent
if (!Setting.value('wasClosedSuccessfully')) {
const answer = confirm(_('The application did not close properly. Would you like to start in safe mode?'));
Setting.setValue('isSafeMode', !!answer);
}
Setting.setValue('wasClosedSuccessfully', false);
}
public async start(argv: string[]): Promise<any> {
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
// insert an extra argument so that they can be processed in a consistent way everywhere.
@@ -331,6 +343,8 @@ class Application extends BaseApplication {
argv = await super.start(argv);
// this.crashDetectionHandler();
await this.applySettingsSideEffects();
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
@@ -354,8 +368,7 @@ class Application extends BaseApplication {
}
// Loads app-wide styles. (Markdown preview-specific styles loaded in app.js)
const filename = Setting.custom_css_files.JOPLIN_APP;
await injectCustomStyles('appStyles', `${dir}/${filename}`);
await injectCustomStyles('appStyles', Setting.customCssFilePath(Setting.customCssFilenames.JOPLIN_APP));
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
@@ -433,7 +446,7 @@ class Application extends BaseApplication {
});
// Loads custom Markdown preview styles
const cssString = await loadCustomCss(`${Setting.value('profileDir')}/userstyle.css`);
const cssString = await loadCustomCss(Setting.customCssFilePath(Setting.customCssFilenames.RENDERED_MARKDOWN));
this.store().dispatch({
type: 'CUSTOM_CSS_APPEND',
css: cssString,
@@ -522,8 +535,10 @@ class Application extends BaseApplication {
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
resourceService: ResourceService.instance(),
};
}
@@ -575,22 +590,6 @@ class Application extends BaseApplication {
// });
// }, 2000);
// const testData = {
// "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmKpb4JiYiY16pGOabje7uMsFd7DcMnruGxJ9HSpOiOduj3ApKqRu0xWCkGyqpekyOjjooZ98wVkDPUFsyVjN+kG8yKFn2xXC5SeRyhIVbdytjYiGshr6x+T9XVI+HnJKQF3WbrcqSOejlDXJv6u7jKrLAlOT3tkqEb0ZefhcEIajq6kNkH51R0lwsFnzxDIK3MW1wNzmiOfM92f8PFxiOBmUtVIngGPlNgyld1FzKN7Ypz1uS6GOqAtRm325qyfE/+2Jgb7WaDFT7VB5pHnOiojj9+xi1DvQWCbbIYXoMi0XVi9i2ZQfM32aFwiHez5UL61IMWUcqQ0/gldh4HFlAQIDAQAB\n-----END PUBLIC KEY-----",
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAmKpb4JiYiY16pGOabje7uMsFd7DcMnruGxJ9HSpOiOduj3ApKqRu0xWCkGyqpekyOjjooZ98wVkDPUFsyVjN+kG8yKFn2xXC5SeRyhIVbdytjYiGshr6x+T9XVI+HnJKQF3WbrcqSOejlDXJv6u7jKrLAlOT3tkqEb0ZefhcEIajq6kNkH51R0lwsFnzxDIK3MW1wNzmiOfM92f8PFxiOBmUtVIngGPlNgyld1FzKN7Ypz1uS6GOqAtRm325qyfE/+2Jgb7WaDFT7VB5pHnOiojj9+xi1DvQWCbbIYXoMi0XVi9i2ZQfM32aFwiHez5UL61IMWUcqQ0/gldh4HFlAQIDAQABAoIBADFFMffPZ9Nk7MLnPmz54cTnCPGzC63jDLuCAQ0LnWMDxiPW4AJaJUZMt+GioISBOWue+D1JOrsv3iLD3bcxyPBOjP33UYxcfpT0a1Ha+j2FriFygX4zxOIEnlyi8VdkLWCOqGj9BlGXKKzpmx4X76Sbbn9mt9+BGNm2vOUnaZcPTVuOI7K6xZynlzMRYSyhu7J0QdYVK44vZ/TjdD/4pgX+ezrGiwx7OCf/KctjvEoYtXYV2gkBOifOlqYOp0fMEC3mVAZfwpvDTbRchb7h0rxmxfKbWsjPtDblByXBLJZ3PGcKcmJlu4Qsfd2AgrY62r+DbNt3EhK072ZilYIfKD0CgYEAybcDbucr67dWMlFh5b79bvJugw6rj1V59Tp+RX9nKgzaiBUHLun6cK5hbgg9z3ejc2SWlX7D+eOyveVjhDlxUOCFURJLo2oPMRKwBBKJkOJhdtAjPzyceYI6Yj2lvtDeijcZfg8F9YqUTMfisDsEi1MbGnqawWwUerN9P5TjRBcCgYEAwcAfw8KTnQsvXPwWwh6Wabtz0bUAKzA/D6oWTR5IbkBfb3jNU8lmh9H66H0P18Nsa3vozA6buW2LDhHCFFkQ4PUTQVKok1qhAsvJBECxdwMqb5iAXk3Yk3qQYGhR23Zkp1u82wmpSaBLKGr+SL9/q5EamqiR3PQYx/aQTeIaFqcCgYAn/N/xXGKYl/++eeOuZ+5V0DmYQZBBGfDTbIUbweXxsBqiX4jNBBVhwTAPYBLgzhbZCVfQyxCOuVT10EOqMrkED35eVAIqoxvf3pSGOiaLUlV/+EMEhj9+1xI753y0FzQGsmWbV98WjiJYFkgaJ5j/BbqZxTRoo8RrjqmFsT5cgQKBgQCWTc4WlmbfSKMIloOtOf9jrMjvoWOtHXN+WmuMjfaQmR2wI13eJvqEWRA1tXdJ4c/FHk39p0OFOQbL9ljCYknmyhiS72XZUlBgE+kwhGNnuSv9gKftAKUH2+gO8j62awUwk8lRfxA2DsTfaQk1NGH9ncauviDR8QcccRmHYeTtNwKBgQCOvHiVaNw8XJIqt2r3j8pEJcr8LO+WNtLDU+h9NhM5a5NxfeRUlxdrqR0FXS4NkE6E3h9iLIRt2V+0bghzJMhKuwdjC0K6+jCb7ImV+Xcl9LNOQ1mPLBLS1jqdQnBS1ZPtcQpMrVi6dU9vVespylKEyGnQnUUtLgYrbO9OMrP1uQ==\n-----END RSA PRIVATE KEY-----",
// "plaintext": "just testing",
// "ciphertext": "LBicxglLvMyBin8uMpUnF5ARQ+KtAM563RViMepnOcyXa/NOJonNBixm+th+jX44\r\n/rie2ESbWg/FnlR4mHCEpTQJFXt12zpeXvtM8Hy1OQMud1B1Hc9hp1hhd1t6cuDz\r\n/Cs10n1+57V6zwHottYA6tn84cBn678SvPa/WTwgvb9lnBVZbesm3dVIr5uh2hk9\r\nNcVkmqyfi+ilkNQ3FIQfL+ciHvPFUIpljgIOipZhmufubdgMGW1HEUYlsmxLE7ce\r\ndpUQJoIbfKJ1x2dJRoeYsCjvcYFWdMUcg78HkXR+UcObP6zkK8cH33fb6PKKd8Z4\r\nToj4HROza8Dp7uCV5XyBTA=="
// };
// await checkTestData(testData);
// const testData = await createTestData();
// await checkTestData(testData);
// await printTestData();
// await runIntegrationTests();
return null;

View File

@@ -265,7 +265,7 @@ export class Bridge {
}
}
restart() {
restart(linuxSafeRestart = true) {
// Note that in this case we are not sending the "appClose" event
// to notify services and component that the app is about to close
// but for the current use-case it's not really needed.
@@ -276,7 +276,7 @@ export class Bridge {
execPath: process.env.PORTABLE_EXECUTABLE_FILE,
};
app.relaunch(options);
} else if (shim.isLinux()) {
} else if (shim.isLinux() && linuxSafeRestart) {
this.showInfoMessageBox(_('The app is now going to close. Please relaunch it to complete the process.'));
} else {
app.relaunch();

View File

@@ -4,7 +4,7 @@ import { _ } from '@joplin/lib/locale';
import bridge from './services/bridge';
import KvStore from '@joplin/lib/services/KvStore';
const { fileExtension } = require('@joplin/lib/path-utils');
const ArrayUtils = require('@joplin/lib/ArrayUtils');
import * as ArrayUtils from '@joplin/lib/ArrayUtils';
const packageInfo = require('./packageInfo.js');
const compareVersions = require('compare-versions');

View File

@@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/
import Setting from '@joplin/lib/models/Setting';
import { saveProfileConfig } from '@joplin/lib/services/profileConfig';
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
import bridge from '../services/bridge';
import restart from '../services/restart';
export const declaration: CommandDeclaration = {
name: 'switchProfile',
@@ -10,17 +10,17 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, profileIndex: number) => {
execute: async (context: CommandContext, profileId: string) => {
const currentConfig = context.state.profileConfig;
if (currentConfig.currentProfile === profileIndex) return;
if (currentConfig.currentProfileId === profileId) return;
const newConfig: ProfileConfig = {
...currentConfig,
currentProfile: profileIndex,
currentProfileId: profileId,
};
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
bridge().restart();
await restart(false);
},
};
};

View File

@@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '@joplin/lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile1',
@@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 0);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 0));
},
};
};

View File

@@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '@joplin/lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile2',
@@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 1);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 1));
},
};
};

View File

@@ -1,5 +1,6 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { profileIdByIndex } from '@joplin/lib/services/profileConfig';
export const declaration: CommandDeclaration = {
name: 'switchProfile3',
@@ -8,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await CommandService.instance().execute('switchProfile', 2);
execute: async (context: CommandContext) => {
await CommandService.instance().execute('switchProfile', profileIdByIndex(context.state.profileConfig, 2));
},
};
};

View File

@@ -1,7 +1,7 @@
import { _ } from '@joplin/lib/locale';
import Setting from '@joplin/lib/models/Setting';
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import bridge from '../services/bridge';
import restart from '../services/restart';
export const declaration: CommandDeclaration = {
name: 'toggleSafeMode',
@@ -14,7 +14,7 @@ export const runtime = (): CommandRuntime => {
enabled = enabled !== null ? enabled : !Setting.value('isSafeMode');
Setting.setValue('isSafeMode', enabled);
await Setting.saveAll();
bridge().restart();
await restart();
},
};
};

View File

@@ -14,6 +14,7 @@ const pathUtils = require('@joplin/lib/path-utils');
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const shared = require('@joplin/lib/components/shared/config-shared.js');
import ClipperConfigScreen from '../ClipperConfigScreen';
import restart from '../../services/restart';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
const settingKeyToControl: any = {
@@ -72,12 +73,12 @@ class ConfigScreenComponent extends React.Component<any, any> {
if (!confirm('This cannot be undone. Do you want to continue?')) return;
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalSyncState);
await Setting.saveAll();
bridge().restart();
await restart();
} else if (key === 'sync.clearLocalDataButton') {
if (!confirm('This cannot be undone. Do you want to continue?')) return;
Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData);
await Setting.saveAll();
bridge().restart();
await restart();
} else if (key === 'sync.openSyncWizard') {
this.props.dispatch({
type: 'DIALOG_OPEN',
@@ -124,19 +125,6 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.switchSection(event.section.name);
}
keyValueToArray(kv: any) {
const output = [];
for (const k in kv) {
if (!kv.hasOwnProperty(k)) continue;
output.push({
key: k,
label: kv[k],
});
}
return output;
}
renderSectionDescription(section: any) {
const description = Setting.sectionDescription(section.name);
if (!description) return null;
@@ -375,7 +363,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
} else if (md.isEnum) {
const items = [];
const settingOptions = md.options();
const array = this.keyValueToArray(settingOptions);
const array = Setting.enumOptionsToValueLabels(settingOptions, md.optionsOrder ? md.optionsOrder() : [], {
valueKey: 'key',
labelKey: 'label',
});
for (let i = 0; i < array.length; i++) {
const e = array[i];
items.push(
@@ -453,7 +445,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
});
const inputType = md.secure === true ? 'password' : 'text';
if (md.subType === 'file_path_and_args') {
if (md.subType === 'file_path_and_args' || md.subType === 'file_path' || md.subType === 'directory_path') {
inputStyle.marginBottom = subLabel.marginBottom;
const splitCmd = (cmdString: string) => {
@@ -483,15 +475,41 @@ class ConfigScreenComponent extends React.Component<any, any> {
};
const browseButtonClick = async () => {
const paths = await bridge().showOpenDialog();
if (!paths || !paths.length) return;
const cmd = splitCmd(this.state.settings[key]);
cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd));
if (md.subType === 'directory_path') {
const paths = await bridge().showOpenDialog({
properties: ['openDirectory'],
});
if (!paths || !paths.length) return;
updateSettingValue(key, paths[0]);
} else {
const paths = await bridge().showOpenDialog();
if (!paths || !paths.length) return;
const cmd = splitCmd(this.state.settings[key]);
cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd));
}
};
const cmd = splitCmd(this.state.settings[key]);
const argComp = md.subType !== 'file_path_and_args' ? null : (
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div style={subLabel}>{_('Arguments:')}</div>
<input
type={inputType}
style={inputStyle}
onChange={(event: any) => {
onArgsChange(event);
}}
value={cmd[1]}
spellCheck={false}
/>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
);
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
@@ -519,25 +537,9 @@ class ConfigScreenComponent extends React.Component<any, any> {
/>
</div>
</div>
<div style={{ ...rowStyle, marginBottom: 5 }}>
<div style={subLabel}>{_('Arguments:')}</div>
<input
type={inputType}
style={inputStyle}
onChange={(event: any) => {
onArgsChange(event);
}}
value={cmd[1]}
spellCheck={false}
/>
<div style={{ width: inputStyle.width, minWidth: inputStyle.minWidth }}>
{descriptionComp}
</div>
</div>
</div>
</div>
{argComp}
</div>
);
} else {
@@ -622,7 +624,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
private async restartApp() {
await Setting.saveAll();
bridge().restart();
await restart();
}
private async checkNeedRestart() {

View File

@@ -37,6 +37,8 @@ class DropboxLoginScreenComponent extends React.Component<any, any> {
const inputStyle = Object.assign({}, theme.inputStyle, { width: 500 });
const buttonStyle = Object.assign({}, theme.buttonStyle, { marginRight: 10 });
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={containerStyle}>
@@ -49,7 +51,7 @@ class DropboxLoginScreenComponent extends React.Component<any, any> {
<p>
<input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle} />
</p>
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>
<button disabled={this.state.checkingAuthToken} style={buttonStyle} onClick={this.shared_.submit_click}>
{_('Submit')}
</button>
</div>

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import versionInfo from '@joplin/lib/versionInfo';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';
import bridge from '../services/bridge';
import restart from '../services/restart';
const packageInfo = require('../packageInfo.js');
const ipcRenderer = require('electron').ipcRenderer;
@@ -75,7 +75,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
const safeMode_click = async () => {
Setting.setValue('isSafeMode', true);
await Setting.saveAll();
bridge().restart();
await restart();
};
try {

View File

@@ -8,7 +8,7 @@ import NoteEditor from '../NoteEditor/NoteEditor';
import NoteContentPropertiesDialog from '../NoteContentPropertiesDialog';
import ShareNoteDialog from '../ShareNoteDialog';
import CommandService from '@joplin/lib/services/CommandService';
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import { PluginHtmlContents, PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import Sidebar from '../Sidebar/Sidebar';
import UserWebview from '../../services/plugins/UserWebview';
import UserWebviewDialog from '../../services/plugins/UserWebviewDialog';
@@ -40,6 +40,7 @@ import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import commands from './commands/index';
import invitationRespond from '../../services/share/invitationRespond';
import restart from '../../services/restart';
const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js');
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
@@ -53,6 +54,7 @@ interface LayerModalState {
interface Props {
plugins: PluginStates;
pluginHtmlContents: PluginHtmlContents;
pluginsLoaded: boolean;
hasNotesBeingSaved: boolean;
dispatch: Function;
@@ -68,7 +70,6 @@ interface Props {
showNeedUpgradingMasterKeyMessage: boolean;
showShouldReencryptMessage: boolean;
showInstallTemplatesPlugin: boolean;
focusedField: string;
themeId: number;
settingEditorCodeView: boolean;
pluginsLegacy: any;
@@ -266,18 +267,22 @@ class MainScreenComponent extends React.Component<Props, State> {
if (this.waitForNotesSavedIID_) shim.clearInterval(this.waitForNotesSavedIID_);
this.waitForNotesSavedIID_ = null;
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: !this.props.hasNotesBeingSaved,
});
const sendCanClose = async (canClose: boolean) => {
if (canClose) {
Setting.setValue('wasClosedSuccessfully', true);
await Setting.saveAll();
}
ipcRenderer.send('asynchronous-message', 'appCloseReply', { canClose });
};
await sendCanClose(!this.props.hasNotesBeingSaved);
if (this.props.hasNotesBeingSaved) {
this.waitForNotesSavedIID_ = shim.setInterval(() => {
if (!this.props.hasNotesBeingSaved) {
shim.clearInterval(this.waitForNotesSavedIID_);
this.waitForNotesSavedIID_ = null;
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: true,
});
void sendCanClose(true);
}
}, 50);
}
@@ -556,13 +561,13 @@ class MainScreenComponent extends React.Component<Props, State> {
const onRestartAndUpgrade = async () => {
Setting.setValue('sync.upgradeState', Setting.SYNC_UPGRADE_STATE_MUST_DO);
await Setting.saveAll();
bridge().restart();
await restart();
};
const onDisableSafeModeAndRestart = async () => {
Setting.setValue('isSafeMode', false);
await Setting.saveAll();
bridge().restart();
await restart();
};
const onInvitationRespond = async (shareUserId: string, folderId: string, masterKey: MasterKeyEntity, accept: boolean) => {
@@ -692,7 +697,6 @@ class MainScreenComponent extends React.Component<Props, State> {
key={key}
resizableLayoutEventEmitter={eventEmitter}
visible={event.visible}
focusedField={this.props.focusedField}
size={event.size}
themeId={this.props.themeId}
/>;
@@ -723,12 +727,13 @@ class MainScreenComponent extends React.Component<Props, State> {
}
} else {
const { view, plugin } = viewInfo;
const html = this.props.pluginHtmlContents[plugin.id]?.[view.id] ?? '';
return <UserWebview
key={view.id}
viewId={view.id}
themeId={this.props.themeId}
html={view.html}
html={html}
scripts={view.scripts}
pluginId={plugin.id}
borderBottom={true}
@@ -762,12 +767,13 @@ class MainScreenComponent extends React.Component<Props, State> {
const { plugin, view } = info;
if (view.containerType !== ContainerType.Dialog) continue;
if (!view.opened) continue;
const html = this.props.pluginHtmlContents[plugin.id]?.[view.id] ?? '';
output.push(<UserWebviewDialog
key={view.id}
viewId={view.id}
themeId={this.props.themeId}
html={view.html}
html={html}
scripts={view.scripts}
pluginId={plugin.id}
buttons={view.buttons}
@@ -853,22 +859,18 @@ const mapStateToProps = (state: AppState) => {
return {
themeId: state.settings.theme,
settingEditorCodeView: state.settings['editor.codeView'],
folders: state.folders,
notes: state.notes,
hasDisabledSyncItems: state.hasDisabledSyncItems,
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(syncInfo.masterKeys).length,
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
selectedFolderId: state.selectedFolderId,
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
pluginsLegacy: state.pluginsLegacy,
plugins: state.pluginService.plugins,
pluginHtmlContents: state.pluginService.pluginHtmlContents,
customCss: state.customCss,
editorNoteStatuses: state.editorNoteStatuses,
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
focusedField: state.focusedField,
layoutMoveMode: state.layoutMoveMode,
mainLayout: state.mainLayout,
startupPluginsLoaded: state.startupPluginsLoaded,

View File

@@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/
import { _ } from '@joplin/lib/locale';
import { createNewProfile, saveProfileConfig } from '@joplin/lib/services/profileConfig';
import Setting from '@joplin/lib/models/Setting';
import bridge from '../../../services/bridge';
import restart from '../../../services/restart';
export const declaration: CommandDeclaration = {
name: 'addProfile',
@@ -19,10 +19,10 @@ export const runtime = (comp: any): CommandRuntime => {
value: '',
onClose: async (answer: string) => {
if (answer) {
const newConfig = await createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfile = newConfig.profiles.length - 1;
const { newConfig, newProfile } = createNewProfile(context.state.profileConfig, answer);
newConfig.currentProfileId = newProfile.id;
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
bridge().restart();
await restart(false);
}
comp.setState({ promptOptions: null });

View File

@@ -86,14 +86,14 @@ const useSwitchProfileMenuItems = (profileConfig: ProfileConfig, menuItemDic: an
menuItem = {
label: profile.name,
click: () => {
void CommandService.instance().execute('switchProfile', i);
void CommandService.instance().execute('switchProfile', profile.id);
},
};
}
menuItem.label = profile.name;
menuItem.type = 'checkbox';
menuItem.checked = profileConfig.currentProfile === i;
menuItem.checked = profileConfig.currentProfileId === profile.id;
switchProfileMenuItems.push(menuItem);
}
@@ -211,6 +211,12 @@ function useMenu(props: Props) {
const [keymapLastChangeTime, setKeymapLastChangeTime] = useState(Date.now());
const [modulesLastChangeTime, setModulesLastChangeTime] = useState(Date.now());
// We use a ref here because the plugin state can change frequently when
// switching note since any plugin view might be rendered again. However we
// need this plugin state only in a click handler when exporting notes, and
// for that a ref is sufficient.
const pluginsRef = useRef(props.plugins);
const onMenuItemClick = useCallback((commandName: string) => {
void CommandService.instance().execute(commandName);
}, []);
@@ -371,7 +377,7 @@ function useMenu(props: Props) {
(action: any) => props.dispatch(action),
module,
{
plugins: props.plugins,
plugins: pluginsRef.current,
customCss: props.customCss,
}
);
@@ -626,9 +632,11 @@ function useMenu(props: Props) {
// editor or a regular text field.
{
role: 'undo',
label: _('Undo'),
},
{
role: 'redo',
label: _('Redo'),
},
separator(),
menuItemDic.textBold,
@@ -905,7 +913,6 @@ function useMenu(props: Props) {
modulesLastChangeTime,
props['spellChecker.language'],
props['spellChecker.enabled'],
props.plugins,
props.customCss,
props.locale,
props.profileConfig,

View File

@@ -39,7 +39,7 @@ class NavigatorComponent extends Component {
};
return (
<div style={this.props.style}>
<div style={this.props.style} className={this.props.className}>
<Screen style={screenStyle} {...screenProps} />
</div>
);

View File

@@ -38,6 +38,7 @@ import ErrorBoundary from '../../../ErrorBoundary';
import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
import eventManager from '@joplin/lib/eventManager';
import { EditContextMenuFilterObject } from '@joplin/lib/services/plugins/api/JoplinWorkspace';
import { checkTableIsUnderCursor, readTableAroundCursor } from './utils/tables';
const menuUtils = new MenuUtils(CommandService.instance());
@@ -259,7 +260,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
return commandOutput;
},
};
}, [props.content, props.visiblePanes, addListItem, wrapSelectionWithStrings, setEditorPercentScroll, setViewerPercentScroll, resetScroll, renderedBody]);
}, [props.content, props.visiblePanes, addListItem, wrapSelectionWithStrings, setEditorPercentScroll, setViewerPercentScroll, resetScroll]);
const onEditorPaste = useCallback(async (event: any = null) => {
const resourceMds = await handlePasteEvent(event);
@@ -465,6 +466,10 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
color: ${theme.color};
}
div.CodeMirror span.cm-variable-2, div.CodeMirror span.cm-variable-3, div.CodeMirror span.cm-keyword {
color: ${theme.color};
}
div.CodeMirror span.cm-quote {
color: ${theme.color};
opacity: ${theme.blockQuoteOpacity};
@@ -479,10 +484,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
opacity: 0.5;
}
div.CodeMirror span.cm-variable-2, div.CodeMirror span.cm-variable-3, div.CodeMirror span.cm-keyword {
color: ${theme.color};
}
div.CodeMirror span.cm-comment {
color: ${theme.codeColor};
}
@@ -672,7 +673,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
// props.content has been updated).
const textChanged = props.searchMarkers.keywords.length > 0 && (props.content !== previousContent || renderedBody !== previousRenderedBody);
if (props.searchMarkers !== previousSearchMarkers || textChanged) {
if (webviewRef.current?.wrappedInstance && (props.searchMarkers !== previousSearchMarkers || textChanged)) {
webviewRef.current.wrappedInstance.send('setMarkers', props.searchMarkers.keywords, props.searchMarkers.options);
if (editorRef.current) {
@@ -753,7 +754,14 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const menu = new Menu();
const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
const cm = editorRef.current;
const hasSelectedText = cm && !!cm.getSelection() ;
const tableIsUnderCursor = checkTableIsUnderCursor(cm);
let tableUnderCursor: string = null;
if (tableIsUnderCursor) tableUnderCursor = readTableAroundCursor(cm);
menu.append(
new MenuItem({
@@ -785,6 +793,27 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
})
);
if (tableUnderCursor) {
menu.append(
new MenuItem({ type: 'separator' })
);
menu.append(
new MenuItem({
label: _('Edit table...'),
click: async () => {
props.dispatch({
type: 'DIALOG_OPEN',
name: 'tableEditor',
props: {
markdownTable: tableUnderCursor,
},
});
},
})
);
}
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
for (const item of spellCheckerMenuItems) {

View File

@@ -0,0 +1,48 @@
function findElementWithClass(element: any, className: string): any {
if (element.classList && element.classList.contains(className)) return element;
for (const child of element.childNodes) {
const hasClass = findElementWithClass(child, className);
if (hasClass) return hasClass;
}
return null;
}
export const checkTableIsUnderCursor = (cm: any) => {
if (!cm) return false;
const coords = cm.cursorCoords(cm.getCursor());
const element = document.elementFromPoint(coords.left, coords.top);
if (!element) return false;
return !!findElementWithClass(element, 'cm-jn-table-item');
};
export const readTableAroundCursor = (cm: any) => {
const idxAtCursor = cm.doc.getCursor().line;
const lineCount = cm.lineCount();
const lines: string[] = [];
for (let i = idxAtCursor - 1; i >= 0; i--) {
const line: string = cm.doc.getLine(i);
if (line.startsWith('|')) {
lines.splice(0, 0, line);
} else {
break;
}
}
lines.push(cm.doc.getLine(idxAtCursor));
for (let i = idxAtCursor + 1; i < lineCount; i++) {
const line: string = cm.doc.getLine(i);
if (line.startsWith('|')) {
lines.push(line);
} else {
break;
}
}
return lines.join('\n');
};

View File

@@ -165,6 +165,7 @@ export default function useJoplinMode(CodeMirror: any) {
}
if (isMonospace) { token = `${token} jn-monospace`; }
if (state.inTable) { token = `${token} jn-table-item`; }
// //////// End Monospace //////////
return token;

View File

@@ -585,7 +585,6 @@ const mapStateToProps = (state: AppState) => {
return {
noteId: noteId,
notes: state.notes,
folders: state.folders,
selectedNoteIds: state.selectedNoteIds,
selectedFolderId: state.selectedFolderId,
isProvisional: state.provisionalNoteIds.includes(noteId),

View File

@@ -4,38 +4,54 @@
// use strict is necessary here so that typescript doesn't place "use strict" above the jest docblock
// https://github.com/microsoft/TypeScript/issues/15819#issuecomment-782235619
import { textToDataUri, svgUriToPng } from './contextMenuUtils';
// import { textToDataUri, svgUriToPng } from './contextMenuUtils';
jest.mock('@joplin/lib/models/Resource');
// jest.mock('@joplin/lib/models/Resource');
// These tests are disabled because unfortunately they require the "canvas"
// module, and it's yet another binary module that fails to compile half of the
// time. Since it's only needed here it's not worth the trouble.
describe('contextMenu', () => {
it('should provide proper copy path', async () => {
const testCase = [
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">test</svg>',
'image/svg+xml',
];
const expectedText = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIj50ZXN0PC9zdmc+';
expect(textToDataUri(testCase[0], testCase[1])).toBe(expectedText);
it('should pass', () => {
expect(1).toBe(1);
});
it('should convert to png binary', async () => {
const testCase = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiIGhlaWdodD0iMTAwcHgiIHdpZHRoPSIxMDBweCI+CjxnPgoJPHBhdGggZD0iTTI4LjEsMzYuNmM0LjYsMS45LDEyLjIsMS42LDIwLjksMS4xYzguOS0wLjQsMTktMC45LDI4LjksMC45YzYuMywxLjIsMTEuOSwzLjEsMTYuOCw2Yy0xLjUtMTIuMi03LjktMjMuNy0xOC42LTMxLjMgICBjLTQuOS0wLjItOS45LDAuMy0xNC44LDEuNEM0Ny44LDE3LjksMzYuMiwyNS42LDI4LjEsMzYuNnoiLz4KCTxwYXRoIGQ9Ik03MC4zLDkuOEM1Ny41LDMuNCw0Mi44LDMuNiwzMC41LDkuNWMtMyw2LTguNCwxOS42LTUuMywyNC45YzguNi0xMS43LDIwLjktMTkuOCwzNS4yLTIzLjFDNjMuNywxMC41LDY3LDEwLDcwLjMsOS44eiIvPgoJPHBhdGggZD0iTTE2LjUsNTEuM2MwLjYtMS43LDEuMi0zLjQsMi01LjFjLTMuOC0zLjQtNy41LTctMTEtMTAuOGMtMi4xLDYuMS0yLjgsMTIuNS0yLjMsMTguN0M5LjYsNTEuMSwxMy40LDUwLjIsMTYuNSw1MS4zeiIvPgoJPHBhdGggZD0iTTksMzEuNmMzLjUsMy45LDcuMiw3LjYsMTEuMSwxMS4xYzAuOC0xLjYsMS43LTMuMSwyLjYtNC42YzAuMS0wLjIsMC4zLTAuNCwwLjQtMC42Yy0yLjktMy4zLTMuMS05LjItMC42LTE3LjYgICBjMC44LTIuNywxLjgtNS4zLDIuNy03LjRjLTUuMiwzLjQtOS44LDgtMTMuMywxMy43QzEwLjgsMjcuOSw5LjgsMjkuNyw5LDMxLjZ6Ii8+Cgk8cGF0aCBkPSJNMTUuNCw1NC43Yy0yLjYtMS02LjEsMC43LTkuNywzLjRjMS4yLDYuNiwzLjksMTMsOCwxOC41QzEzLDY5LjMsMTMuNSw2MS44LDE1LjQsNTQuN3oiLz4KCTxwYXRoIGQ9Ik0zOS44LDU3LjZDNTQuMyw2Ni43LDcwLDczLDg2LjUsNzYuNGMwLjYtMC44LDEuMS0xLjYsMS43LTIuNWM0LjgtNy43LDctMTYuMyw2LjgtMjQuOGMtMTMuOC05LjMtMzEuMy04LjQtNDUuOC03LjcgICBjLTkuNSwwLjUtMTcuOCwwLjktMjMuMi0xLjdjLTAuMSwwLjEtMC4yLDAuMy0wLjMsMC40Yy0xLDEuNy0yLDMuNC0yLjksNS4xQzI4LjIsNDkuNywzMy44LDUzLjksMzkuOCw1Ny42eiIvPgoJPHBhdGggZD0iTTI2LjIsODguMmMzLjMsMiw2LjcsMy42LDEwLjIsNC43Yy0zLjUtNi4yLTYuMy0xMi42LTguOC0xOC41Yy0zLjEtNy4yLTUuOC0xMy41LTktMTcuMmMtMS45LDgtMiwxNi40LTAuMywyNC43ICAgQzIwLjYsODQuMiwyMy4yLDg2LjMsMjYuMiw4OC4yeiIvPgoJPHBhdGggZD0iTTMwLjksNzNjMi45LDYuOCw2LjEsMTQuNCwxMC41LDIxLjJjMTUuNiwzLDMyLTIuMyw0Mi42LTE0LjZDNjcuNyw3Niw1Mi4yLDY5LjYsMzcuOSw2MC43QzMyLDU3LDI2LjUsNTMsMjEuMyw0OC42ICAgYy0wLjYsMS41LTEuMiwzLTEuNyw0LjZDMjQuMSw1Ny4xLDI3LjMsNjQuNSwzMC45LDczeiIvPgo8L2c+Cjwvc3ZnPg==';
const png = await svgUriToPng(document, testCase);
expect(png).toBeInstanceOf(Uint8Array);
});
// it('should provide proper copy path', async () => {
// const testCase = [
// '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">test</svg>',
// 'image/svg+xml',
// ];
// const expectedText = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIj50ZXN0PC9zdmc+';
// expect(textToDataUri(testCase[0], testCase[1])).toBe(expectedText);
// });
it('should throw error on invalid svg uri', async () => {
// We are mocking console.error since jsdom throws errors to console when we try to load an invalid img
// https://github.com/facebook/jest/pull/5267#issuecomment-356605468
const consoleError = console.error;
console.error = jest.fn();
const testCases: Array<string> = [
'data:image/svg+xml;base64,error',
'invalid',
];
for (const testCase of testCases) {
await expect(svgUriToPng(document, testCase)).rejects.toBeInstanceOf(Error);
}
console.error = consoleError;
});
// it('should convert to png binary', async () => {
// const testCase = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiIGhlaWdodD0iMTAwcHgiIHdpZHRoPSIxMDBweCI+CjxnPgoJPHBhdGggZD0iTTI4LjEsMzYuNmM0LjYsMS45LDEyLjIsMS42LDIwLjksMS4xYzguOS0wLjQsMTktMC45LDI4LjksMC45YzYuMywxLjIsMTEuOSwzLjEsMTYuOCw2Yy0xLjUtMTIuMi03LjktMjMuNy0xOC42LTMxLjMgICBjLTQuOS0wLjItOS45LDAuMy0xNC44LDEuNEM0Ny44LDE3LjksMzYuMiwyNS42LDI4LjEsMzYuNnoiLz4KCTxwYXRoIGQ9Ik03MC4zLDkuOEM1Ny41LDMuNCw0Mi44LDMuNiwzMC41LDkuNWMtMyw2LTguNCwxOS42LTUuMywyNC45YzguNi0xMS43LDIwLjktMTkuOCwzNS4yLTIzLjFDNjMuNywxMC41LDY3LDEwLDcwLjMsOS44eiIvPgoJPHBhdGggZD0iTTE2LjUsNTEuM2MwLjYtMS43LDEuMi0zLjQsMi01LjFjLTMuOC0zLjQtNy41LTctMTEtMTAuOGMtMi4xLDYuMS0yLjgsMTIuNS0yLjMsMTguN0M5LjYsNTEuMSwxMy40LDUwLjIsMTYuNSw1MS4zeiIvPgoJPHBhdGggZD0iTTksMzEuNmMzLjUsMy45LDcuMiw3LjYsMTEuMSwxMS4xYzAuOC0xLjYsMS43LTMuMSwyLjYtNC42YzAuMS0wLjIsMC4zLTAuNCwwLjQtMC42Yy0yLjktMy4zLTMuMS05LjItMC42LTE3LjYgICBjMC44LTIuNywxLjgtNS4zLDIuNy03LjRjLTUuMiwzLjQtOS44LDgtMTMuMywxMy43QzEwLjgsMjcuOSw5LjgsMjkuNyw5LDMxLjZ6Ii8+Cgk8cGF0aCBkPSJNMTUuNCw1NC43Yy0yLjYtMS02LjEsMC43LTkuNywzLjRjMS4yLDYuNiwzLjksMTMsOCwxOC41QzEzLDY5LjMsMTMuNSw2MS44LDE1LjQsNTQuN3oiLz4KCTxwYXRoIGQ9Ik0zOS44LDU3LjZDNTQuMyw2Ni43LDcwLDczLDg2LjUsNzYuNGMwLjYtMC44LDEuMS0xLjYsMS43LTIuNWM0LjgtNy43LDctMTYuMyw2LjgtMjQuOGMtMTMuOC05LjMtMzEuMy04LjQtNDUuOC03LjcgICBjLTkuNSwwLjUtMTcuOCwwLjktMjMuMi0xLjdjLTAuMSwwLjEtMC4yLDAuMy0wLjMsMC40Yy0xLDEuNy0yLDMuNC0yLjksNS4xQzI4LjIsNDkuNywzMy44LDUzLjksMzkuOCw1Ny42eiIvPgoJPHBhdGggZD0iTTI2LjIsODguMmMzLjMsMiw2LjcsMy42LDEwLjIsNC43Yy0zLjUtNi4yLTYuMy0xMi42LTguOC0xOC41Yy0zLjEtNy4yLTUuOC0xMy41LTktMTcuMmMtMS45LDgtMiwxNi40LTAuMywyNC43ICAgQzIwLjYsODQuMiwyMy4yLDg2LjMsMjYuMiw4OC4yeiIvPgoJPHBhdGggZD0iTTMwLjksNzNjMi45LDYuOCw2LjEsMTQuNCwxMC41LDIxLjJjMTUuNiwzLDMyLTIuMyw0Mi42LTE0LjZDNjcuNyw3Niw1Mi4yLDY5LjYsMzcuOSw2MC43QzMyLDU3LDI2LjUsNTMsMjEuMyw0OC42ICAgYy0wLjYsMS41LTEuMiwzLTEuNyw0LjZDMjQuMSw1Ny4xLDI3LjMsNjQuNSwzMC45LDczeiIvPgo8L2c+Cjwvc3ZnPg==';
// const png = await svgUriToPng(document, testCase);
// expect(png).toBeInstanceOf(Uint8Array);
// });
// it('should throw error on invalid svg uri', async () => {
// // We are mocking console.error since jsdom throws errors to console when we try to load an invalid img
// // https://github.com/facebook/jest/pull/5267#issuecomment-356605468
// const consoleError = console.error;
// console.error = jest.fn();
// const testCases: Array<string> = [
// 'data:image/svg+xml;base64,error',
// 'invalid',
// ];
// for (const testCase of testCases) {
// await expect(svgUriToPng(document, testCase)).rejects.toBeInstanceOf(Error);
// }
// console.error = consoleError;
// });
});

View File

@@ -86,18 +86,18 @@ export function menuItems(dispatch: Function): ContextMenuItems {
if (!filePath) return;
await fs.copy(resourcePath, filePath);
},
// We handle images received as text seperately as it can be saved in multiple formats
// We handle images received as text separately as it can be saved in multiple formats
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
},
saveAsSvg: {
label: _('Save as SVG'),
label: _('Save as %s', 'SVG'),
onAction: async (options: ContextMenuOptions) => {
await saveFileData(options.textToCopy, options.filename);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
},
saveAsPng: {
label: _('Save as PNG'),
label: _('Save as %s', 'PNG'),
onAction: async (options: ContextMenuOptions) => {
// First convert it to png then save
if (options.mime != 'image/svg+xml') {

View File

@@ -30,11 +30,11 @@ export interface ContextMenuItems {
[key: string]: ContextMenuItem;
}
export async function resourceInfo(options: ContextMenuOptions): Promise<any> {
export async function resourceInfo(options: ContextMenuOptions) {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const filePath = resource ? Resource.fullPath(resource) : null;
const resourcePath = resource ? Resource.fullPath(resource) : null;
const filename = resource ? (resource.filename ? resource.filename : resource.title) : options.filename ? options.filename : '';
return { resource, filePath, filename };
return { resource, resourcePath, filename };
}
export function textToDataUri(text: string, mime: string): string {

View File

@@ -27,7 +27,6 @@ export interface NoteEditorProps {
editorNoteStatuses: any;
syncStarted: boolean;
bodyEditor: string;
folders: any[];
notesParentType: string;
selectedNoteTags: any[];
lastEditorScrollPercents: any;

View File

@@ -1,3 +1,5 @@
import * as React from 'react';
import { useMemo, useEffect, useState, useRef, useCallback } from 'react';
import { AppState } from '../../app.reducer';
import eventManager from '@joplin/lib/eventManager';
import NoteListUtils from '../utils/NoteListUtils';
@@ -11,12 +13,12 @@ import CommandService from '@joplin/lib/services/CommandService';
import shim from '@joplin/lib/shim';
import styled from 'styled-components';
import { themeStyle } from '@joplin/lib/theme';
const React = require('react');
const { ItemList } = require('../ItemList.min.js');
const { connect } = require('react-redux');
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import { Props } from './types';
import usePrevious from '../hooks/usePrevious';
const commands = [
require('./commands/focusElementNoteList'),
@@ -29,50 +31,48 @@ const StyledRoot = styled.div`
border-right: 1px solid ${(props: any) => props.theme.dividerColor};
`;
class NoteListComponent extends React.Component {
constructor() {
super();
const itemAnchorRefs_: any = {
current: {},
};
CommandService.instance().componentRegisterCommands(this, commands);
export const itemAnchorRef = (itemId: string) => {
if (itemAnchorRefs_.current[itemId] && itemAnchorRefs_.current[itemId].current) return itemAnchorRefs_.current[itemId].current;
return null;
};
this.itemHeight = 34;
const NoteListComponent = (props: Props) => {
const [dragOverTargetNoteIndex, setDragOverTargetNoteIndex] = useState(null);
const [width, setWidth] = useState(0);
const [, setHeight] = useState(0);
this.state = {
dragOverTargetNoteIndex: null,
width: 0,
height: 0,
useEffect(() => {
itemAnchorRefs_.current = {};
CommandService.instance().registerCommands(commands);
return () => {
itemAnchorRefs_.current = {};
CommandService.instance().unregisterCommands(commands);
};
}, []);
this.noteListRef = React.createRef();
this.itemListRef = React.createRef();
this.itemAnchorRefs_ = {};
const [itemHeight, setItemHeight] = useState(34);
this.renderItem = this.renderItem.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.noteItem_titleClick = this.noteItem_titleClick.bind(this);
this.noteItem_noteDragOver = this.noteItem_noteDragOver.bind(this);
this.noteItem_noteDrop = this.noteItem_noteDrop.bind(this);
this.noteItem_checkboxClick = this.noteItem_checkboxClick.bind(this);
this.noteItem_dragStart = this.noteItem_dragStart.bind(this);
this.onGlobalDrop_ = this.onGlobalDrop_.bind(this);
this.registerGlobalDragEndEvent_ = this.registerGlobalDragEndEvent_.bind(this);
this.unregisterGlobalDragEndEvent_ = this.unregisterGlobalDragEndEvent_.bind(this);
this.itemContextMenu = this.itemContextMenu.bind(this);
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
}
const focusItemIID_ = useRef<any>(null);
const noteListRef = useRef(null);
const itemListRef = useRef(null);
style() {
if (this.styleCache_ && this.styleCache_[this.props.themeId]) return this.styleCache_[this.props.themeId];
let globalDragEndEventRegistered_ = false;
const theme = themeStyle(this.props.themeId);
const style = useMemo(() => {
const theme = themeStyle(props.themeId);
const style = {
return {
root: {
backgroundColor: theme.backgroundColor,
},
listItem: {
maxWidth: '100%',
height: this.itemHeight,
height: itemHeight,
boxSizing: 'border-box',
display: 'flex',
alignItems: 'stretch',
@@ -99,76 +99,71 @@ class NoteListComponent extends React.Component {
textDecoration: 'line-through',
},
};
}, [props.themeId, itemHeight]);
this.styleCache_ = {};
this.styleCache_[this.props.themeId] = style;
return style;
}
itemContextMenu(event: any) {
const itemContextMenu = useCallback((event: any) => {
const currentItemId = event.currentTarget.getAttribute('data-id');
if (!currentItemId) return;
let noteIds = [];
if (this.props.selectedNoteIds.indexOf(currentItemId) < 0) {
if (props.selectedNoteIds.indexOf(currentItemId) < 0) {
noteIds = [currentItemId];
} else {
noteIds = this.props.selectedNoteIds;
noteIds = props.selectedNoteIds;
}
if (!noteIds.length) return;
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: this.props.notes,
dispatch: this.props.dispatch,
watchedNoteFiles: this.props.watchedNoteFiles,
plugins: this.props.plugins,
inConflictFolder: this.props.selectedFolderId === Folder.conflictFolderId(),
customCss: this.props.customCss,
notes: props.notes,
dispatch: props.dispatch,
watchedNoteFiles: props.watchedNoteFiles,
plugins: props.plugins,
inConflictFolder: props.selectedFolderId === Folder.conflictFolderId(),
customCss: props.customCss,
});
menu.popup(bridge().window());
}
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles,props.plugins, props.selectedFolderId, props.customCss]);
onGlobalDrop_() {
this.unregisterGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: null });
}
const onGlobalDrop_ = () => {
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
};
registerGlobalDragEndEvent_() {
if (this.globalDragEndEventRegistered_) return;
this.globalDragEndEventRegistered_ = true;
document.addEventListener('dragend', this.onGlobalDrop_);
}
const registerGlobalDragEndEvent_ = () => {
if (globalDragEndEventRegistered_) return;
globalDragEndEventRegistered_ = true;
document.addEventListener('dragend', onGlobalDrop_);
};
unregisterGlobalDragEndEvent_() {
this.globalDragEndEventRegistered_ = false;
document.removeEventListener('dragend', this.onGlobalDrop_);
}
const unregisterGlobalDragEndEvent_ = () => {
globalDragEndEventRegistered_ = false;
document.removeEventListener('dragend', onGlobalDrop_);
};
dragTargetNoteIndex_(event: any) {
return Math.abs(Math.round((event.clientY - this.itemListRef.current.offsetTop() + this.itemListRef.current.offsetScroll()) / this.itemHeight));
}
const dragTargetNoteIndex_ = (event: any) => {
return Math.abs(Math.round((event.clientY - itemListRef.current.offsetTop() + itemListRef.current.offsetScroll()) / itemHeight));
};
noteItem_noteDragOver(event: any) {
if (this.props.notesParentType !== 'Folder') return;
const noteItem_noteDragOver = (event: any) => {
if (props.notesParentType !== 'Folder') return;
const dt = event.dataTransfer;
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
const newIndex = this.dragTargetNoteIndex_(event);
if (this.state.dragOverTargetNoteIndex === newIndex) return;
this.registerGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: newIndex });
const newIndex = dragTargetNoteIndex_(event);
if (dragOverTargetNoteIndex === newIndex) return;
registerGlobalDragEndEvent_();
setDragOverTargetNoteIndex(newIndex);
}
}
};
async noteItem_noteDrop(event: any) {
if (this.props.notesParentType !== 'Folder') return;
const noteItem_noteDrop = async (event: any) => {
if (props.notesParentType !== 'Folder') return;
if (this.props.noteSortOrder !== 'order') {
if (props.noteSortOrder !== 'order') {
const doIt = await bridge().showConfirmMessageBox(_('To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"', _('Custom order'), _('View'), _('Sort notes by')), {
buttons: [_('Do it now'), _('Cancel')],
});
@@ -181,17 +176,17 @@ class NoteListComponent extends React.Component {
// TODO: check that parent type is folder
const dt = event.dataTransfer;
this.unregisterGlobalDragEndEvent_();
this.setState({ dragOverTargetNoteIndex: null });
unregisterGlobalDragEndEvent_();
setDragOverTargetNoteIndex(null);
const targetNoteIndex = this.dragTargetNoteIndex_(event);
const targetNoteIndex = dragTargetNoteIndex_(event);
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
void Note.insertNotesAt(this.props.selectedFolderId, noteIds, targetNoteIndex);
}
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex);
};
async noteItem_checkboxClick(event: any, item: any) {
const noteItem_checkboxClick = async (event: any, item: any) => {
const checked = event.target.checked;
const newNote = {
id: item.id,
@@ -199,37 +194,37 @@ class NoteListComponent extends React.Component {
};
await Note.save(newNote, { userSideValidation: true });
eventManager.emit('todoToggle', { noteId: item.id, note: newNote });
}
};
async noteItem_titleClick(event: any, item: any) {
const noteItem_titleClick = async (event: any, item: any) => {
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_TOGGLE',
id: item.id,
});
} else if (event.shiftKey) {
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_EXTEND',
id: item.id,
});
} else {
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT',
id: item.id,
});
}
}
};
noteItem_dragStart(event: any) {
const noteItem_dragStart = (event: any) => {
let noteIds = [];
// Here there is two cases:
// - If multiple notes are selected, we drag the group
// - If only one note is selected, we drag the note that was clicked on (which might be different from the currently selected note)
if (this.props.selectedNoteIds.length >= 2) {
noteIds = this.props.selectedNoteIds;
if (props.selectedNoteIds.length >= 2) {
noteIds = props.selectedNoteIds;
} else {
const clickedNoteId = event.currentTarget.getAttribute('data-id');
if (clickedNoteId) noteIds.push(clickedNoteId);
@@ -240,61 +235,66 @@ class NoteListComponent extends React.Component {
event.dataTransfer.setDragImage(new Image(), 1, 1);
event.dataTransfer.clearData();
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
}
};
renderItem(item: any, index: number) {
const renderItem = useCallback((item: any, index: number) => {
const highlightedWords = () => {
if (this.props.notesParentType === 'Search') {
const query = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
if (props.notesParentType === 'Search') {
const query = BaseModel.byId(props.searches, props.selectedSearchId);
if (query) {
return this.props.highlightedWords;
return props.highlightedWords;
}
}
return [];
};
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
const ref = this.itemAnchorRefs_[item.id];
if (!itemAnchorRefs_.current[item.id]) itemAnchorRefs_.current[item.id] = React.createRef();
const ref = itemAnchorRefs_.current[item.id];
return <NoteListItem
ref={ref}
key={item.id}
style={this.style()}
style={style}
item={item}
index={index}
themeId={this.props.themeId}
width={this.state.width}
height={this.itemHeight}
dragItemIndex={this.state.dragOverTargetNoteIndex}
themeId={props.themeId}
width={width}
height={itemHeight}
dragItemIndex={dragOverTargetNoteIndex}
highlightedWords={highlightedWords()}
isProvisional={this.props.provisionalNoteIds.includes(item.id)}
isSelected={this.props.selectedNoteIds.indexOf(item.id) >= 0}
isWatched={this.props.watchedNoteFiles.indexOf(item.id) < 0}
itemCount={this.props.notes.length}
onCheckboxClick={this.noteItem_checkboxClick}
onDragStart={this.noteItem_dragStart}
onNoteDragOver={this.noteItem_noteDragOver}
onNoteDrop={this.noteItem_noteDrop}
onTitleClick={this.noteItem_titleClick}
onContextMenu={this.itemContextMenu}
isProvisional={props.provisionalNoteIds.includes(item.id)}
isSelected={props.selectedNoteIds.indexOf(item.id) >= 0}
isWatched={props.watchedNoteFiles.indexOf(item.id) < 0}
itemCount={props.notes.length}
onCheckboxClick={noteItem_checkboxClick}
onDragStart={noteItem_dragStart}
onNoteDragOver={noteItem_noteDragOver}
onNoteDrop={noteItem_noteDrop}
onTitleClick={noteItem_titleClick}
onContextMenu={itemContextMenu}
/>;
}
}, [style, props.themeId, width, itemHeight, dragOverTargetNoteIndex, props.provisionalNoteIds, props.selectedNoteIds, props.watchedNoteFiles,
props.notes,
props.notesParentType,
props.searches,
props.selectedSearchId,
props.highlightedWords,
]);
itemAnchorRef(itemId: string) {
if (this.itemAnchorRefs_[itemId] && this.itemAnchorRefs_[itemId].current) return this.itemAnchorRefs_[itemId].current;
return null;
}
const previousSelectedNoteIds = usePrevious(props.selectedNoteIds, []);
const previousNotes = usePrevious(props.notes, []);
const previousVisible = usePrevious(props.visible, false);
componentDidUpdate(prevProps: any) {
if (prevProps.selectedNoteIds !== this.props.selectedNoteIds && this.props.selectedNoteIds.length === 1) {
const id = this.props.selectedNoteIds[0];
const doRefocus = this.props.notes.length < prevProps.notes.length;
useEffect(() => {
if (previousSelectedNoteIds !== props.selectedNoteIds && props.selectedNoteIds.length === 1) {
const id = props.selectedNoteIds[0];
const doRefocus = props.notes.length < previousNotes.length;
for (let i = 0; i < this.props.notes.length; i++) {
if (this.props.notes[i].id === id) {
this.itemListRef.current.makeItemIndexVisible(i);
for (let i = 0; i < props.notes.length; i++) {
if (props.notes[i].id === id) {
itemListRef.current.makeItemIndexVisible(i);
if (doRefocus) {
const ref = this.itemAnchorRef(id);
const ref = itemAnchorRef(id);
if (ref) ref.focus();
}
break;
@@ -302,24 +302,24 @@ class NoteListComponent extends React.Component {
}
}
if (prevProps.visible !== this.props.visible) {
this.updateSizeState();
if (previousVisible !== props.visible) {
updateSizeState();
}
}
}, [previousSelectedNoteIds,previousNotes, previousVisible, props.selectedNoteIds, props.notes]);
scrollNoteIndex_(keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) {
const scrollNoteIndex_ = (keyCode: any, ctrlKey: any, metaKey: any, noteIndex: any) => {
if (keyCode === 33) {
// Page Up
noteIndex -= (this.itemListRef.current.visibleItemCount() - 1);
noteIndex -= (itemListRef.current.visibleItemCount() - 1);
} else if (keyCode === 34) {
// Page Down
noteIndex += (this.itemListRef.current.visibleItemCount() - 1);
noteIndex += (itemListRef.current.visibleItemCount() - 1);
} else if ((keyCode === 35 && ctrlKey) || (keyCode === 40 && metaKey)) {
// CTRL+End, CMD+Down
noteIndex = this.props.notes.length - 1;
noteIndex = props.notes.length - 1;
} else if ((keyCode === 36 && ctrlKey) || (keyCode === 38 && metaKey)) {
// CTRL+Home, CMD+Up
@@ -334,31 +334,31 @@ class NoteListComponent extends React.Component {
noteIndex += 1;
}
if (noteIndex < 0) noteIndex = 0;
if (noteIndex > this.props.notes.length - 1) noteIndex = this.props.notes.length - 1;
if (noteIndex > props.notes.length - 1) noteIndex = props.notes.length - 1;
return noteIndex;
}
};
async onKeyDown(event: any) {
const onKeyDown = async (event: any) => {
const keyCode = event.keyCode;
const noteIds = this.props.selectedNoteIds;
const noteIds = props.selectedNoteIds;
if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode == 36)) {
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
let noteIndex = BaseModel.modelIndexById(props.notes, noteId);
noteIndex = this.scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
noteIndex = scrollNoteIndex_(keyCode, event.ctrlKey, event.metaKey, noteIndex);
const newSelectedNote = this.props.notes[noteIndex];
const newSelectedNote = props.notes[noteIndex];
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT',
id: newSelectedNote.id,
});
this.itemListRef.current.makeItemIndexVisible(noteIndex);
itemListRef.current.makeItemIndexVisible(noteIndex);
this.focusNoteId_(newSelectedNote.id);
focusNoteId_(newSelectedNote.id);
event.preventDefault();
}
@@ -373,7 +373,7 @@ class NoteListComponent extends React.Component {
// SPACE
event.preventDefault();
const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
const notes = BaseModel.modelsByIds(props.notes, noteIds);
const todos = notes.filter((n: any) => !!n.is_todo);
if (!todos.length) return;
@@ -382,7 +382,7 @@ class NoteListComponent extends React.Component {
await Note.save(toggledTodo);
}
this.focusNoteId_(todos[0].id);
focusNoteId_(todos[0].id);
}
if (keyCode === 9) {
@@ -400,62 +400,73 @@ class NoteListComponent extends React.Component {
// Ctrl+A key
event.preventDefault();
this.props.dispatch({
props.dispatch({
type: 'NOTE_SELECT_ALL',
});
}
}
};
focusNoteId_(noteId: string) {
const focusNoteId_ = (noteId: string) => {
// - We need to focus the item manually otherwise focus might be lost when the
// list is scrolled and items within it are being rebuilt.
// - We need to use an interval because when leaving the arrow pressed, the rendering
// of items might lag behind and so the ref is not yet available at this point.
if (!this.itemAnchorRef(noteId)) {
if (this.focusItemIID_) shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = shim.setInterval(() => {
if (this.itemAnchorRef(noteId)) {
this.itemAnchorRef(noteId).focus();
shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
if (!itemAnchorRef(noteId)) {
if (focusItemIID_.current) shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = shim.setInterval(() => {
if (itemAnchorRef(noteId)) {
itemAnchorRef(noteId).focus();
shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = null;
}
}, 10);
} else {
this.itemAnchorRef(noteId).focus();
itemAnchorRef(noteId).focus();
}
}
};
updateSizeState() {
this.setState({
width: this.noteListRef.current.clientWidth,
height: this.noteListRef.current.clientHeight,
});
}
const updateSizeState = () => {
setWidth(noteListRef.current.clientWidth);
setHeight(noteListRef.current.clientHeight);
};
resizableLayout_resize() {
this.updateSizeState();
}
const resizableLayout_resize = () => {
updateSizeState();
};
componentDidMount() {
this.props.resizableLayoutEventEmitter.on('resize', this.resizableLayout_resize);
this.updateSizeState();
}
useEffect(() => {
props.resizableLayoutEventEmitter.on('resize', resizableLayout_resize);
return () => {
props.resizableLayoutEventEmitter.off('resize', resizableLayout_resize);
};
}, [props.resizableLayoutEventEmitter]);
componentWillUnmount() {
if (this.focusItemIID_) {
shim.clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
useEffect(() => {
updateSizeState();
return () => {
if (focusItemIID_.current) {
shim.clearInterval(focusItemIID_.current);
focusItemIID_.current = null;
}
CommandService.instance().componentUnregisterCommands(commands);
};
}, []);
useEffect(() => {
// When a note list item is styled by userchrome.css, its height is reflected.
// Ref. https://github.com/laurent22/joplin/pull/6542
const noteItem = Object.values<any>(itemAnchorRefs_.current)[0]?.current;
const actualItemHeight = noteItem?.getHeight() ?? 0;
if (actualItemHeight >= 8) { // To avoid generating too many narrow items
setItemHeight(actualItemHeight);
}
});
this.props.resizableLayoutEventEmitter.off('resize', this.resizableLayout_resize);
const renderEmptyList = () => {
if (props.notes.length) return null;
CommandService.instance().componentUnregisterCommands(commands);
}
renderEmptyList() {
if (this.props.notes.length) return null;
const theme = themeStyle(this.props.themeId);
const theme = themeStyle(props.themeId);
const padding = 10;
const emptyDivStyle = {
padding: `${padding}px`,
@@ -464,39 +475,35 @@ class NoteListComponent extends React.Component {
backgroundColor: theme.backgroundColor,
fontFamily: theme.fontFamily,
};
// emptyDivStyle.width = emptyDivStyle.width - padding * 2;
// emptyDivStyle.height = emptyDivStyle.height - padding * 2;
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
}
return <div style={emptyDivStyle}>{props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
};
renderItemList(style: any) {
if (!this.props.notes.length) return null;
const renderItemList = () => {
if (!props.notes.length) return null;
return (
<ItemList
ref={this.itemListRef}
disabled={this.props.isInsertingNotes}
itemHeight={this.style().listItem.height}
ref={itemListRef}
disabled={props.isInsertingNotes}
itemHeight={style.listItem.height}
className={'note-list'}
items={this.props.notes}
style={style}
itemRenderer={this.renderItem}
onKeyDown={this.onKeyDown}
items={props.notes}
style={props.size}
itemRenderer={renderItem}
onKeyDown={onKeyDown}
/>
);
}
};
render() {
if (!this.props.size) throw new Error('props.size is required');
if (!props.size) throw new Error('props.size is required');
return (
<StyledRoot ref={this.noteListRef}>
{this.renderEmptyList()}
{this.renderItemList(this.props.size)}
</StyledRoot>
);
}
}
return (
<StyledRoot ref={noteListRef}>
{renderEmptyList()}
{renderItemList()}
</StyledRoot>
);
};
const mapStateToProps = (state: AppState) => {
return {

View File

@@ -1,6 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer';
import { itemAnchorRef } from '../NoteList';
export const declaration: CommandDeclaration = {
name: 'focusElementNoteList',
@@ -8,13 +9,13 @@ export const declaration: CommandDeclaration = {
parentLabel: () => _('Focus'),
};
export const runtime = (comp: any): CommandRuntime => {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, noteId: string = null) => {
noteId = noteId || stateUtils.selectedNoteId(context.state);
if (noteId) {
const ref = comp.itemAnchorRef(noteId);
const ref = itemAnchorRef(noteId);
if (ref) ref.focus();
}
},

View File

@@ -0,0 +1,24 @@
import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
export interface Props {
themeId: any;
selectedNoteIds: string[];
notes: NoteEntity[];
dispatch: Function;
watchedNoteFiles: any[];
plugins: PluginStates;
selectedFolderId: string;
customCss: string;
notesParentType: string;
noteSortOrder: string;
resizableLayoutEventEmitter: any;
isInsertingNotes: boolean;
folders: FolderEntity[];
size: any;
searches: any[];
selectedSearchId: string;
highlightedWords: string[];
provisionalNoteIds: string[];
visible: boolean;
}

View File

@@ -167,6 +167,7 @@ function NoteListControls(props: Props) {
const mapStateToProps = (state: AppState) => {
return {
showNewNoteButtons: state.focusedField !== 'globalSearch',
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],
sortOrderField: state.settings['notes.sortOrder.field'],
sortOrderReverse: state.settings['notes.sortOrder.reverse'],

View File

@@ -73,6 +73,7 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
focus: function() {
if (anchorRef.current) anchorRef.current.focus();
},
getHeight: () => anchorRef.current?.clientHeight,
};
});

View File

@@ -10,7 +10,6 @@ interface Props {
resizableLayoutEventEmitter: any;
size: Size;
visible: boolean;
focusedField: string;
themeId: number;
}
@@ -34,7 +33,7 @@ export default function NoteListWrapper(props: Props) {
return (
<StyledRoot>
<NoteListControls showNewNoteButtons={props.focusedField !== 'globalSearch'} height={controlHeight} />
<NoteListControls height={controlHeight} />
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
</StyledRoot>
);

View File

@@ -22,6 +22,7 @@ import Dialog from './Dialog';
import SyncWizardDialog from './SyncWizard/Dialog';
import MasterPasswordDialog from './MasterPasswordDialog/Dialog';
import EditFolderDialog from './EditFolderDialog/Dialog';
import TableEditorDialog from './TableEditorDialog/Dialog';
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
const { ImportScreen } = require('./ImportScreen.min.js');
const { ResourceScreen } = require('./ResourceScreen.js');
@@ -38,6 +39,7 @@ interface Props {
zoomFactor: number;
needApiAuth: boolean;
dialogs: AppStateDialog[];
dialogContentMaxSize: Size;
}
interface ModalDialogProps {
@@ -51,6 +53,7 @@ interface RegisteredDialogProps {
themeId: number;
key: string;
dispatch: Function;
dialogContentMaxSize: Size;
}
interface RegisteredDialog {
@@ -60,19 +63,25 @@ interface RegisteredDialog {
const registeredDialogs: Record<string, RegisteredDialog> = {
syncWizard: {
render: (props: RegisteredDialogProps, customProps: any) => {
return <SyncWizardDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
return <SyncWizardDialog key={props.key} dispatch={props.dispatch} dialogContentMaxSize={props.dialogContentMaxSize} themeId={props.themeId} {...customProps}/>;
},
},
masterPassword: {
render: (props: RegisteredDialogProps, customProps: any) => {
return <MasterPasswordDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
return <MasterPasswordDialog key={props.key} dispatch={props.dispatch} dialogContentMaxSize={props.dialogContentMaxSize} themeId={props.themeId} {...customProps}/>;
},
},
editFolder: {
render: (props: RegisteredDialogProps, customProps: any) => {
return <EditFolderDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
return <EditFolderDialog key={props.key} dispatch={props.dispatch} dialogContentMaxSize={props.dialogContentMaxSize} themeId={props.themeId} {...customProps}/>;
},
},
tableEditor: {
render: (props: RegisteredDialogProps, customProps: any) => {
return <TableEditorDialog key={props.key} dispatch={props.dispatch} dialogContentMaxSize={props.dialogContentMaxSize} themeId={props.themeId} {...customProps}/>;
},
},
};
@@ -195,10 +204,12 @@ class RootComponent extends React.Component<Props, any> {
for (const dialog of props.dialogs) {
const md = registeredDialogs[dialog.name];
if (!md) throw new Error(`Unknown dialog: ${dialog.name}`);
output.push(md.render({
key: dialog.name,
themeId: props.themeId,
dispatch: props.dispatch,
dialogContentMaxSize: props.dialogContentMaxSize,
}, dialog.props));
}
return output;
@@ -228,7 +239,7 @@ class RootComponent extends React.Component<Props, any> {
<StyleSheetContainer themeId={this.props.themeId}></StyleSheetContainer>
<MenuBar/>
<GlobalStyle/>
<Navigator style={navigatorStyle} screens={screens} />
<Navigator style={navigatorStyle} screens={screens} className={`profile-${this.props.profileConfigCurrentProfileId}`} />
{this.renderModalMessage(this.modalDialogProps())}
{this.renderDialogs()}
</ThemeProvider>
@@ -245,6 +256,12 @@ const mapStateToProps = (state: AppState) => {
themeId: state.settings.theme,
needApiAuth: state.needApiAuth,
dialogs: state.dialogs,
dialogContentMaxSize: {
// Minus padding, margins and dialog header and button bar.
width: state.windowContentSize.width - 36 * 2,
height: state.windowContentSize.height - 36 * 2 - 28 - 30 - 20,
},
profileConfigCurrentProfileId: state.profileConfig.currentProfileId,
};
};

View File

@@ -5,7 +5,7 @@ import useSyncTargetUpgrade, { SyncTargetUpgradeResult } from '@joplin/lib/servi
const { render } = require('react-dom');
const ipcRenderer = require('electron').ipcRenderer;
import Setting from '@joplin/lib/models/Setting';
const bridge = require('@electron/remote').require('./bridge').default;
import restart from '../services/restart';
function useAppCloseHandler(upgradeResult: SyncTargetUpgradeResult) {
useEffect(function() {
@@ -64,7 +64,7 @@ function useStyle() {
function useRestartOnDone(upgradeResult: SyncTargetUpgradeResult) {
useEffect(function() {
if (upgradeResult.done && !upgradeResult.error) {
bridge().restart();
void restart();
}
}, [upgradeResult.done]);
}

View File

@@ -0,0 +1,101 @@
import * as React from 'react';
import { useCallback, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
import Dialog from '../Dialog';
import DialogTitle from '../DialogTitle';
import { parseMarkdownTable } from '../../../lib/markdownUtils';
import { Size } from '../ResizableLayout/utils/types';
interface Props {
themeId: number;
dispatch: Function;
markdownTable: string;
dialogContentMaxSize: Size;
}
const markdownTableToObject = (markdownTable: string): any => {
const table = parseMarkdownTable(markdownTable);
return {
columns: table.headers.map(h => {
return {
title: h.label,
field: h.name,
hozAlign: h.justify,
editor: 'input',
};
}),
data: table.rows.map(row => {
return {
...row,
};
}),
};
};
export default function(props: Props) {
const elementId = `tabulator_${Math.floor(Math.random() * 1000000)}`;
const onClose = useCallback(() => {
props.dispatch({
type: 'DIALOG_CLOSE',
name: 'tableEditor',
});
}, [props.dispatch]);
const onButtonRowClick = useCallback(async (event: ClickEvent) => {
if (event.buttonName === 'cancel') {
onClose();
return;
}
if (event.buttonName === 'ok') {
return;
}
}, [onClose]);
useEffect(() => {
const table = markdownTableToObject(props.markdownTable);
const Tabulator = (window as any).Tabulator;
// TODO: probably doesn't need to be called every time
// TODO: Load CSS/JS dynamically?
// TODO: Clean up on exit
Tabulator.extendModule('edit', 'editors', {});
new Tabulator(`#${elementId}`, {
...table,
height: props.dialogContentMaxSize.height,
});
}, []);
function renderContent() {
return (
<div className="dialog-content">
<div id={elementId}></div>
</div>
);
}
function renderDialogWrapper() {
return (
<div className="dialog-root">
<DialogTitle title={_('Edit table')}/>
{renderContent()}
<DialogButtonRow
themeId={props.themeId}
onClick={onButtonRowClick}
okButtonLabel={_('Save')}
/>
</div>
);
}
return (
<Dialog onClose={onClose} renderContent={renderDialogWrapper}/>
);
}

View File

@@ -7,6 +7,10 @@
uses 'eval'.
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'">
-->
<!--
To add files below, then need to be in the "vendor" directory. To make this happen, use copyApplicationAssets.js
-->
<title>Joplin</title>
<link rel="stylesheet" href="style.min.css">
<link rel="stylesheet" href="style/icons/style.css">
@@ -15,6 +19,9 @@
<link rel="stylesheet" href="vendor/lib/smalltalk/css/smalltalk.css">
<link rel="stylesheet" href="vendor/lib/roboto-fontface/css/roboto/roboto-fontface.css">
<link rel="stylesheet" href="vendor/lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="vendor/lib/tabulator-tables/dist/css/tabulator.min.css">
<script type="text/javascript" src="vendor/lib/tabulator-tables/dist/js/tabulator.min.js"></script>
<style>
.smalltalk {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.8.2",
"version": "2.8.8",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -116,11 +116,10 @@
"app-builder-bin": "^1.9.11",
"babel-cli": "^6.26.0",
"babel-preset-react": "^6.24.1",
"canvas": "^2.9.0",
"electron": "14.1.0",
"electron-builder": "^22.11.7",
"electron-notarize": "^1.0.0",
"electron-rebuild": "^3.2.3",
"electron": "18.2.0",
"electron-builder": "^23.0.3",
"electron-notarize": "^1.2.1",
"electron-rebuild": "^3.2.7",
"glob": "^7.1.6",
"gulp": "^4.0.2",
"jest": "^26.6.3",
@@ -175,6 +174,7 @@
"styled-components": "5.1.1",
"styled-system": "5.1.5",
"taboverride": "^4.0.3",
"tabulator-tables": "^5.1.4",
"tinymce": "^5.2.0"
}
}

View File

@@ -15,7 +15,7 @@ import Note from '@joplin/lib/models/Note';
const { ItemList } = require('../gui/ItemList.min');
const HelpButton = require('../gui/HelpButton.min');
const { surroundKeywords, nextWhitespaceIndex, removeDiacritics } = require('@joplin/lib/string-utils.js');
const { mergeOverlappingIntervals } = require('@joplin/lib/ArrayUtils.js');
import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils';
import markupLanguageUtils from '../utils/markupLanguageUtils';
import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
import Logger from '@joplin/lib/Logger';

View File

@@ -1,4 +1,5 @@
import Logger from '@joplin/lib/Logger';
import time from '@joplin/lib/time';
const logger = Logger.create('BackOffHandler');
@@ -9,23 +10,39 @@ const logger = Logger.create('BackOffHandler');
// When a plugin needs to be throttled that way a warning is displayed so
// that the author gets an opportunity to fix it.
//
// 2. If the plugin makes many simultaneous calls (over 100), the handler throws
// an exception to stop the plugin. In that case the plugin will be broken,
// but most plugins will not get this error anyway because call are usually
// made in sequence. It might reveal a bug though - for example if the plugin
// 2. If the plugin makes many simultaneous calls, the handler throws an
// exception to stop the plugin. In that case the plugin will be broken, but
// most plugins will not get this error anyway because call are usually made
// in sequence. It might reveal a bug though - for example if the plugin
// makes a call every 1 second, but does not wait for the response (or assume
// the response will come in less than one second). In that case, the back
// off intervals combined with the incorrect code will make the plugin fail.
export default class BackOffHandler {
private backOffIntervals_ = Array(100).fill(0).concat([0, 1, 1, 2, 3, 5, 8]);
// The current logic is:
//
// - Up to 1000 calls per 10 seconds without restrictions
// - For calls 1000 to 2000, a 100 ms wait time is applied
// - Over 2000 calls, a 200 ms wait time is applied
// - After 10 seconds without making any call, the limits are reset (back to
// 0 second between calls).
//
// If more than 5000 simultaneous calls are being throttled, it's a bug in
// the plugin (not waiting for API responses), so we stop responding and
// throw an error.
private backOffIntervals_ =
Array(1000).fill(0).concat(
Array(1000).fill(100)).concat(
[200]);
private lastRequestTime_ = 0;
private pluginId_: string;
private resetBackOffInterval_ = (this.backOffIntervals_[this.backOffIntervals_.length - 1] + 1) * 1000;
private resetBackOffInterval_ = 10 * 1000;
private backOffIndex_ = 0;
private waitCount_ = 0;
private maxWaitCount_ = 100;
private maxWaitCount_ = 5000;
public constructor(pluginId: string) {
this.pluginId_ = pluginId;
@@ -51,21 +68,13 @@ export default class BackOffHandler {
this.waitCount_++;
// For now don't actually apply a backoff and don't abort.
logger.warn(`Plugin ${this.pluginId_}: Applying a backoff of ${interval} milliseconds due to frequent plugin API calls. Consider reducing the number of calls, caching the data, or requesting more data per call. API call was: `, path, args, `[Wait count: ${this.waitCount_}]`);
logger.warn(`Plugin ${this.pluginId_}: Applying a backoff of ${interval} seconds due to frequent plugin API calls. Consider reducing the number of calls, caching the data, or requesting more data per call. API call was: `, path, args, `[Wait count: ${this.waitCount_}]`);
if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API calls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
if (this.waitCount_ > this.maxWaitCount_) logger.error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
await time.msleep(interval);
this.waitCount_--;
// if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
// await time.sleep(interval);
// this.waitCount_--;
}
}

View File

@@ -97,6 +97,7 @@ export default class PluginRunner extends BasePluginRunner {
return cb(...args);
}
// @ts-ignore
private backOffHandler(pluginId: string): BackOffHandler {
if (!this.backOffHandlers_[pluginId]) {
this.backOffHandlers_[pluginId] = new BackOffHandler(pluginId);
@@ -157,12 +158,14 @@ export default class PluginRunner extends BasePluginRunner {
const debugMappedArgs = fullPath.includes('setHtml') ? '<hidden>' : mappedArgs;
logger.debug(`Got message (3): ${fullPath}`, debugMappedArgs);
try {
await this.backOffHandler(plugin.id).wait(fullPath, debugMappedArgs);
} catch (error) {
logger.error(error);
return;
}
this.recordCallStat(plugin.id);
// try {
// await this.backOffHandler(plugin.id).wait(fullPath, debugMappedArgs);
// } catch (error) {
// logger.error(error);
// return;
// }
let result: any = null;
let error: any = null;

View File

@@ -0,0 +1,10 @@
import Setting from '@joplin/lib/models/Setting';
import bridge from './bridge';
export default async (linuxSafeRestart: boolean = true) => {
Setting.setValue('wasClosedSuccessfully', true);
await Setting.saveAll();
bridge().restart(linuxSafeRestart);
};

View File

@@ -83,6 +83,8 @@ async function main() {
'codemirror/addon/dialog/dialog.css',
'@joeattardi/emoji-button/dist/index.js',
'mark.js/dist/mark.min.js',
'tabulator-tables/dist/css/tabulator.min.css',
'tabulator-tables/dist/js/tabulator.min.js',
{
src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'),
dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`,

View File

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

View File

@@ -86,7 +86,7 @@ class Dropdown extends React.Component {
if (this.props.labelTransform && this.props.labelTransform === 'trim') headerLabel = headerLabel.trim();
const closeList = () => {
if (this.props.onClose()) this.props.onClose();
if (this.props.onClose) this.props.onClose();
this.setState({ listVisible: false });
};
@@ -116,7 +116,7 @@ class Dropdown extends React.Component {
onPress={() => {
this.updateHeaderCoordinates();
this.setState({ listVisible: true });
if (this.props.onOpen()) this.props.onOpen();
if (this.props.onOpen) this.props.onOpen();
}}
>
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>

View File

@@ -413,12 +413,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
if (md.isEnum) {
value = value.toString();
const items = [];
const settingOptions = md.options();
for (const k in settingOptions) {
if (!settingOptions.hasOwnProperty(k)) continue;
items.push({ label: settingOptions[k], value: k.toString() });
}
const items = Setting.enumOptionsToValueLabels(md.options(), md.optionsOrder ? md.optionsOrder() : []);
return (
<View key={key} style={{ flexDirection: 'column', borderBottomWidth: 1, borderBottomColor: theme.dividerColor }}>

View File

@@ -1201,8 +1201,8 @@ class NoteScreenComponent extends BaseScreenComponent {
onSaveButtonPress={this.saveNoteButton_press}
showSideMenuButton={false}
showSearchButton={false}
showUndoButton={this.state.undoRedoButtonState.canUndo || this.state.undoRedoButtonState.canRedo}
showRedoButton={this.state.undoRedoButtonState.canRedo}
showUndoButton={(this.state.undoRedoButtonState.canUndo || this.state.undoRedoButtonState.canRedo) && this.state.mode === 'edit'}
showRedoButton={this.state.undoRedoButtonState.canRedo && this.state.mode === 'edit'}
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
onUndoButtonPress={this.screenHeader_undoButtonPress}
onRedoButtonPress={this.screenHeader_redoButtonPress}

View File

@@ -176,7 +176,7 @@ class NoteTagsDialogComponent extends React.Component {
this.setState({ newTags: value });
}}
style={this.styles().tagBoxInput}
placeholder={_('tag1,tag2,...')}
placeholder={_('tag1, tag2, ...')}
/>
</View>
<View style={this.styles().tagBox}>

View File

@@ -492,13 +492,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 82;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.8.0;
MARKETING_VERSION = 12.8.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -521,12 +521,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 82;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.8.0;
MARKETING_VERSION = 12.8.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -667,14 +667,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 82;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.8.0;
MARKETING_VERSION = 12.8.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -698,14 +698,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 82;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.8.0;
MARKETING_VERSION = 12.8.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -567,4 +567,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 3503e0565874e79261edc56dcddb35b3a49a2984
COCOAPODS: 1.10.2
COCOAPODS: 1.11.3

View File

@@ -2,7 +2,7 @@
"name": "@joplin/app-mobile",
"description": "Joplin for Mobile",
"license": "MIT",
"version": "0.8.9",
"version": "2.8.0",
"private": true,
"scripts": {
"start": "react-native start --reset-cache",
@@ -15,8 +15,8 @@
"postinstall": "jetify && yarn run build"
},
"dependencies": {
"@joplin/lib": "~2.6",
"@joplin/renderer": "~2.6",
"@joplin/lib": "~2.8",
"@joplin/renderer": "~2.8",
"@react-native-community/clipboard": "^1.5.0",
"@react-native-community/datetimepicker": "^3.0.3",
"@react-native-community/geolocation": "^2.0.2",
@@ -75,7 +75,7 @@
"@codemirror/lang-markdown": "^0.18.4",
"@codemirror/state": "^0.18.7",
"@codemirror/view": "^0.18.19",
"@joplin/tools": "~2.6",
"@joplin/tools": "~2.8",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/node": "^14.14.6",

View File

@@ -36,6 +36,50 @@ function shimInit() {
return temp;
};
// This function can be used to debug "Network Request Failed" errors. It
// uses the native XMLHttpRequest which is more likely to get the proper
// response and error message.
shim.debugFetch = async (url, options = null) => {
options = {
method: 'GET',
headers: {},
...options,
};
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method, url, true);
for (const [key, value] of Object.entries(options.headers)) {
xhr.setRequestHeader(key, value);
}
xhr.onload = function() {
console.info('======================== XHR RESPONSE');
console.info(xhr.getAllResponseHeaders());
console.info('-------------------------------------');
// console.info(xhr.responseText);
console.info('======================== XHR RESPONSE');
resolve(xhr.responseText);
};
xhr.onerror = function() {
console.info('======================== XHR ERROR');
console.info(xhr.getAllResponseHeaders());
console.info('-------------------------------------');
console.info(xhr.responseText);
console.info('======================== XHR ERROR');
reject(new Error(xhr.responseText));
};
// TODO: Send POST data here if needed
xhr.send();
});
};
shim.fetch = async function(url, options = null) {
// The native fetch() throws an uncatchable error that crashes the
// app if calling it with an invalid URL such as '//.resource' or

View File

@@ -1,7 +1,7 @@
{
"name": "@joplin/fork-htmlparser2",
"description": "Fast & forgiving HTML/XML/RSS parser",
"version": "4.1.39",
"version": "4.1.40",
"author": "Felix Boehm <me@feedic.com>",
"publishConfig": {
"access": "public"

View File

@@ -2,7 +2,7 @@
"name": "@joplin/fork-sax",
"description": "An evented streaming XML parser in JavaScript",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"version": "1.2.43",
"version": "1.2.44",
"main": "lib/sax.js",
"publishConfig": {
"access": "public"

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-uslug",
"version": "1.0.4",
"version": "1.0.5",
"description": "A permissive slug generator that works with unicode.",
"author": "Jeremy Selier <jerem.selier@gmail.com>",
"publishConfig": {

View File

@@ -15,7 +15,7 @@ import { Command } from './types';
*
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.ts)
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
*
* To view what arguments are supported, you can open any of these files
* and look at the `execute()` command.

View File

@@ -16,8 +16,12 @@ interface ItemChangeEvent {
interface SyncStartEvent {
withErrors: boolean;
}
interface ResourceChangeEvent {
id: string;
}
declare type ItemChangeHandler = (event: ItemChangeEvent)=> void;
declare type SyncStartHandler = (event: SyncStartEvent)=> void;
declare type ResourceChangeHandler = (event: ResourceChangeEvent)=> void;
/**
* The workspace service provides access to all the parts of Joplin that
* are being worked on - i.e. the currently selected notes or notebooks as
@@ -42,6 +46,11 @@ export default class JoplinWorkspace {
* Called when the content of the current note changes.
*/
onNoteChange(handler: ItemChangeHandler): Promise<Disposable>;
/**
* Called when a resource is changed. Currently this handled will not be
* called when a resource is added or deleted.
*/
onResourceChange(handler: ResourceChangeHandler): Promise<void>;
/**
* Called when an alarm associated with a to-do is triggered.
*/

View File

@@ -297,7 +297,7 @@ export interface MenuItem {
/**
* Set to "separator" to create a divider line
*/
type?: string;
type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio');
/**
* Accelerator associated with the menu item
@@ -364,6 +364,12 @@ export enum SettingItemType {
Button = 6,
}
export enum SettingItemSubType {
FilePathAndArgs = 'file_path_and_args',
FilePath = 'file_path', // Not supported on mobile!
DirectoryPath = 'directory_path', // Not supported on mobile!
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
@@ -381,6 +387,12 @@ export interface SettingItem {
value: any;
type: SettingItemType;
/**
* Currently only used to display a file or directory selector. Always set
* `type` to `SettingItemType.String` when using this property.
*/
subType?: SettingItemSubType;
label: string;
description?: string;

View File

@@ -1,6 +1,6 @@
{
"name": "generator-joplin",
"version": "2.8.0",
"version": "2.8.1",
"description": "Scaffolds out a new Joplin plugin",
"homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin",
"author": {

View File

@@ -8,7 +8,7 @@ cd "$LIB_DIR"
yarn run generatePluginTypes
cd "$SCRIPT_DIR"
rsync -a --delete "$LIB_DIR/plugin_types/services/plugins/api/" "$SCRIPT_DIR/generators/app/templates/api/"
rsync -a --delete "$LIB_DIR/plugin_types/lib/services/plugins/api/" "$SCRIPT_DIR/generators/app/templates/api/"
cp "$LIB_DIR/services/plugins/api/types.ts" "$SCRIPT_DIR/generators/app/templates/api/"
cp "$SCRIPT_DIR/generators/app/templates/api_index.ts" "$SCRIPT_DIR/generators/app/templates/api/index.ts"
rm -f "$SCRIPT_DIR/generators/app/templates/api/types.d.ts"

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/htmlpack",
"version": "2.8.0",
"version": "2.8.1",
"description": "Pack an HTML file and all its linked resources into a single HTML file",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -14,7 +14,7 @@
"author": "Laurent Cozic",
"license": "MIT",
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.39",
"@joplin/fork-htmlparser2": "^4.1.40",
"css": "^3.0.0",
"datauri": "^4.1.0",
"fs-extra": "^10.0.0",

View File

@@ -19,6 +19,13 @@ describe('ArrayUtils', function() {
expect(a.length).toBe(3);
}));
it('should pull array elements', (async () => {
expect(ArrayUtils.pull(['a', 'b', 'c', 'a', 'b', 'c'], 'a')).toEqual(['b', 'c', 'b', 'c']);
expect(ArrayUtils.pull(['b', 'c', 'b', 'c'], 'a')).toEqual(['b', 'c', 'b', 'c']);
expect(ArrayUtils.pull(['a', 'b', 'c', 'a', 'b', 'c'], 'a', 'c')).toEqual(['b', 'b']);
expect(ArrayUtils.pull([], 'a')).toEqual([]);
}));
it('should find items using binary search', (async () => {
let items = ['aaa', 'ccc', 'bbb'];
expect(ArrayUtils.binarySearch(items, 'bbb')).toBe(-1); // Array not sorted!

View File

@@ -1,12 +1,10 @@
const ArrayUtils = {};
ArrayUtils.unique = function(array) {
export const unique = function(array: any[]) {
return array.filter(function(elem, index, self) {
return index === self.indexOf(elem);
});
};
ArrayUtils.removeElement = function(array, element) {
export const removeElement = function(array: any[], element: any) {
const index = array.indexOf(element);
if (index < 0) return array;
const newArray = array.slice();
@@ -15,7 +13,7 @@ ArrayUtils.removeElement = function(array, element) {
};
// https://stackoverflow.com/a/10264318/561309
ArrayUtils.binarySearch = function(items, value) {
export const binarySearch = function(items: any[], value: any) {
let startIndex = 0,
stopIndex = items.length - 1,
middle = Math.floor((stopIndex + startIndex) / 2);
@@ -36,7 +34,7 @@ ArrayUtils.binarySearch = function(items, value) {
return items[middle] != value ? -1 : middle;
};
ArrayUtils.findByKey = function(array, key, value) {
export const findByKey = function(array: any[], key: any, value: any) {
for (let i = 0; i < array.length; i++) {
const o = array[i];
if (typeof o !== 'object') continue;
@@ -45,7 +43,7 @@ ArrayUtils.findByKey = function(array, key, value) {
return null;
};
ArrayUtils.contentEquals = function(array1, array2) {
export const contentEquals = function(array1: any[], array2: any[]) {
if (array1 === array2) return true;
if (!array1.length && !array2.length) return true;
if (array1.length !== array2.length) return false;
@@ -60,10 +58,10 @@ ArrayUtils.contentEquals = function(array1, array2) {
// Merges multiple overlapping intervals into a single interval
// e.g. [0, 25], [20, 50], [75, 100] --> [0, 50], [75, 100]
ArrayUtils.mergeOverlappingIntervals = function(intervals, limit) {
export const mergeOverlappingIntervals = function(intervals: any[], limit: number) {
intervals.sort((a, b) => a[0] - b[0]);
const stack = [];
const stack: any[] = [];
if (intervals.length) {
stack.push(intervals[0]);
for (let i = 1; i < intervals.length && stack.length < limit; i++) {
@@ -80,7 +78,7 @@ ArrayUtils.mergeOverlappingIntervals = function(intervals, limit) {
return stack;
};
ArrayUtils.shuffle = function(array) {
export const shuffle = function(array: any[]) {
array = array.slice();
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
@@ -91,4 +89,13 @@ ArrayUtils.shuffle = function(array) {
return array;
};
module.exports = ArrayUtils;
// Used to replace lodash.pull, so that we don't need to import the whole
// package. Not optimised.
export const pull = (array: any[], ...elements: any[]) => {
const output: any[] = [];
for (const e of array) {
if (elements.includes(e)) continue;
output.push(e);
}
return output;
};

View File

@@ -738,7 +738,7 @@ export default class BaseApplication {
Setting.setConstant('tempDir', tempDir);
Setting.setConstant('pluginDataDir', `${profileDir}/plugin-data`);
Setting.setConstant('cacheDir', cacheDir);
Setting.setConstant('pluginDir', `${profileDir}/plugins`);
Setting.setConstant('pluginDir', `${rootProfileDir}/plugins`);
SyncTargetRegistry.addClass(SyncTargetNone);
SyncTargetRegistry.addClass(SyncTargetFilesystem);
@@ -811,8 +811,12 @@ export default class BaseApplication {
appLogger.info(`Client ID: ${Setting.value('clientId')}`);
if (Setting.value('firstStart')) {
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
// If it's a sub-profile, the locale must come from the root
// profile.
if (!Setting.value('isSubProfile')) {
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
}
Setting.skipDefaultMigrations();
@@ -825,9 +829,10 @@ export default class BaseApplication {
Setting.setValue('firstStart', 0);
} else {
Setting.applyDefaultMigrations();
setLocale(Setting.value('locale'));
}
setLocale(Setting.value('locale'));
if (Setting.value('env') === Env.Dev) {
// Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
// Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');

View File

@@ -83,16 +83,15 @@ export default class SyncTargetRegistry {
}
return output;
// const sorted: Record<string, string> = {};
// for (const o of syncTargetOrder) {
// sorted[o] = output[o];
// }
// for (const [name, value] of Object.entries(output)) {
// if (!sorted[name]) sorted[name] = value;
// }
// return sorted;
}
public static optionsOrder(): string[] {
return [
'0', // None
'10', // Joplin Cloud
'7', // Dropbox
'3', // OneDrive
];
}
}

View File

@@ -124,8 +124,15 @@ class FileApiDriverDropbox {
if (!options.responseFormat) options.responseFormat = 'text';
try {
// IMPORTANT:
//
// We cannot use POST here, because iOS (as of version 14?) doesn't
// support POST requests with an empty body:
//
// https://www.dropboxforum.com/t5/Dropbox-API-Support-Feedback/Error-1017-quot-cannot-parse-response-quot/td-p/589595
const response = await this.api().exec(
'POST',
'GET',
'files/download',
null,
{

View File

@@ -6,7 +6,7 @@ import time from './time';
const { isHidden } = require('./path-utils');
import JoplinError from './JoplinError';
import { Lock, LockClientType, LockType } from './services/synchronizer/LockHandler';
const ArrayUtils = require('./ArrayUtils');
import * as ArrayUtils from './ArrayUtils';
const { sprintf } = require('sprintf-js');
const Mutex = require('async-mutex').Mutex;

View File

@@ -1,25 +0,0 @@
const shim = require('./shim').default;
class GeolocationNode {
static async currentPosition(options = null) {
if (!options) options = {};
let response = await shim.fetch('https://freegeoip.app/json/');
if (!response.ok) throw new Error(`Could not get geolocation: ${await response.text()}`);
response = await response.json();
if (!('latitude' in response) || !('longitude' in response)) throw new Error(`Invalid geolocation response: ${response ? JSON.stringify(response) : '<null>'}`);
return {
timestamp: new Date().getTime(),
coords: {
longitude: response.longitude,
altitude: 0,
latitude: response.latitude,
},
};
}
}
module.exports = { GeolocationNode };

View File

@@ -0,0 +1,76 @@
import Logger from './Logger';
import shim from './shim';
const logger = Logger.create('geolocation-node');
interface CurrentPositionResponseCoordinates {
longitude: number;
latitude: number;
altitude: number;
}
interface CurrentPositionResponse {
timestamp: number;
coords: CurrentPositionResponseCoordinates;
}
interface CurrentPositionOptions {}
type GeoipService = ()=> Promise<CurrentPositionResponse>;
const fetchJson = async (url: string): Promise<any> => {
let r = await shim.fetch(url);
if (!r.ok) throw new Error(`Could not get geolocation: ${await r.text()}`);
r = await r.json();
return r;
};
const geoipServices: Record<string, GeoipService> = {
ipwhois: async (): Promise<CurrentPositionResponse> => {
const r = await fetchJson('https://ipwho.is/');
if (!('latitude' in r) || !('longitude' in r)) throw new Error(`Invalid geolocation response: ${r ? JSON.stringify(r) : '<null>'}`);
return {
timestamp: Date.now(),
coords: {
longitude: r.longitude,
altitude: 0,
latitude: r.latitude,
},
};
},
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 {
static async currentPosition(options: CurrentPositionOptions = null) {
if (!options) options = {};
for (const [serviceName, handler] of Object.entries(geoipServices)) {
try {
const response = await handler();
return response;
} catch (error) {
logger.warn(`Could not get geolocation from service "${serviceName}"`);
logger.warn(error);
}
}
throw new Error('Could not get geolocation from any of the services');
}
}

View File

@@ -51,6 +51,8 @@
"Add recipient:": "Afegeix un destinatari:",
"Add title": "Afegeix títol",
"Add to dictionary": "Afegeix al diccionari",
"Admin": "Administració",
"Admin dashboard": "Tauler d'administració",
"Advanced options": "Opcions avançades",
"Advanced tools": "Opcions avançades",
"All notes": "Totes les notes",
@@ -77,7 +79,9 @@
"Authentication was not completed (did not receive an authentication token).": "No s'ha completat l'autenticació (no ha rebut el testimoni d'autenticació).",
"Authorisation token:": "Testimoni d'autorització:",
"Auto": "Automàtic",
"Auto-add disabled accounts for deletion": "Afegeix automàticament per a eliminar els comptes deshabilitats",
"Auto-pair braces, parenthesis, quotations, etc.": "Aparellament automàtic de claus, parèntesis, cites, etc.",
"Automatically check for updates": "Comprova les actualitzacions automàticament",
"Automatically switch theme to match system theme": "Canvia de tema automàticament per a coincidir amb el tema del sistema",
"Back": "Enrere",
"Bold": "Negreta",
@@ -132,6 +136,7 @@
"Completed": "Finalitzat",
"Completed decryption.": "El desxifratge ha finalitzat.",
"Completed: %s (%s)": "Completat: %s (%s)",
"Compress old changes": "Comprimeix els canvis vells",
"Configuration": "Configuració",
"Confirm password cannot be empty": "El camp *Confirma la contrasenya* no pot ser buit",
"Confirm password:": "Confirmeu la contrasenya:",
@@ -145,6 +150,7 @@
"Copy": "Copia",
"Copy dev mode command to clipboard": "Copia l'ordre en mode de desenvolupament al porta-retalls",
"Copy external link": "Copia l'enllaç extern",
"Copy image": "Copia la imatge",
"Copy Link Address": "Copia l'adreça de l'enllaç",
"Copy Markdown link": "Copia l'enllaç Markdown",
"Copy path to clipboard": "Copia el camí al porta-retalls",
@@ -159,6 +165,8 @@
"Could not upgrade master key: %s": "No s'ha pogut actualitzar la clau mestra: %s",
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "No s'ha pogut verificar l'estat de compartició d'aquest bloc de notes - s'està avortant. Torneu a provar quan tingueu connexió a Internet.",
"Create a notebook": "Crea un bloc de notes nou",
"Create notebook": "Crea un bloc de notes",
"Create user": "Crea un usuari",
"Created": "Creació",
"created date": "data de creació",
"Created local items: %d.": "Elements locals creats: %d.",
@@ -180,6 +188,7 @@
"Custom TLS certificates": "Certificats TLS personalitzats",
"Cut": "Retalla",
"Dark": "Fosc",
"Dashboard": "Tauler de control",
"Database v%s": "Base de dades v%s",
"Date": "Data",
"Date format": "Format de data",
@@ -191,6 +200,8 @@
"Default: %s": "Per defecte: %s",
"Delete": "Suprimeix",
"Delete attachment \"%s\"?": "Voleu suprimir l'adjunt \"%s\"?",
"Delete expired sessions": "Suprimeix les sessions expirades",
"Delete expired tokens": "Suprimeix els tokens expirats",
"Delete line": "Suprimeix la línia",
"Delete local data and re-download from sync target": "Suprimeix les dades locals i torna a baixar de la destinació de sincronització",
"Delete note \"%s\"?": "Voleu suprimir la nota \"%s\"?",
@@ -255,6 +266,8 @@
"Editor monospace font family": "Lletra d'amplada fixa de l'editor",
"Either \"text\" or \"json\"": "«text» o «json»",
"Emacs": "Emacs",
"Email": "Correu electrònic",
"Emails": "Correus electrònics",
"emphasised text": "text ressaltat",
"Enable": "Activa",
"Enable ++insert++ syntax": "Activa la sintaxi ++insert++",
@@ -361,6 +374,7 @@
"Idle": "Ociós",
"Ignore": "Ignora",
"Ignore TLS certificate errors": "Ignora els errors de certificat TLS",
"Images": "Imatges",
"Import": "Importació",
"Importing from \"%s\" as \"%s\" format. Please wait...": "S'està important el format de «%s» a «%s». Espereu...",
"Importing notes...": "S'estan important notes...",
@@ -439,6 +453,7 @@
"Login with Dropbox": "Inicia sessió amb Dropbox",
"Login with OneDrive": "Inicia sessió amb OneDrive",
"Logout": "Desconnecta",
"Logs": "Registres",
"Make a donation": "Donatius",
"Manage master password": "Gestiona la contrasenya mestra",
"Manage master password...": "Gestiona la contrasenya mestra...",
@@ -539,6 +554,7 @@
"Operation cancelled": "L'operació s'ha cancel·lat",
"Options": "Opcions",
"Or create an account.": "O creeu un compte.",
"Other applications...": "Altres aplicacions",
"Output format: %s": "Format destí: %s",
"Page orientation for PDF export": "Orientació de la pàgina per exportació a PDF",
"Page size for PDF export": "Mida de la pàgina per exportació a PDF",
@@ -551,7 +567,6 @@
"PDF File": "Fitxer PDF",
"Permission needed": "Cal permís",
"Permission to use camera": "Permís per a utilitzar la càmera",
"Please click on \"%s\" to proceed": "Feu clic a \"%s\" per a continuar",
"Please confirm that you would like to re-encrypt your complete database.": "Confirmeu que voleu tornar a xifrar la vostra base de dades completa.",
"Please enter your password in the master key list below before upgrading the key.": "Entreu la vostra contrasenya en la llista de claus mestres d'aquí a sota abans d'actualitzar la clau.",
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Teniu en compte que si és un bloc de notes llarg, pot necessitar uns quants minuts fins que totes les notes es mostren al dispositiu del destinatari.",
@@ -580,6 +595,10 @@
"Previous versions of this note": "Versions prèvies d'aquesta nota",
"Print": "Imprimeix",
"Privacy Policy": "Política de privacitat",
"Process failed payment subscriptions": "Processa els pagaments de subscripcions fallits",
"Process oversized accounts": "Processa els comptes més grans del permés",
"Process user deletions": "Processa les supressions d'usuaris",
"Profile": "Perfil",
"Profile Version: %s": "Versió del perfil: %s",
"Properties": "Propietats",
"Public-private key pair:": "Parell de claus pública-privada:",
@@ -642,6 +661,8 @@
"See the pre-release page for more details: %s": "Consulta la pàgina de prellançament per a més detalls: %s",
"Select": "Selecciona",
"Select all": "Seleccioneu tot",
"Select emoji...": "Selecciona un emoji...",
"Select file...": "Selecciona un fitxer...",
"Server is already running on port %d": "El servidor ja està corrent en el port %d",
"Server is not running.": "El servidor no està corrent.",
"Server is running on port %d": "El servidor està corrent en el port %d",
@@ -832,6 +853,8 @@
"Unsupported link or message: %s": "Missatge o enllaç no suportat: %s",
"Untitled": "Sense títol",
"Update": "Actualització",
"Update profile": "Actualitza el perfil",
"Update total sizes": "Actualitza les mides totals",
"Updated": "Actualització",
"updated date": "data d'actualització",
"Updated local items: %d.": "Elements locals actualitzats: %d.",
@@ -851,6 +874,7 @@
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Utilitzeu això per a reconstruir l'índex de cerca si hi ha un problema amb la cerca. Pot trigar molt en funció del nombre de notes.",
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "S'utilitza per a la majoria de text a l'editor Markdown. Si no es troba, s'utilitza un tipus de lletra genèric proporcional (amplada variable).",
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "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).",
"User deletions": "Supressions d'usuari",
"Users": "Usuaris",
"Valid": "Vàlid",
"View": "Vista",
@@ -881,7 +905,6 @@
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Podeu utilitzar l'eina següent per tornar a xifrar les vostres dades, per exemple si sabeu que algunes de les vostres notes estan xifrades amb un mètode de xifratge obsolet.",
"Your choice: ": "La vostra tria: ",
"Your data is going to be re-encrypted and synced again.": "Les vostres dades es tornaran a xifrar i sincronitzar.",
"Your master password is needed to decrypt some of your data.": "La contrasenya mestra és necessària per a desxifrar algunes dades.",
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "La contrasenya és necessària per a desxifrar algunes dades. Teclegeu `:e2ee decrypt` per definir-la.",
"Your permission to use your camera is required.": "Es necessita el vostre permís per a utilitzar la càmera.",
"Your version: %s": "La vostra versió: %s",

View File

@@ -79,6 +79,7 @@
"Authentication was not completed (did not receive an authentication token).": "Godkendelse blev ikke fuldført (modtog ikke autorisationstoken).",
"Authorisation token:": "Autoriseringstoken:",
"Auto": "Auto",
"Auto-add disabled accounts for deletion": "Føj automatisk deaktiverede konti til sletning",
"Auto-pair braces, parenthesis, quotations, etc.": "Auto-par klammer, parenteser, citater, etc.",
"Automatically check for updates": "Tjek automatisk efter opdateringer",
"Automatically switch theme to match system theme": "Skifter automatisk tema så det matcher systemets tema",
@@ -135,6 +136,7 @@
"Completed": "Fuldført",
"Completed decryption.": "Dekryptering færdig.",
"Completed: %s (%s)": "Fuldført: %s (%s)",
"Compress old changes": "Komprimér gamle ændringer",
"Configuration": "Indstillinger",
"Confirm password cannot be empty": "Bekræftelsen af adgangskode må ikke være tom",
"Confirm password:": "Bekræft adgangskode:",
@@ -198,6 +200,8 @@
"Default: %s": "Standard: %s",
"Delete": "Slet",
"Delete attachment \"%s\"?": "Slet venhæftning \"%s\"?",
"Delete expired sessions": "Slet udløbne sessioner",
"Delete expired tokens": "Slet udløbne tokens",
"Delete line": "Slet linje",
"Delete local data and re-download from sync target": "Slet lokal data og download igen fra synkroniseringsmål",
"Delete note \"%s\"?": "Slet noten \"%s\"?",
@@ -262,6 +266,8 @@
"Editor monospace font family": "Editor monospace skrifttypefamilie",
"Either \"text\" or \"json\"": "Enten \"text\" eller \"json\"",
"Emacs": "Emacs",
"Email": "E-mail",
"Emails": "E-mails",
"emphasised text": "fremhævet tekst",
"Enable": "Aktivér",
"Enable ++insert++ syntax": "Aktivér ++insert++ syntaks",
@@ -368,6 +374,7 @@
"Idle": "Ledig",
"Ignore": "Ignorér",
"Ignore TLS certificate errors": "Ignorer TLS certifikat-fejl",
"Images": "Billeder",
"Import": "Importer",
"Importing from \"%s\" as \"%s\" format. Please wait...": "Importerer fra \"%s\" som \"%s\"-format. Vent venligst...",
"Importing notes...": "Importerer noter...",
@@ -547,6 +554,7 @@
"Operation cancelled": "Udførelse annulleret",
"Options": "Indstillinger",
"Or create an account.": "Eller opret en konto.",
"Other applications...": "Andre applikationer...",
"Output format: %s": "Output-format: %s",
"Page orientation for PDF export": "Sideretning for PDF eksport",
"Page size for PDF export": "Sidestørrelse for PDF eksport",
@@ -559,7 +567,6 @@
"PDF File": "PDF-fil",
"Permission needed": "Tilladelse nødvendig",
"Permission to use camera": "Tilladelse til at bruge kameraet",
"Please click on \"%s\" to proceed": "Klik på \"%s\" for at fortsætte",
"Please confirm that you would like to re-encrypt your complete database.": "Bekræft venligst at du ønsker at genkryptere hele din database.",
"Please enter your password in the master key list below before upgrading the key.": "Indtast din adgangskode i hovednøglelisten nedenfor før opgradering af nøglen.",
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Bemærk, at hvis der er tale om en stor notesbog, kan det tage et par minutter, før alle noter vises på modtagerens enhed.",
@@ -588,6 +595,9 @@
"Previous versions of this note": "Tidligere udgaver af denne note",
"Print": "Udskriv",
"Privacy Policy": "Privatlivspolitik",
"Process failed payment subscriptions": "Behandl mislykkede betalingsabonnementer",
"Process oversized accounts": "Behandl for store konti",
"Process user deletions": "Behandl brugersletninger",
"Profile": "Profil",
"Profile Version: %s": "Profil-version: %s",
"Properties": "Egenskaber",
@@ -651,6 +661,8 @@
"See the pre-release page for more details: %s": "Se for-udgivelsessiden for yderligere information: %s",
"Select": "Vælg",
"Select all": "Vælg alt",
"Select emoji...": "Vælg emoji...",
"Select file...": "Vælg fil...",
"Server is already running on port %d": "Serveren kører allerede på port %d",
"Server is not running.": "Serveren kører ikke.",
"Server is running on port %d": "Serveren kører på port %d",
@@ -842,6 +854,7 @@
"Untitled": "Uden titel",
"Update": "Opdater",
"Update profile": "Opdater profil",
"Update total sizes": "Opdater samlede størrelser",
"Updated": "Opdateret",
"updated date": "opdateringsdato",
"Updated local items: %d.": "Opdaterede lokale emner: %d.",
@@ -892,7 +905,6 @@
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Du kan bruge værktøjet nedenfor til at genkryptere dine data for eksempel hvis du ved at nogle af dine noter er krypteret med en forældet krypteringsmetode.",
"Your choice: ": "Dit valg: ",
"Your data is going to be re-encrypted and synced again.": "Dine data bliver genkrypteret og synkroniseret igen.",
"Your master password is needed to decrypt some of your data.": "Din hovedadgangskode er nødvendig for at dekryptere nogle af dine data.",
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Din adgangskode er nødvendig for at dekryptere nogle af dine data. Tast `:e2ee decrypt` for at indstille den.",
"Your permission to use your camera is required.": "Din tilladelse til at bruge kameraet er påkrævet.",
"Your version: %s": "Din version: %s",

View File

@@ -58,6 +58,7 @@
"All notes": "Alle Notizen",
"All potential ports are in use - please report the issue at %s": "Alle potentielle Port-Nummern sind in Verwendung - bitte melde dieses Problem auf %s",
"Also displays unset and hidden config variables.": "Zeigt auch nicht gesetzte und versteckte Konfigurationsvariablen an.",
"Also publish linked notes": "Auch verlinkte Notizen veröffentlichen",
"Always": "Immer",
"An update is available, do you want to download it now?": "Es ist eine Aktualisierung verfügbar. Soll sie jetzt heruntergeladen werden?",
"Appearance": "Erscheinungsbild",
@@ -79,6 +80,7 @@
"Authentication was not completed (did not receive an authentication token).": "Authentifizierung wurde nicht abgeschlossen (keinen Authentifizierung-Token erhalten).",
"Authorisation token:": "Berechtigungstoken:",
"Auto": "Automatisch",
"Auto-add disabled accounts for deletion": "Automatisches Hinzufügen deaktivierter Accounts zur Löschliste",
"Auto-pair braces, parenthesis, quotations, etc.": "Automatisches Hinzufügen von geschweiften Klammern, runden Klammern, Anführungszeichen usw.",
"Automatically check for updates": "Automatisch auf Aktualisierungen prüfen",
"Automatically switch theme to match system theme": "Automatisch das Design ändern, um es dem System-Design anzupassen",
@@ -101,8 +103,8 @@
"Cannot move note to \"%s\" notebook": "Kann Notiz nicht in das Notizbuch „%s“ verschieben",
"Cannot move notebook to this location": "Kann Notizbuch nicht an diesen Ort verschieben",
"Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.": "Kann Token nicht erneuern: Authentifikationsdaten nicht vorhanden. Ein Neustart der Synchronisation könnte das Problem beheben.",
"Cannot save %s \"%s\" because it is larger than the allowed limit (%s)": "Kann %s \"%s\" nicht speichern, da es größer ist als das erlaubte Limit (%s)",
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) for this account": "Kann %s \"%s\" nicht speichern, da es die zulässige Gesamtgröße (%s) für dieses Konto überschreiten würde",
"Cannot save %s \"%s\" because it is larger than the allowed limit (%s)": "Kann %s „%s“ nicht speichern, da es größer ist als das erlaubte Limit (%s)",
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) for this account": "Kann %s „%s“ nicht speichern, da es die zulässige Gesamtgröße (%s) für dieses Konto überschreiten würde",
"Cannot share encrypted notebook with recipient %s because they have not enabled end-to-end encryption. They may do so from the screen Configuration > Encryption.": "Das verschlüsselte Notizbuch kann nicht mit %s geteilt werden, da die Person die Ende-zu-Ende Verschlüsselung nicht aktiviert hat. Dies ist unter Optionen > Verschlüsselung möglich.",
"Change application layout": "Anwendungs-Layout ändern",
"Change language": "Sprache ändern",
@@ -160,10 +162,12 @@
"Could not connect to plugin repository.": "Konnte keine Verbindung zum Plugin-Repository herstellen.",
"Could not export notes: %s": "Konnte Notizen nicht exportieren: %s",
"Could not install plugin: %s": "Erweiterung konnte nicht installiert werden: %s",
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Die Einladung konnte nicht beantwortet werden. Bitte versuche es erneut, oder prüfe, ob der Notizbuch-Besitzer dieses noch freigibt.\n\nDer Fehler war: \"%s\"",
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Die Einladung konnte nicht beantwortet werden. Bitte versuche es erneut, oder prüfe, ob der Notizbuch-Besitzer dieses noch freigibt.\n\nDer Fehler war: „%s“",
"Could not upgrade master key: %s": "Konnte Hauptschlüssel nicht aktualisieren: %s",
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Der Freigabestatus dieses Notizbuchs konnte nicht überprüft werden - Vorgang wird abgebrochen. Bitte versuche es erneut, wenn eine Internetverbindung besteht.",
"Create": "Erstellen",
"Create a notebook": "Notizbuch erstellen",
"Create new profile...": "Neues Profil erstellen...",
"Create notebook": "Notizbuch erstellen",
"Create user": "Benutzer erstellen",
"Created": "Erstellt",
@@ -257,12 +261,14 @@
"Edit in external editor": "Im externen Editor bearbeiten",
"Edit note.": "Notiz bearbeiten.",
"Edit notebook": "Notizbuch bearbeiten",
"Edit profile configuration...": "Profilkonfiguration bearbeiten...",
"Editor": "Editor",
"Editor font": "Schriftart im Editor",
"Editor font family": "Schriftfamilie im Editor",
"Editor font size": "Schriftgröße im Editor",
"Editor maximum width": "Maximale Breite des Editors",
"Editor monospace font family": "Nichtproportionale Schriftfamilie im Editor",
"Editor: %s": "Editor: %s",
"Either \"text\" or \"json\"": "Entweder „text“ oder „json“",
"Emacs": "Emacs",
"Email": "E-Mail",
@@ -332,6 +338,7 @@
"Fetching resources: %d/%d": "Anhänge abrufen: %d/%d",
"File": "Datei",
"File system": "Dateisystem",
"Filter tags": "Tags filtern",
"Firefox Extension": "Firefox-Erweiterung",
"Fix search index": "Suchindex reparieren",
"Fixing search index...": "Suchindex wird repariert ...",
@@ -373,6 +380,7 @@
"Idle": "Wartend",
"Ignore": "Ignorieren",
"Ignore TLS certificate errors": "TLS-Zertifikatfehler ignorieren",
"Images": "Bilder",
"Import": "Importieren",
"Importing from \"%s\" as \"%s\" format. Please wait...": "Aus „%s“ im Format „%s“ wird importiert. Bitte warten ...",
"Importing notes...": "Notizen werden importiert ...",
@@ -552,6 +560,7 @@
"Operation cancelled": "Vorgang abgebrochen",
"Options": "Optionen",
"Or create an account.": "Oder erstelle ein Konto.",
"Other applications...": "Andere Anwendungen...",
"Output format: %s": "Ausgabeformat: %s",
"Page orientation for PDF export": "Seitenausrichtung für den PDF-Export",
"Page size for PDF export": "Seitengröße für den PDF-Export",
@@ -564,7 +573,7 @@
"PDF File": "PDF-Datei",
"Permission needed": "Berechtigung benötigt",
"Permission to use camera": "Berechtigung zur Verwendung der Kamera",
"Please click on \"%s\" to proceed": "Klicke bitte auf \"%s\" um fortzufahren",
"Please click on \"%s\" to proceed, or set the passwords in the \"%s\" list below.": "Bitte klicke auf „%s“, um fortzufahren, oder trage die Passwörter in die Liste „%s“ unten ein.",
"Please confirm that you would like to re-encrypt your complete database.": "Bitte bestätige, dass du deine gesamte Datenbank neu verschlüsseln möchtest.",
"Please enter your password in the master key list below before upgrading the key.": "Bitte gib zuerst dein Passwort in der unten stehenden Liste der Hauptschlüssel ein, bevor du den Schlüssel aktualisierst.",
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Wenn das Notizbuch sehr groß ist, kann es ein paar Minuten dauern, bis alle Notizen auf dem Empfängergerät erscheinen.",
@@ -597,6 +606,7 @@
"Process oversized accounts": "Verarbeite zu große Konten",
"Process user deletions": "Verarbeite Benutzerlöschungen",
"Profile": "Profil",
"Profile name:": "Profilname:",
"Profile Version: %s": "Profil-Version: %s",
"Properties": "Eigenschaften",
"Public-private key pair:": "Öffentlich-Privates Schlüsselpaar:",
@@ -646,6 +656,7 @@
"Safe mode is currently active. Note rendering and all plugins are temporarily disabled.": "Sicherer Modus aktiv. Notizdarstellung und alle Plugins werden vorübergehend deaktiviert.",
"Save": "Speichern",
"Save alarm": "Alarm speichern",
"Save as %s": "Speichern unter %s",
"Save as...": "Speichern unter ...",
"Save changes": "Änderungen speichern",
"Save geo-location with notes": "Momentanen Standort zusammen mit Notizen speichern",
@@ -659,6 +670,8 @@
"See the pre-release page for more details: %s": "Weitere Informationen findest Du auf der Vorabversionsseite: %s",
"Select": "Auswählen",
"Select all": "Alles auswählen",
"Select emoji...": "Emoji auswählen...",
"Select file...": "Datei auswählen...",
"Server is already running on port %d": "Server läuft bereits auf Port %d",
"Server is not running.": "Server läuft nicht.",
"Server is running on port %d": "Server läuft auf Port %d",
@@ -723,7 +736,9 @@
"Swap line down": "Zeile nach unten verschieben",
"Swap line up": "Zeile nach oben verschieben",
"Switch between note and to-do type": "Zwischen Notiz- und Aufgaben-Typ wechseln",
"Switch profile": "Profil wechseln",
"Switch to note type": "Zu Notiz-Typ wechseln",
"Switch to profile %d": "Wechsle zu Profil %d",
"Switch to to-do type": "Zu Aufgaben-Typ wechseln",
"Switches to [notebook] - all further operations will happen within this notebook.": "Wechselt zu [notebook] - alle weiteren Aktionen werden in diesem Notizbuch ausgeführt.",
"Sync Status": "Synchronisationsstatus",
@@ -745,6 +760,7 @@
"Synchronising...": "Wird synchronisiert ...",
"Synchronizing...": "Wird synchronisiert ...",
"Tabloid": "Tabloid",
"tag1, tag2, ...": "tag1, tag2, ...",
"Tagged: %d.": "Verschlagwortet: %d.",
"Tags": "Schlagwörter",
"Take photo": "Foto aufnehmen",
@@ -752,6 +768,7 @@
"Text editor command": "Texteditor-Befehl",
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Vielen Dank! Das Joplin Cloud-Konto ist nun eingerichtet und einsatzbereit.",
"The app is now going to close. Please relaunch it to complete the process.": "Die App wird nun geschlossen. Bitte starte sie erneut, um den Vorgang abzuschließen.",
"The application did not close properly. Would you like to start in safe mode?": "Die Anwendung wurde nicht ordnungsgemäß geschlossen. Im abgesicherten Modus starten?",
"The application has been authorised - you may now close this browser tab.": "Die Anwendung wurde autorisiert - du kannst diesen Browsertab nun schließen.",
"The application has been authorised!": "Die Anwendung wurde erfolgreich autorisiert!",
"The application has been successfully authorised.": "Die Anwendung wurde erfolgreich autorisiert.",
@@ -774,7 +791,7 @@
"The notebook could not be saved: %s": "Dieses Notizbuch konnte nicht gespeichert werden: %s",
"The notes have been imported: %s": "Die Notizen wurden importiert: %s",
"The possible commands are:": "Die möglichen Befehle sind:",
"The recipient could not be removed from the list. Please try again.\n\nThe error was: \"%s\"": "Der Empfänger konnte nicht von der Liste entfernt werden. Bitte versuche es erneut.\n\nDer Fehler war: \"%s\"",
"The recipient could not be removed from the list. Please try again.\n\nThe error was: \"%s\"": "Der Empfänger konnte nicht von der Liste entfernt werden. Bitte versuche es erneut.\n\nDer Fehler war: „%s“",
"The sync target needs to be upgraded before Joplin can sync. The operation may take a few minutes to complete and the app needs to be restarted. To proceed please click on the link.": "Das Synchronisationsziel muss aktualisiert werden, bevor Joplin synchronisiert werden kann. Der Vorgang kann einige Minuten dauern, und die App muss neu gestartet werden. Um fortzufahren, klicke bitte auf den Link.",
"The sync target needs to be upgraded. Press this banner to proceed.": "Das Synchronisationsziel muss aktualisiert werden! Klicke auf diesen Banner, um fortzufahren.",
"The tag \"%s\" already exists. Please choose a different name.": "Das Schlagwort „%s“ existiert bereits. Bitte wähle einen anderen Namen.",
@@ -901,7 +918,7 @@
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Du kannst das folgende Werkzeug verwenden, um deine Daten erneut zu verschlüsseln, falls einige deiner Notizen mit einer veralteten Verschlüsselungsmethode verschlüsselt sein sollten.",
"Your choice: ": "Deine Auswahl: ",
"Your data is going to be re-encrypted and synced again.": "Deine Daten werden neu verschlüsselt und erneut synchronisiert.",
"Your master password is needed to decrypt some of your data.": "Dein Master-Passwort ist nötig um einige deiner Daten zu entschlüsseln.",
"Your password is needed to decrypt some of your data.": "Dein Passwort ist nötig, um einige deiner Daten zu entschlüsseln.",
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Dein Passwort ist nötig um einige deiner Daten zu entschlüsseln. Tippe „:e2ee decrypt“ um es zu setzen.",
"Your permission to use your camera is required.": "Deine Zustimmung zur Verwendung deiner Kamera ist erforderlich.",
"Your version: %s": "Deine Version: %s",

View File

@@ -7,6 +7,7 @@
"%d notes match this pattern. Delete them?": "%d σημειώσεις ταιριάζουν. Θέλετε να διαγραφούν;",
"%s %s (%s, %s)": "%s %s (%s, %s)",
"%s (%s) could not be uploaded: %s": "%s (%s) δεν μπορούν να ανέβουν: %s",
"%s (%s) would like to share a notebook with you.": "%s (%s) θα ήθελε να μοιραστεί μαζί σας ένα σημειωματάριο.",
"%s (%s): %s": "%s (%s): %s",
"%s (pre-release)": "%s (pre-release)",
"%s - Copy": "%s - Αντίγραφο",
@@ -26,6 +27,7 @@
"&Tools": "&Εργαλεία",
"&View": "&Εμφάνιση",
"(%s)": "(%s)",
"(None)": "(Κανένας)",
"(wysiwyg: %s)": "(wysiwyg: %s)",
"- Camera: to allow taking a picture and attaching it to a note.": "- Φωτογραφική μηχανή: για να μπορείτε να τραβήξετε μια φωτογραφία και να την προσαρτήσετε σε μια σημείωση.",
"- Location: to allow attaching geo-location information to a note.": "- Τοποθεσία: να επιτρέπεται η επισύναψη πληροφοριών γεωγραφικής θέσης σε μια σημείωση.",
@@ -39,13 +41,18 @@
"accelerator": "επιταχυντής",
"Accelerator \"%s\" is not valid.": "Ο επιταχυντής \"%s\" δεν είναι έγκυρος.",
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to unexpected behaviour.": "Ο επιταχυντής \"%s\" χρησιμοποιείται για εντολές \"%s\" και \"%s\". Αυτό μπορεί να οδηγήσει σε απροσδόκητη συμπεριφορά.",
"Accept": "Αποδοχή",
"Action": "Ενέργεια",
"Actions": "Ενέργειες",
"Active": "Ενεργό",
"Actual Size": "Πραγματικό μέγεθος",
"Add body": "Προσθήκη σώματος κειμένου",
"Add or remove tags:": "Προσθήκη ή διαγραφή ετικετών:",
"Add recipient:": "Προσθήκη παραλήπτη:",
"Add title": "Προσθήκη τίτλου",
"Add to dictionary": "Add to dictionary",
"Admin": "Διαχειριστής",
"Admin dashboard": "Πίνακας ελέγχου διαχειριστή",
"Advanced options": "Επιλογές για προχωρημένους",
"Advanced tools": "Προηγμένα εργαλεία",
"All notes": "Όλες οι σημειώσεις",
@@ -63,6 +70,7 @@
"Attach photo": "Επισυνάψτε μία φωτογραφία",
"Attach...": "Επισύναψη...",
"Attaches the given file to the note.": "Προσθέτει το καθορισμένο αρχείο στη σημείωση.",
"attachment": "επισυναπτόμενο",
"Attachment conflict: \"%s\"": "Διένεξη συνημμένου: \"%s\"",
"Attachment download behaviour": "Συμπεριφορά λήψης συνημμένων",
"Attachments": "Επισυναπτόμενα",
@@ -71,7 +79,9 @@
"Authentication was not completed (did not receive an authentication token).": "Ο έλεγχος ταυτότητας δεν ολοκληρώθηκε (δεν παρελήφθει κανένα authentication token).",
"Authorisation token:": "Token εξουσιοδότησης:",
"Auto": "Αυτόματα",
"Auto-add disabled accounts for deletion": "Αυτόματη προσθήκη απενεργοποιημένων λογαριασμών για διαγραφή",
"Auto-pair braces, parenthesis, quotations, etc.": "Αυτόματη-σύζευξη αγκίστρων, παρενθέσεων, εισαγωγικών, κ.λπ.",
"Automatically check for updates": "Αυτόματος έλεγχος για ενημερώσεις",
"Automatically switch theme to match system theme": "Αυτόματη εναλλαγή θέματος ώστε να ταιριάζει με το θέμα συστήματος",
"Back": "Πίσω",
"Bold": "Έντονη γραφή",
@@ -92,6 +102,9 @@
"Cannot move note to \"%s\" notebook": "Δεν είναι δυνατή η μετακίνηση της σημείωσης στο σημειωματάριο \"%s\"",
"Cannot move notebook to this location": "Δεν είναι δυνατή η μετακίνηση του σημειωματάριου σε αυτήν τη θέση",
"Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.": "Δεν είναι δυνατή η ανανέωση του token: δεν υπάρχουν δεδομένα ελέγχου ταυτότητας. Η επανεκκίνηση του συγχρονισμού, μπορεί να διορθώσει το πρόβλημα.",
"Cannot save %s \"%s\" because it is larger than the allowed limit (%s)": "Δεν είναι δυνατή η αποθήκευση του %s \"%s\" επειδή είναι μεγαλύτερο από το επιτρεπόμενο όριο (%s)",
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) for this account": "Δεν είναι δυνατή η αποθήκευση του %s \"%s\" επειδή υπερβαίνει το συνολικό επιτρεπόμενο μέγεθος (%s) για αυτόν τον λογαριασμό",
"Cannot share encrypted notebook with recipient %s because they have not enabled end-to-end encryption. They may do so from the screen Configuration > Encryption.": "Δεν είναι δυνατή η κοινή χρήση κρυπτογραφημένου σημειωματάριου με τον παραλήπτη %s επειδή δεν έχει ενεργοποιήσει την κρυπτογράφηση από άκρο σε άκρο. Μπορούν να το κάνουν από την οθόνη Διαμόρφωση > Κρυπτογράφηση.",
"Change application layout": "Αλλαγή διάταξης εφαρμογής",
"Change language": "Αλλαγή γλώσσας",
"Characters": "Χαρακτήρες",
@@ -113,22 +126,31 @@
"Code": "Κώδικας",
"Code Block": "Μπλοκ Κώδικα",
"Code View": "Προβολή κώδικα",
"Collaborate on notebooks with others": "Συνεργασία σε σημειωματάρια με άλλους",
"Coming alarms": "Προσεχείς ειδοποιήσεις",
"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\".": "Λίστα διαδρομών από φακέλους, διαχωρισμένων με κόμμα, για τη φόρτωση των πιστοποιητικών, ή διαδρομή των μεμονωμένων αρχείων πιστοποιητικών. Για παράδειγμα: /my/cert_dir, /other/custom.pem. Σημειώστε ότι αν κάνετε αλλαγές στις ρυθμίσεις TLS, πρέπει να αποθηκεύσετε τις αλλαγές σας πριν κάνετε κλικ στο \"Έλεγχος ρύθμισης παραμέτρων συγχρονισμού\".",
"command": "εντολή",
"Command": "Εντολή",
"Command palette": "Παλέτα εντολών",
"Command palette...": "Παλέτα εντολών...",
"Completed": "Ολοκληρώθηκε",
"Completed decryption.": "Η αποκρυπτογράφηση ολοκληρώθηκε.",
"Completed: %s (%s)": "Ολοκληρώθηκε: %s (%s)",
"Compress old changes": "Συμπίεση παλαιών αλλαγών",
"Configuration": "Ρυθμίσεις",
"Confirm password cannot be empty": "Η επιβεβαίωση του κωδικού πρόσβασης δεν μπορεί να είναι κενή",
"Confirm password:": "Επιβεβαίωση κωδικού πρόσβασης:",
"Confirmation": "Επιβεβαίωση",
"Conflicted: %d": "Με διένεξη: %d",
"Conflicts": "Διενέξεις",
"Conflicts (attachments)": "Διενέξεις (συνημμένα)",
"Content provided by %s": "Περιεχόμενο που παρέχεται από %s",
"Convert to note": "Μετατροπή σε σημείωση",
"Convert to todo": "Μετατροπή σε to-do",
"Copy": "Αντιγραφή",
"Copy dev mode command to clipboard": "Αντιγραφή dev mode εντολής στο πρόχειρο",
"Copy external link": "Αντιγραφή εξωτερικού συνδέσμου",
"Copy image": "Αντιγραφή εικόνας",
"Copy Link Address": "Αντιγραφή διεύθυνσης σύνδεσης",
"Copy Markdown link": "Αντιγραφή συνδέσμου Markdown",
"Copy path to clipboard": "Αντιγραφή διαδρομής στο πρόχειρο",
@@ -136,15 +158,21 @@
"Copy token": "Αντιγραφή token",
"Could not authorise application:\n\n%s\n\nPlease try again.": "Δεν ήταν δυνατή η εξουσιοδότηση της εφαρμογής:\n\n%s\n\nΠαρακαλώ προσπαθήστε ξανά.",
"Could not connect to Joplin Server. Please check the Synchronisation options in the config screen. Full error was:\n\n%s": "Δεν ήταν δυνατή η σύνδεση με το διακομιστή Joplin. Ελέγξτε τις επιλογές συγχρονισμού στην οθόνη ρύθμισης παραμέτρων. Το πλήρες σφάλμα ήταν:\n\n%s",
"Could not connect to plugin repository.": "Δεν ήταν δυνατή η σύνδεση στο plugin repository.",
"Could not export notes: %s": "Δεν ήταν δυνατή η εξαγωγή σημειώσεων: %s",
"Could not install plugin: %s": "Δεν ήταν δυνατή η εγκατάσταση του plugin: %s",
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Δεν μπόρεσε να ανταποκριθεί στην πρόσκληση. Προσπαθήστε ξανά ή επικοινωνήστε με τον ιδιοκτήτη του φορητού υπολογιστή, αν εξακολουθεί να το μοιράζεται.\n\nΤο σφάλμα ήταν: \"%s\"",
"Could not upgrade master key: %s": "Δεν ήταν δυνατή η αναβάθμιση του master κλειδιού: %s",
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Δεν ήταν δυνατή η επαλήθευση της κατάστασης κοινής χρήσης αυτού του φορητού υπολογιστή - διακοπή. Προσπαθήστε ξανά όταν συνδεθείτε στο διαδίκτυο.",
"Create a notebook": "Δημιουργία σημειωματάριου",
"Create notebook": "Δημιουργία σημειωματαρίου",
"Create user": "Δημιουργία χρήστη",
"Created": "Δημιουργήθηκε",
"created date": "ημερομηνία δημιουργίας",
"Created local items: %d.": "Δημιουργημένα τοπικά στοιχεία: %d.",
"Created locally": "Δημιουργήθηκε τοπικά",
"Created remote items: %d.": "Δημιουργημένα απομακρυσμένα στοιχεία: %d.",
"Created: ": "Δημιουργήθηκε: ",
"Created: %d.": "Δημιουργήθηκε: %d.",
"Created: %s": "Δημιουργήθηκε: %s",
"Creates a new note.": "Δημιουργεί μία νέα σημείωση.",
@@ -160,7 +188,9 @@
"Custom TLS certificates": "Custom πιστοποιητικά TLS",
"Cut": "Αποκοπή",
"Dark": "Dark",
"Dashboard": "Οθόνη Διαχείρισης",
"Database v%s": "Βάση Δεδομένων v%s",
"Date": "Ημερομηνία",
"Date format": "Μορφή ημερομηνίας",
"days": "ημέρες",
"Decrypted items: %d": "Αποκρυπτογραφημένα στοιχεία: %d",
@@ -170,6 +200,8 @@
"Default: %s": "Προεπιλογή: %s",
"Delete": "Διαγραφή",
"Delete attachment \"%s\"?": "Διαγραφή συνημμένου \"%s\";",
"Delete expired sessions": "Διαγραφή ληγμένων συνεδριών",
"Delete expired tokens": "Διαγραφή των ληγμένων token",
"Delete line": "Διαγραφή γραμμής",
"Delete local data and re-download from sync target": "Διαγραφή τοπικών δεδομένων και εκ νέου λήψη από τον προορισμό",
"Delete note \"%s\"?": "Διαγραφή σημείωσης \"%s\";",
@@ -178,6 +210,7 @@
"Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.": "Διαγραφή σημειωματάριου; Όλες οι σημειώσεις και τα υπο-σημειωματάρια θα διαγραφούν επίσης.",
"Delete plugin \"%s\"?": "Διαγραφή plugin \"%s\";",
"Delete these %d notes?": "Διαγραφή των σημειώσεων %d;",
"Delete this invitation? The recipient will no longer have access to this shared notebook.": "Να διαγραφεί αυτή την πρόσκληση; Ο παραλήπτης δεν θα έχει πλέον πρόσβαση σε αυτό το κοινόχρηστο σημειωματάριο.",
"Deleted local items: %d.": "Διαγραμμένα τοπικά στοιχεία: %d.",
"Deleted remote items: %d.": "Διαγραμμένα απομακρυσμένα στοιχεία: %d.",
"Deletes the given notebook.": "Διαγράφει το καθορισμένο σημειωματάριο.",
@@ -187,6 +220,7 @@
"Destination format: %s": "Μορφή προορισμού: %s",
"Directory": "Φάκελος",
"Directory to synchronise with (absolute path)": "Φάκελος για συγχρονισμό (absolute path)",
"Disable": "Απενεργοποίηση",
"Disable encryption": "Απενεργοποίηση κρυπτογράφησης",
"Disable safe mode and restart": "Απενεργοποίηση ασφαλούς λειτουργίας και επανεκκίνηση",
"Disable Web Clipper Service": "Απενεργοποίηση υπηρεσίας Web Clipper",
@@ -205,6 +239,7 @@
"Displays version information": "Εμφανίζει πληροφορίες έκδοσης",
"Do it now": "Κάνε το τώρα",
"Do not ask for confirmation.": "Χωρίς να ζητείται επιβεβαίωση.",
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": "Μην χάσετε τον κωδικό πρόσβασης, καθώς, για λόγους ασφαλείας, αυτός θα είναι ο *μόνος* τρόπος αποκρυπτογράφησης των δεδομένων! Για να ενεργοποιήσετε την κρυπτογράφηση, εισαγάγετε τον κωδικό πρόσβασής σας παρακάτω.",
"Download": "Λήψη",
"Download and install the relevant extension for your browser:": "Κάντε λήψη και εγκατάσταση της αντίστοιχης επέκτασης για το πρόγραμμα περιήγησής σας:",
"Downloaded": "Έχουν ληφθεί",
@@ -227,9 +262,12 @@
"Editor font": "Γραμματοσειρά editor",
"Editor font family": "Οικογένεια γραμματοσειράς editor",
"Editor font size": "Μέγεθος γραμματοσειράς editor",
"Editor maximum width": "Μέγιστο πλάτος editor",
"Editor monospace font family": "Οικογένεια μονοδιαστημικών γραμματοσειρών επεξεργαστή",
"Either \"text\" or \"json\"": "Είτε \"text\" ή \"json\"",
"Emacs": "Emacs",
"Email": "Email",
"Emails": "Emails",
"emphasised text": "κείμενο με έμφαση",
"Enable": "Ενεργό",
"Enable ++insert++ syntax": "Ενεργοποίηση ++insert++ syntax",
@@ -255,12 +293,16 @@
"Enable Web Clipper Service": "Ενεργοποίηση υπηρεσίας Web Clipper",
"Enable ~sub~ syntax": "Ενεργοποίηση ~sub~ syntax",
"Enabled": "Ενεργοποιημένη",
"Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target.": "Ενεργοποίηση κρυπτογράφησης σημαίνει *όλες* οι σημειώσεις και τα συνημμένα σας πρόκειται να συγχρονιστούν εκ νέου και να σταλούν κρυπτογραφημένα στο στόχο συγχρονισμού.",
"Encrypted": "Κρυπτογραφημένο",
"Encrypted items cannot be modified": "Δεν είναι δυνατή η τροποποίηση κρυπτογραφημένων στοιχείων",
"Encrypted notebooks cannot be renamed": "Τα κρυπτογραφημένα σημειωματάρια δεν μπορούν να μετονομαστούν",
"Encryption": "Κρυπτογράφηση",
"Encryption Config": "Ρυθμίσεις κρυπτογράφησης",
"Encryption is: %s": "Η κρυπτογράφηση είναι: %s",
"Encryption keys": "Κλειδιά κρυπτογράφησης",
"Encryption:": "Κρυπτογράφηση:",
"End-to-end encryption": "Κρυπτογράφηση από άκρο σε άκρο",
"Enter code here": "Βάλτε τον κωδικό εδώ",
"Enter master password:": "Πληκτρολογήστε τον κωδικό πρόσβασης:",
"Enter notebook title": "Δώστε τον τίτλο του σημειωματάριου",
@@ -286,10 +328,11 @@
"Fail-safe": "Ασφαλής",
"Fail-safe: Do not wipe out local data when sync target is empty (often the result of a misconfiguration or bug)": "Ασφάλεια: Mην διαγράφετε τα τοπικά δεδομένα όταν ο προορισμός συγχρονισμού είναι κενός (συχνά το αποτέλεσμα μιας εσφαλμένης ρύθμισης παραμέτρων ή ενός σφάλματος)",
"Fatal error:": "Ανεπανόρθωτο σφάλμα:",
"Feature flags": "Feature flags",
"Fetched items: %d/%d.": "Ληφθέντα στοιχεία: %d/%d.",
"Fetching resources: %d/%d": "Λήψη πόρων: %d/%d",
"File": "Αρχείο",
"File system": "File system",
"File system": "Σύστημα Αρχείων",
"Firefox Extension": "Επέκταση Firefox",
"Fix search index": "Διόρθωση ευρετηρίων αναζήτησης",
"Fixing search index...": "Διόρθωση ευρετηρίων αναζήτησης...",
@@ -301,22 +344,27 @@
"For information on how to customise the shortcuts please visit %s": "Για πληροφορίες σχετικά με τον τρόπο προσαρμογής των συντομεύσεων, επισκεφτείτε το %s",
"For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:": "Για περισσότερες πληροφορίες σχετικά με την κρυπτογράφηση End-To-End (E2EE) και συμβουλές σχετικά με τον τρόπο ενεργοποίησής της, ελέγξτε το εγχειρίδιο:",
"For the list of keyboard shortcuts and config options, type `help keymap`": "Για τη λίστα των συντομεύσεων πληκτρολογίου και των ρυθμίσεων, πληκτρολογήστε `help keymap`",
"Force path style": "Επιβολή path style",
"Forward": "Μπροστά",
"Found: %d.": "Βρέθηκε: %d.",
"FTS enabled: %d": "FTS ενεργοποιημένο: %d",
"Full changelog": "Πλήρες αρχείο αλλαγών",
"General": "Γενικά",
"Generated": "Δημιουργήθηκε",
"Generating link...": "Δημιουργία συνδέσμου...",
"Get it now:": "Λήψη τώρα:",
"Get pre-releases when checking for updates": "Λήψη προεκδόσεων κατά τον έλεγχο για ενημερώσεις",
"Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.": "Αποκτά ή ορίζει την τιμή μιας παραμέτρου. Αν δεν υπάρχει [τιμή], θα εμφανιστεί το [όνομα]. Εάν δεν υπάρχει ούτε [όνομα] ούτε [τιμή], θα εμφανιστεί η τρέχουσα παράμετρος.",
"Go to source URL": "Μεταβείτε στη διεύθυνση URL προέλευσης",
"Goto Anything...": "Γρήγορη Μετακίνηση...",
"Grant authorisation": "Έγκριση εξουσιοδότησης",
"Heading": "Επικεφαλίδα",
"Help": "Βοήθεια",
"Hide %s": "Απόκρυψη %s",
"Hide disabled keys": "Απόκρυψη απενεργοποιημένων κλειδιών",
"Hide Joplin": "Κρύψε το Joplin",
"Highlight": "Επισήμανση",
"Home": "Home",
"Horizontal Rule": "Οριζόντια Διαγράμμιση",
"HTML Directory": "Φάκελος HTML",
"HTML File": "Αρχείο HTML",
@@ -326,6 +374,7 @@
"Idle": "Αδρανής",
"Ignore": "Παράβλεψη",
"Ignore TLS certificate errors": "Παράβλεψη σφαλμάτων πιστοποιητικού TLS",
"Images": "Εικόνες",
"Import": "Εισαγωγή",
"Importing from \"%s\" as \"%s\" format. Please wait...": "Εισαγωγή από \"%s\" σε μορφή \"%s\". Παρακαλώ περιμένετε...",
"Importing notes...": "Εισαγωγή σημειώσεων...",
@@ -354,30 +403,40 @@
"Invalid answer: %s": "Μη έγκυρη απάντηση: %s",
"Invalid command: \"%s\"": "Ακυρη εντολή: \"%s\"",
"Invalid option value: \"%s\". Possible values are: %s.": "Άκυρη τιμή: \" %s\". Οι πιθανές τιμές είναι: %s.",
"Invalid password": "Μη έγκυρος κωδικός πρόσβασης",
"Italic": "Πλάγια γραφή",
"Item \"%s\" could not be downloaded: %s": "Το στοιχείο \"%s\" δεν μπορεί να κατέβει: %s",
"Items": "Στοιχεία",
"Items that cannot be decrypted": "Στοιχεία που δεν μπορούν αποκρυπτογραφηθούν",
"Items that cannot be synchronised": "Στοιχεία που δεν μπορούν να συγχρονιστούν",
"Joplin can synchronise your notes using various providers. Select one from the list below.": "Το Joplin μπορεί να συγχρονίσει τις σημειώσεις σας χρησιμοποιώντας διάφορους παρόχους. Επιλέξτε έναν από τον παρακάτω κατάλογο.",
"Joplin Cloud": "Joplin Cloud",
"Joplin Cloud email": "Joplin Cloud email",
"Joplin Cloud password": "Joplin Cloud password",
"Joplin Export Directory": "Φάκελος εξαγωγής Joplin",
"Joplin Export File": "Αρχείο εξαγωγής Joplin",
"Joplin failed to decrypt these items multiple times, possibly because they are corrupted or too large. These items will remain on the device but Joplin will no longer attempt to decrypt them.": "Το Joplin απέτυχε να αποκρυπτογραφήσει αυτά τα στοιχεία πολλές φορές, πιθανώς επειδή είναι αλλοιωμένα ή πολύ μεγάλα. Αυτά τα στοιχεία θα παραμείνουν στη συσκευή, αλλά το Joplin δεν θα προσπαθήσει πλέον να τα αποκρυπτογραφήσει.",
"Joplin Forum": "Joplin φόρουμ",
"Joplin Server": "Joplin Server",
"Joplin Server email": "Joplin Server email",
"Joplin Server password": "Joplin Server password",
"Joplin Server URL": "Joplin Server URL",
"Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.": "Το Joplin Web Clipper επιτρέπει την αποθήκευση ιστοσελίδων και screenshots από το πρόγραμμα περιήγησης στο Joplin.",
"Joplin website": "Joplin website",
"Joplin's own sync service. Also gives access to Joplin-specific features such as publishing notes or collaborating on notebooks with others.": "Η υπηρεσία συγχρονισμού του Joplin. Παρέχει επίσης πρόσβαση σε ειδικές λειτουργίες του Joplin, όπως η δημοσίευση σημειώσεων ή η συνεργασία σε σημειωματάρια με άλλους.",
"Keep note history for": "Διατήρηση ιστορικού σημειώσεων για",
"Keyboard Mode": "Λειτουργία πληκτρολογίου",
"Keyboard Shortcut": "Συντόμευση πληκτρολογίου",
"Keyboard Shortcuts": "Συντομεύσεις πληκτρολογίου",
"Keychain Supported: %s": "Υποστηριζόμενη κλειδοθήκη: %s",
"Keys that need upgrading": "Κλειδιά που χρειάζονται αναβάθμιση",
"Landscape": "Οριζόντια",
"Language": "Γλώσσα",
"Last error: %s": "Τελευταίο σφάλμα: %s",
"Later": "Αργότερα",
"Layout": "Διάταξη",
"Layout button sequence": "Ακολουθία κουμπιών διάταξης",
"Leave notebook...": "Αφήστε το σημειωματάριο...",
"Legal": "Legal",
"Letter": "Letter",
"Light": "Light",
@@ -385,20 +444,32 @@
"Link has been copied to clipboard!": "Ο σύνδεσμος έχει αντιγραφεί στο πρόχειρο!",
"Links with protocol \"%s\" are not supported": "Σύνδεσμοι με πρωτόκολλο \"%s\" δεν υποστηρίζονται",
"List item": "Στοιχείο λίστας",
"Loaded": "Φορτωμένα",
"Location": "Τοποθεσία",
"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.": "Το αρχείο κλειδώματος έχει ήδη δημιουργηθεί. Αν γνωρίζετε ότι δεν πραγματοποιείται συγχρονισμός, μπορείτε να διαγράψετε το αρχείο κλειδώματος στο \"%s\" και να συνεχίσετε τη λειτουργία.",
"Log": "Log",
"Login": "Σύνδεση",
"Login below.": "Συνδέσου παρακάτω.",
"Login with Dropbox": "Είσοδος με Dropbox",
"Login with OneDrive": "Είσοδος με Onedrive",
"Logout": "Αποσύνδεση",
"Logs": "Αρχεία Καταγραφής",
"Make a donation": "Κάντε μια δωρεά",
"Manage master password": "Διαχείριση κύριου κωδικού πρόσβασης",
"Manage master password...": "Διαχείριση κύριου κωδικού πρόσβασης...",
"Manage your plugins": "Διαχείριση των επεκτάσεων",
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "Διαχειρίζεται τη διαμόρφωση E2EE. Οι εντολές είναι `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, και `target-status`.",
"Manual": "Χειροκίνητα",
"Markdown": "Markdown",
"Markdown + Front Matter": "Markdown + Front Matter",
"Marks a to-do as done.": "Επισημαίνει μία σημείωση to-do ως ολοκληρωμένη.",
"Marks a to-do as non-completed.": "Σήμανση ενός to-do σε μη-ολοκληρωμένο.",
"Markup": "Markup",
"Master Key %s": "Κύριο Κλειδί %s",
"Master password": "Κύριος κωδικός πρόσβασης",
"Master password:": "Κύριος κωδικός πρόσβασης:",
"Max concurrent connections": "Μέγιστος αριθμός ταυτόχρονων συνδέσεων",
"Missing keys": "Απουσία κλειδιών",
"Missing Master Keys": "Απουσία κυρίως κλειδιών",
"Missing required argument: %s": "Λείπει το απαιτούμενο όρισμα: %s",
"Mobile data - auto-sync disabled": "Δεδομένα κινητής τηλεφωνίας - ο αυτόματος συγχρονισμός απενεργοποιήθηκε",
@@ -439,6 +510,7 @@
"Nord": "Nord",
"Not authentified with %s. Please provide any missing credentials.": "Δεν έγινε επαλήθευση με το %s. Παρακαλώ καταχωρίστε τα διαπιστευτήρια που λείπουν.",
"Not downloaded": "Δεν έγινε λήψη",
"Not generated": "Δεν δημιουργήθηκαν",
"note": "σημείωση",
"Note": "Σημείωση",
"Note area growth factor": "Συντελεστής ανάπτυξης περιοχής σημειώσεων",
@@ -453,12 +525,15 @@
"Note list growth factor": "Συντελεστής ανάπτυξης λίστας σημειώσεων",
"Note properties": "Ιδιότητες σημείωσης",
"Note title": "Τίτλος σημειωματάριου",
"Note&book": "Note&book",
"Note: Does not work in all desktop environments.": "Σημείωση: Δεν λειτουργεί σε όλα τα γραφικά περιβάλλοντα.",
"Note: When a note is shared, it will no longer be encrypted on the server.": "Σημείωση: Όταν μια σημείωση είναι κοινόχρηστη, δεν θα κρυπτογραφείται πλέον στο διακομιστή.",
"Notebook": "Σημειωματάριo",
"Notebook list growth factor": "Συντελεστής ανάπτυξης λίστας σημειωματάριων",
"Notebook: %s": "Σημειωματάριο: %s",
"Notebooks": "Σημειωματάρια",
"Notebooks cannot be named \"%s\", which is a reserved title.": "Τα σημειωματάρια δεν μπορούν να ονομαστούν \"%s\", που είναι δεσμευμένος τίτλος.",
"Notes": "Σημειώσεις",
"Notes and settings are stored in: %s": "Οι σημειώσεις και οι ρυθμίσεις αποθηκεύονται στο: %s",
"Notes can only be created within a notebook.": "Σημειώσεις μπορούν να δημιουργηθούν μόνο μέσα σε σημειωματάριο.",
"Numbered List": "Αριθμημένη λίστα",
@@ -474,9 +549,12 @@
"Open": "Άνοιγμα",
"Open %s": "Άνοιγμα %s",
"Open profile directory": "Άνοιγμα φακέλου προφίλ",
"Open Sync Wizard...": "Άνοιγμα Οδηγού συγχρονισμού...",
"Open...": "Άνοιγμα...",
"Operation cancelled": "Η λειτουργία ακυρώθηκε",
"Options": "Επιλογές",
"Or create an account.": "Ή δημιουργείστε ένα λογαριασμό.",
"Other applications...": "Άλλες εφαρμογές...",
"Output format: %s": "Μορφή εξόδου: %s",
"Page orientation for PDF export": "Προσανατολισμός σελίδας για εξαγωγή PDF",
"Page size for PDF export": "Μέγεθος σελίδας για εξαγωγή PDF",
@@ -491,6 +569,7 @@
"Permission to use camera": "Άδεια χρήσης κάμερας",
"Please confirm that you would like to re-encrypt your complete database.": "Επιβεβαιώστε ότι θέλετε να ξανακρυπτογραφήσετε την πλήρη βάση δεδομένων σας.",
"Please enter your password in the master key list below before upgrading the key.": "Εισαγάγετε τον κωδικό πρόσβασής σας στη λίστα του master κλειδιού πριν αναβαθμίσετε το κλειδί.",
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Λάβετε υπόψη ότι αν πρόκειται για ένα μεγάλο σημειωματάριο, μπορεί να χρειαστούν μερικά λεπτά για να εμφανιστούν όλες οι σημειώσεις στη συσκευή του παραλήπτη.",
"Please open the following URL in your browser to authenticate the application. The application will create a directory in \"Apps/Joplin\" and will only read and write files in this directory. It will have no access to any files outside this directory nor to any other personal data. No data will be shared with any third party.": "Παρακαλώ ανοίξτε την ακόλουθη διεύθυνση URL στο πρόγραμμα περιήγησής σας για να πιστοποιήσετε την εφαρμογή. Η εφαρμογή θα δημιουργήσει έναν φάκελο στο \"Apps / Joplin\" και θα διαβάζει και θα γράφει μόνο αρχεία σε αυτόν. Δεν θα έχει πρόσβαση σε αρχεία εκτός αυτού του φακέλου ούτε σε άλλα προσωπικά δεδομένα. Δεν πρόκειται να κοινοποιηθούν δεδομένα σε τρίτους.",
"Please select a notebook first.": "Παρακαλώ επιλέξτε πρώτα σημειωματάριο.",
"Please select the note or notebook to be deleted first.": "Παρακαλώ πρώτα επιλέξτε τη σημείωση ή το σημειωματάριο που θέλετε να διαγραφτεί.",
@@ -516,16 +595,29 @@
"Previous versions of this note": "Προηγούμενες εκδόσεις αυτής της σημείωσης",
"Print": "Εκτύπωση",
"Privacy Policy": "Πολιτική Προστασίας Προσωπικών Δεδομένων",
"Process failed payment subscriptions": "Επεξεργασία αποτυχημένων συνδρομών πληρωμής",
"Process oversized accounts": "Επεξεργασία υπερμεγεθών λογαριασμών",
"Process user deletions": "Επεξεργασία διαγραφών χρηστών",
"Profile": "Προφιλ",
"Profile Version: %s": "Έκδοση προφίλ: %s",
"Properties": "Ιδιότητες",
"Public-private key pair:": "Ζεύγος δημόσιου-ιδιωτικού κλειδιού:",
"Publish note...": "Δημοσίευση σημείωσης...",
"Publish Notes": "Δημοσίευση σημειώσεων",
"Publish notes to the internet": "Δημοσίευση σημειώσεων στο διαδίκτυο",
"Quit": "Έξοδος",
"Re-encrypt data": "Επανακρυπτογράφηση δεδομένων",
"Re-encryption": "Επανα-κρυπτογράφηση",
"Re-upload local data to sync target": "Εκ νέου αποστολή τοπικών δεδομένων στον προορισμό για συγχρονισμό",
"Read more about it": "Διαβάστε περισσότερα σχετικά με αυτό",
"Read time: %s min": "Χρόνος ανάγνωσης: %s min",
"Recipient has accepted the invitation": "Ο παραλήπτης αποδέχθηκε την πρόσκληση",
"Recipient has not yet accepted the invitation": "Ο παραλήπτης δεν έχει ακόμη αποδεχθεί την πρόσκληση",
"Recipient has rejected the invitation": "Ο παραλήπτης απέρριψε την πρόσκληση",
"Recipients:": "Αποδέκτες:",
"Redo": "Επανάληψη",
"Refresh": "Ανανέωση",
"Reject": "Απόρριψη",
"Remove": "Κατάργηση",
"Remove tag \"%s\" from all notes?": "Κατάργηση της ετικέτας \"%s\" από όλες τις σημειώσεις;",
"Remove this search from the sidebar?": "Κατάργηση αυτής της αναζήτησης από την πλευρική μπάρα;",
@@ -534,6 +626,7 @@
"Rename tag:": "Μετονομασία ετικέτας:",
"Renames the given <item> (note or notebook) to <name>.": "Μετονομάζει το καθορισμένο <item> (σημείωση ή σημειωματάριο) στο <name>.",
"Renew token": "Ανανέωση token",
"Reset master password": "Επαναφορά κύριου κωδικού πρόσβασης",
"Resources: %d.": "Πόροι: %d.",
"Restart and upgrade": "Επανεκκίνηση και αναβάθμιση",
"Restart now": "Επανεκίνηση τώρα",
@@ -546,6 +639,12 @@
"Reverses the sorting order.": "Αντιστρέφει τη σειρά ταξινόμησης.",
"Revision: %s (%s)": "Αναθεώρηση: %s (%s)",
"Runs the commands contained in the text file. There should be one command per line.": "Εκτελεί τις εντολές που περιέχονται στο αρχείο κειμένου. Θα πρέπει να υπάρχει μία εντολή ανά γραμμή.",
"S3": "S3",
"S3 access key": "S3 access key",
"S3 bucket": "S3 bucket",
"S3 region": "S3 region",
"S3 secret key": "S3 secret key",
"S3 URL": "S3 URL",
"Safe mode is currently active. Note rendering and all plugins are temporarily disabled.": "Η ασφαλής λειτουργία είναι ενεργή αυτήν τη στιγμή. Σημειώστε ότι η απόδοση και όλα τα πρόσθετα απενεργοποιούνται προσωρινά.",
"Save": "Αποθήκευση",
"Save alarm": "Αποθήκευση ειδοποίησης",
@@ -560,23 +659,33 @@
"Search:": "Εύρεση:",
"Searches for the given <pattern> in all the notes.": "Αναζητά το καθορισμένο <pattern> σε όλες τις σημειώσεις.",
"See the pre-release page for more details: %s": "Δείτε τη σελίδα προέκδοσης για περισσότερες λεπτομέρειες: %s",
"Select": "Επιλογή",
"Select all": "Επιλογή όλων",
"Select emoji...": "Επιλογή emoji...",
"Select file...": "Επιλογή φακέλου...",
"Server is already running on port %d": "Ο διακομιστής εκτελείται ήδη στη θύρα %d",
"Server is not running.": "Ο διακομιστής δεν εκτελείται.",
"Server is running on port %d": "Ο διακομιστής εκτελείται στη θύρα %d",
"Set alarm": "Ορισμός υπενθύμισης",
"Set alarm:": "Ορισμός ειδοποίησης:",
"Set it to 0 to make it take the complete available space. Recommended width is 600.": "Ορίστε την τιμή 0 για να καταλάβει όλο τον διαθέσιμο χώρο. Το συνιστώμενο πλάτος είναι 600.",
"Set the password": "Ορισμός κωδικού πρόσβασης",
"Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s": "Ορίζει την ιδιότητα <name> του καθορισμένου <note> στην καθορισμένη [τιμή]. Πιθανές ιδιότητες είναι:\n\n%s",
"Share": "Κοινοποίηση",
"Share Notebook": "Κοινοποίηση Σημειωματάριου",
"Share notebook...": "Κοινοποίηση σημειωματάριου...",
"Sharing notebook...": "Κοινοποίηση σημειωματάριου...",
"Shortcuts are not available in CLI mode.": "Οι συντομεύσεις δεν είναι διαθέσιμες στη λειτουργία CLI.",
"Show Advanced Settings": "Εμφάνιση επιλογών για προχωρημένους",
"Show all": "Εμφάνιση όλων",
"Show completed to-dos": "Εμφάνιση ολοκληρωμένων to-dos",
"Show disabled keys": "Εμφάνιση απενεργοποιημένων κλειδιών",
"Show note counts": "Εμφάνιση πλήθους σημειώσεων",
"Show sort order buttons": "Εμφάνιση κουμπιών σειράς ταξινόμησης",
"Show tray icon": "Εμφάνιση εικονιδίου στη γραμμή εργασιών",
"Sidebar": "Πλευρική μπάρα",
"Size": "Μέγεθος",
"Skip this version": "Παραλείψτε αυτή την έκδοση",
"Skipped items: %d (use --retry-failed-items to retry decrypting them)": "Στοιχεία που έχουν παραλειφθεί: %d (χρήσιμοποίησε --retry-failed-items για επανάληψη της αποκρυπτογράφησης τους)",
"Skipped: %d.": "Παραλείφθηκε: %d.",
"Solarised Dark": "Solarised Dark",
@@ -589,6 +698,7 @@
"Sort selected lines": "Ταξινόμηση επιλεγμένων γραμμών",
"Sorts the item by <field> (eg. title, updated_time, created_time).": "Ταξινόμηση του στοιχείου με <field> (π.χ. τίτλος, ώρα_ενημέρωσης, ώρα_δημιουργίας).",
"Source format: %s": "Μορφή προέλευσης: %s",
"Source: ": "Πηγή: ",
"Specify the port that should be used by the API server. If not set, a default will be used.": "Καθορίστε τη πόρτα που πρέπει να χρησιμοποιείται από το διακομιστή API. Εάν δεν οριστεί, θα χρησιμοποιηθεί μια προεπιλογή.",
"Spell checker": "Ορθογράφος",
"Split View": "Διαίρεση προβολής",
@@ -626,6 +736,7 @@
"Sync Target Upgrade": "Συγχρονισμός αναβάθμισης προορισμού",
"Sync to provided target (defaults to sync.target config value)": "Συγχρονισμός με το ορισμένο προορισμό (προεπιλεγμένη τιμή συγχρονισμού sync.target)",
"Sync Version: %s": "Έκδοση συγχρονισμού: %s",
"Sync your notes": "Συγχρονισμός των σημειώσεών σας",
"Synchronisation": "Συγχρονισμός",
"Synchronisation interval": "Διάστημα συγχρονισμού",
"Synchronisation is already in progress.": "Ο συγχρονισμός είναι ήδη σε εξέλιξη.",
@@ -636,11 +747,14 @@
"Synchronise only over WiFi connection": "Συγχρονισμός μόνο μέσω σύνδεσης WiFi",
"Synchronises with remote storage.": "Συγχρονίζει με απομακρυσμένο αποθηκευτικό χώρο.",
"Synchronising...": "Συγχρονισμός...",
"Synchronizing...": "Συγχρονισμός...",
"Tabloid": "Tabloid",
"Tagged: %d.": "Με ετικέτα: %d.",
"Tags": "Ετικέτες",
"Take photo": "Βγάλτε μια φωτογραφία",
"Tasks": "Εργασίες",
"Text editor command": "Εντολή \"Επεξεργαστή κειμένου\"",
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Ευχαριστώ! Ο λογαριασμός σας στο Joplin Cloud είναι τώρα ρυθμισμένος και έτοιμος για χρήση.",
"The app is now going to close. Please relaunch it to complete the process.": "Η εφαρμογή θα κλείσει τώρα. Παρακαλώ ανοίξτε την ξανά για να ολοκληρώσετε τη διαδικασία.",
"The application has been authorised - you may now close this browser tab.": "Η εφαρμογή έχει εξουσιοδοτηθεί - μπορείτε τώρα να κλείσετε αυτήν την καρτέλα του προγράμματος περιήγησης.",
"The application has been authorised!": "Η εφαρμογή έχει εγκριθεί!",
@@ -654,16 +768,22 @@
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "Η εντολή του editor (που μπορεί να περιλαμβάνει ορίσματα) θα χρησιμοποιηθεί για το άνοιγμα μιας σημείωσης. Εάν δεν δωθεί καμία, θα προσπαθήσει να ανιχνεύσει αυτόματα το προεπιλεγμένο πρόγραμμα επεξεργασίας.",
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "Ο συντελεστής ανάπτυξης καθορίζει το πόσο θα αναπτυχθεί ή θα συρρίκνωθεί το αντικειμένο ώστε να χωρά στον διαθέσιμο χώρο σε σχέση με τα άλλα αντικείμενα. Έτσι, ένα στοιχείο με συντελεστή 2 θα πάρει διπλάσιο χώρο από ένα στοιχείο με συντελεστή 1. Επανεκκινήστε την εφαρμογή για να δείτε αλλαγές.",
"The following attachments are being watched for changes:": "Τα ακόλουθα συνημμένα παρακολουθούνται για αλλαγές:",
"The following keys use an out-dated encryption algorithm and it is recommended to upgrade them. The upgraded key will still be able to decrypt and encrypt your data as usual.": "Τα ακόλουθα κλειδιά χρησιμοποιούν έναν αλγόριθμο κρυπτογράφησης που είναι ξεπερασμένος και συνιστάται η αναβάθμισή τους. Το αναβαθμισμένο κλειδί θα εξακολουθεί να είναι σε θέση να αποκρυπτογραφήσει και να κρυπτογραφήσει τα δεδομένα σας ως συνήθως.",
"The Joplin mobile app does not currently support this type of link: %s": "Η εφαρμογή Joplin για κινητά δεν υποστηρίζει αυτήν τη στιγμή αυτόν τον τύπο συνδέσμου: %s",
"The Joplin team has vetted this plugin and it meets our standards for security and performance.": "Η ομάδα του Joplin έχει ελέγξει αυτό το πρόσθετο και πληροί τα πρότυπα ασφαλείας και επιδόσεων.",
"The keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.": "Τα κλειδιά με αυτά τα ID χρησιμοποιούνται για την κρυπτογράφηση ορισμένων από τα στοιχεία σας, ωστόσο η εφαρμογή δεν έχει επί του παρόντος πρόσβαση σε αυτά. Είναι πιθανό να μεταφορτωθούν τελικά μέσω συγχρονισμού.",
"The master key has been upgraded successfully!": "Το κλειδί master αναβαθμίστηκε με επιτυχία!",
"The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.": "Τα κύρια κλειδιά με αυτά τα αναγνωριστικά (ID) χρησιμοποιούνται για την κρυπτογράφηση ορισμένων αντικειμένων σας, ωστόσο η εφαρμογή δεν έχει αυτή τη στιγμή πρόσβαση σε αυτά. Είναι πιθανό τελικά να ληφθούν μέσω συγχρονισμού.",
"The note \"%s\" has been successfully restored to the notebook \"%s\".": "Η σημείωση \"%s\" έχει επαναφερθεί με επιτυχία στο σημειωματάριο \"%s\".",
"The notebook could not be saved: %s": "Αυτό το σημειωματάριο δεν μπορεί να σωθεί: %s",
"The notes have been imported: %s": "Οι σημειώσεις έχουν εισαχθεί: %s",
"The possible commands are:": "Οι πιθανές εντολές είναι:",
"The recipient could not be removed from the list. Please try again.\n\nThe error was: \"%s\"": "Ο παραλήπτης δεν μπόρεσε να διαγραφεί από τη λίστα. Προσπαθήστε ξανά.\n\nΤο σφάλμα ήταν: \"%s\"",
"The sync target needs to be upgraded before Joplin can sync. The operation may take a few minutes to complete and the app needs to be restarted. To proceed please click on the link.": "Ο στόχος συγχρονισμού πρέπει να αναβαθμιστεί πριν από το συγχρονισμό του Joplin. Η λειτουργία μπορεί να διαρκέσει λίγα λεπτά για να ολοκληρωθεί και η εφαρμογή πρέπει να επανεκκινηθεί. Για να προχωρήσετε κάντε κλικ στο σύνδεσμο.",
"The sync target needs to be upgraded. Press this banner to proceed.": "Ο προορισμός συγχρονισμού πρέπει να αναβαθμιστεί. Πατήστε αυτό το banner για να προχωρήσετε.",
"The tag \"%s\" already exists. Please choose a different name.": "Η ετικέτα \"%s\" υπάρχει ήδη. Επιλέξτε ένα διαφορετικό όνομα.",
"The target to synchronise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).": "Ο προορισμός συγχρονισμού. Κάθε προορισμός συγχρονισμού μπορεί να έχει πρόσθετες παραμέτρους οι οποίες ονομάζονται ως `sync.NUM.NAME` (οι οποίες αναφέρονται παρακάτω).",
"The Web Clipper needs your authorisation to access your data.": "Το Web Clipper χρειάζεται την άδειά σας για να αποκτήσει πρόσβαση στα δεδομένα σας.",
"The web clipper service is enabled and set to auto-start.": "Η υπηρεσία web clipper είναι ενεργοποιημένη και έχει ρυθμιστεί για αυτόματη εκκίνηση.",
"The web clipper service is not enabled.": "Η υπηρεσία Web Clipper δεν είναι ενεργοποιημένη.",
"Theme": "Θέμα",
@@ -672,6 +792,7 @@
"There is no data to export.": "Δεν υπάρχουν δεδομένα για εξαγωγή.",
"There was a [conflict](%s) on the attachment below.\n\n%s": "Υπήρξε μια [διένεξη](%s) στο παρακάτω συνημμένο.\n\n%s",
"There was an error downloading this attachment:": "Παρουσιάστηκε σφάλμα κατά τη λήψη αυτού του συνημμένου:",
"There was an error setting up your Joplin Cloud account. Please verify your email and password and try again. Error was:\n\n%s": "Υπήρξε σφάλμα στη ρύθμιση του λογαριασμού σας στο Joplin Cloud. Παρακαλούμε επαληθεύστε το email και τον κωδικό πρόσβασής σας και δοκιμάστε ξανά. Το σφάλμα ήταν:\n\n%s",
"These items will remain on the device but will not be uploaded to the sync target. In order to find these items, either search for the title or the ID (which is displayed in brackets above).": "Αυτά τα στοιχεία θα παραμείνουν στη συσκευή, αλλά δεν θα μεταφορτωθούν στο στόχο συγχρονισμού. Για να βρείτε αυτά τα στοιχεία, αναζητήστε είτε τον τίτλο είτε το αναγνωριστικό (το οποίο εμφανίζεται στις παραπάνω παρενθέσεις).",
"These plugins enhance the Markdown renderer with additional features. Please note that, while these features might be useful, they are not standard Markdown and thus most of them will only work in Joplin. Additionally, some of them are *incompatible* with the WYSIWYG editor. If you open a note that uses one of these plugins in that editor, you will lose the plugin formatting. It is indicated below which plugins are compatible or not with the WYSIWYG editor.": "Αυτά τα πρόσθετα βελτιώνουν τον Markdown renderer με πρόσθετες δυνατότητες. Λάβετε υπόψη ότι, παρόλο που αυτές οι δυνατότητες μπορεί να είναι χρήσιμες, δεν είναι τυπικές Markdown και επομένως οι περισσότερες από αυτές θα λειτουργούν μόνο στο Joplin. Επιπλέον, ορισμένα από αυτά είναι *ασύμβατα* με το πρόγραμμα επεξεργασίας WYSIWYG. Εάν ανοίξετε μια σημείωση που χρησιμοποιεί ένα από αυτά τα πρόσθετα σε αυτόν τον επεξεργαστή, θα χάσετε τη μορφοποίηση της προσθήκης. Υποδεικνύεται παρακάτω ποια πρόσθετα είναι συμβατά ή όχι με τον επεξεργαστή WYSIWYG.",
"This attachment is not downloaded or not decrypted yet": "Αυτό το συνημμένο δεν έχει ακόμη μεταφορτωθεί ή αποκρυπτογραφηθεί",
@@ -686,10 +807,12 @@
"This service allows the browser extension to communicate with Joplin. When enabling it your firewall may ask you to give permission to Joplin to listen to a particular port.": "Αυτή η υπηρεσία επιτρέπει στην επέκταση του προγράμματος περιήγησης να επικοινωνεί με το Joplin. Όταν την ενεργοποιήσετε, το τείχος προστασίας μπορεί να σας ζητήσει να δώσετε άδεια στο Joplin να επικοινωνεί μέσω της συγκεκριμένης πόρτας.",
"This will allow Joplin to run in the background. It is recommended to enable this setting so that your notes are constantly being synchronised, thus reducing the number of conflicts.": "Αυτό θα επιτρέψει στο Joplin να τρέξει στο παρασκήνιο. Συνιστάται να ενεργοποιήσετε αυτήν τη ρύθμιση έτσι ώστε οι σημειώσεις σας να συγχρονίζονται συνεχώς, μειώνοντας έτσι τον αριθμό των διενέξεων.",
"This will open a new screen. Save your current changes?": "Αυτό θα ανοίξει μια νέα οθόνη. Να αποθηκευθούν οι τρέχουσες αλλαγές;",
"This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?": "Αυτό θα αφαιρέσει το σημειωματάριο από τη συλλογή σας και δεν θα έχετε πλέον πρόσβαση στο περιεχόμενό του. Θέλετε να συνεχίσετε;",
"Time format": "Μορφή ώρας",
"title": "τίτλος",
"Title": "Τίτλος",
"To allow Joplin to synchronise with Dropbox, please follow the steps below:": "Για να επιτρέψετε στο Joplin να συγχρονιστεί με το Dropbox, ακολουθήστε τα παρακάτω βήματα:",
"To continue, please enter your master password below.": "Για να συνεχίσετε, παρακαλώ πληκτρολογήστε τον κύριο κωδικό πρόσβασής σας παρακάτω.",
"To delete a tag, untag the associated notes.": "Για να διαγράψετε μία ετικέτα, αφαιρέστε την πρώτα από τις σχετικές σημειώσεις.",
"To delete: %d": "Προς διαγραφή: %d",
"To enter command line mode, press \":\"": "Για να εισέλθετε στη λειτουργία γραμμής εντολών, πατήστε \":\"",
@@ -706,11 +829,14 @@
"Toggle editors": "Εναλλαγή επεξεργαστών κειμένου",
"Toggle external editing": "Εναλλαγή εξωτερικής επεξεργασίας",
"Toggle note list": "Εναλλαγή λίστας σημειώσεων",
"Toggle own sort order": "Εναλλαγή της δικής σας σειράς ταξινόμησης",
"Toggle safe mode": "Εναλλαγή ασφαλούς λειτουργίας",
"Toggle sidebar": "Εναλλαγή πλευρικής μπάρας",
"Toggle sort order field": "Εναλλαγή του πεδίου ταξινόμησης",
"Token has been copied to the clipboard!": "Το Token έχει αντιγραφεί στο πρόχειρο!",
"Tools": "Εργαλεία",
"Total: %d/%d": "Σύνολο: %d/%d",
"Try again": "Δοκιμάστε ξανά",
"Type `help [command]` for more information about a command; or type `help all` for the complete usage information.": "Πληκτρολογήστε `help [command]` για περισσότερες πληροφορίες σχετικά με μια εντολή, ή πληκτρολογήστε `help all` για όλες τις πληροφορίες χρήσης.",
"Type `joplin help` for usage information.": "Πληκτρολογήστε `joplin help` για πληροφορίες χρήσης.",
"Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.": "Πληκτρολογήστε έναν τίτλο σημείωσης ή μέρος του περιεχομένου του για να μεταβείτε σε αυτόν. Ή πληκτρολογήστε # ακολουθούμενο από ένα όνομα ετικέτας ή @ ακολουθούμενο από ένα σημειωματάριο. Ή πληκτρολογίστε : για να ψάξετε για εντολές.",
@@ -720,14 +846,20 @@
"Undo": "Αναίρεση",
"Unknown flag: %s": "Άγνωστο flag: %s",
"Unknown item type downloaded - please upgrade Joplin to the latest version": "Λήψη άγνωστου τύπου στοιχείου - αναβαθμίστε το Joplin στην πιο πρόσφατη έκδοση",
"Unpublish note": "Αποδημοσίευση σημείωσης",
"Unshare": "Κατάργηση κοινής χρήσης",
"Unshare this notebook? The recipients will no longer have access to its content.": "Διακόψτε την κοινή χρήση σε αυτό το σημειωματάριο; Οι παραλήπτες δεν θα έχουν πλέον πρόσβαση στο περιεχόμενό του.",
"Unsupported image type: %s": "Μη υποστηριζόμενος τύπος εικόνας: %s",
"Unsupported link or message: %s": "Μη υποστηριζόμενος σύνδεσμος ή μήνυμα: %s",
"Untitled": "Χωρίς τίτλο",
"Update": "Ενημέρωση",
"Update profile": "Ενημέρωση προφίλ",
"Update total sizes": "Ενημέρωση συνολικών μεγεθών",
"Updated": "Ενημερώθηκε",
"updated date": "ημερομηνία ενημέρωσης",
"Updated local items: %d.": "Ενημερωμένα τοπικά στοιχεία: %d.",
"Updated remote items: %d.": "Ενημερωμένα απομακρυσμένα στοιχεία: %d.",
"Updated: ": "Ενημερώθηκε: ",
"Updated: %d.": "Ενημερώθηκε: %d.",
"Updated: %s": "Ενημερώθηκε: %s",
"Updating...": "Ενημέρωση...",
@@ -742,6 +874,9 @@
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Χρησιμοποιήστε το για να ξαναφτιάξετε το ευρετήριο αναζήτησης, εάν υπάρχει πρόβλημα με την αναζήτηση. Μπορεί να χρειαστεί πολύς χρόνος ανάλογα με τον αριθμό των σημειώσεων.",
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Χρησιμοποιείται για το μεγαλύτερο μέρος του κειμένου στο πρόγραμμα επεξεργασίας σημειώσεων. Εάν δεν βρεθεί, χρησιμοποιείται μια γενική αναλογική γραμματοσειρά (μεταβλητού πλάτους).",
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "Χρησιμοποιείται όταν απαιτείται γραμματοσειρά σταθερού πλάτους για την ευανάκριβη δημιουργία κειμένου (π.χ. πίνακες, πλαίσια ελέγχου, κωδικός). Εάν δεν βρεθεί, χρησιμοποιείται μια γενική μονοδιάστημα (σταθερού πλάτους).",
"User deletions": "Διαγραφές χρήστη",
"Users": "Χρήστες",
"Valid": "Έγκυρo",
"View": "Προβολή",
"View on map": "Προβολή στο χάρτη",
"View them now": "Προβολή τώρα",
@@ -770,6 +905,7 @@
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Μπορείτε να χρησιμοποιήσετε το παρακάτω εργαλείο για να κρυπτογραφήσετε εκ νέου τα δεδομένα σας, για παράδειγμα, εάν γνωρίζετε ότι ορισμένες από τις σημειώσεις σας είναι κρυπτογραφημένες με μια παρωχημένη μέθοδο κρυπτογράφησης.",
"Your choice: ": "Η επιλογή σας: ",
"Your data is going to be re-encrypted and synced again.": "Τα δεδομένα σας πρόκειται να κρυπτογραφηθούν και να συγχρονιστούν ξανά.",
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Ο κωδικός πρόσβασής σας είναι απαραίτητος για την αποκρυπτογράφηση ορισμένων δεδομένων σας. Πληκτρολογήστε `:e2ee decrypt` για να τον ορίσετε.",
"Your permission to use your camera is required.": "Απαιτείται η άδειά σας για τη χρήση της φωτογραφικής σας μηχανής.",
"Your version: %s": "Η έκδοσή σου: %s",
"Zoom In": "Μεγέθυνση",

View File

@@ -51,6 +51,8 @@
"Add recipient:": "Agregar destinatario:",
"Add title": "Añadir título",
"Add to dictionary": "Agregar al diccionario",
"Admin": "Administrador",
"Admin dashboard": "Tablero de Mandos de Administrador",
"Advanced options": "Opciones avanzadas",
"Advanced tools": "Herramientas avanzadas",
"All notes": "Todas las notas",
@@ -77,7 +79,9 @@
"Authentication was not completed (did not receive an authentication token).": "Autenticación no completada (no se recibió token de autenticación).",
"Authorisation token:": "Token de autorización:",
"Auto": "Automático",
"Auto-add disabled accounts for deletion": "Añadir automáticamente las cuentas deshabilitadas para su eliminación",
"Auto-pair braces, parenthesis, quotations, etc.": "Autoemparejar llaves, paréntesis, comillas, etc.",
"Automatically check for updates": "Comprobar actualizaciones",
"Automatically switch theme to match system theme": "Cambiar automáticamente el tema para que coincida con el tema del sistema",
"Back": "Retroceder",
"Bold": "Negrita",
@@ -132,6 +136,7 @@
"Completed": "Completada",
"Completed decryption.": "Descifrado completado.",
"Completed: %s (%s)": "Completado: %s (%s)",
"Compress old changes": "Comprimir cambios antiguos",
"Configuration": "Configuración",
"Confirm password cannot be empty": "La contraseña de confirmación no puede estar vacía",
"Confirm password:": "Confirmar contraseña:",
@@ -145,6 +150,7 @@
"Copy": "Copiar",
"Copy dev mode command to clipboard": "Copiar comando del modo de desarrollador al portapapeles",
"Copy external link": "Copiar enlace externo",
"Copy image": "Copiar imagen",
"Copy Link Address": "Copiar Enlace",
"Copy Markdown link": "Copiar el enlace de Markdown",
"Copy path to clipboard": "Copiar la ruta al portapapeles",
@@ -159,6 +165,8 @@
"Could not upgrade master key: %s": "No se pudo actualizar la clave maestra: %s",
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "No se ha podido verificar el estado de compartición de esta libreta - abortando. Por favor, inténtelo de nuevo cuando esté conectado a Internet.",
"Create a notebook": "Crea una libreta",
"Create notebook": "Crea libreta",
"Create user": "Crear usuario",
"Created": "Creada",
"created date": "fecha de creación",
"Created local items: %d.": "Elementos locales creados: %d.",
@@ -180,6 +188,7 @@
"Custom TLS certificates": "Certificados TLS personalizados",
"Cut": "Cortar",
"Dark": "Oscuro",
"Dashboard": "Tablero de Mandos",
"Database v%s": "Base de datos v%s",
"Date": "Fecha",
"Date format": "Formato de fecha",
@@ -191,6 +200,8 @@
"Default: %s": "Por defecto: %s",
"Delete": "Borrar",
"Delete attachment \"%s\"?": "¿Borrar adjunto «%s»?",
"Delete expired sessions": "Borrar sesiones expiradas",
"Delete expired tokens": "Borrar tokens expirados",
"Delete line": "Borrar línea",
"Delete local data and re-download from sync target": "Eliminar datos locales y volver a descargarlos desde el objetivo de sincronización",
"Delete note \"%s\"?": "¿Borrar nota «%s»?",
@@ -255,6 +266,8 @@
"Editor monospace font family": "Familia de fuente monoespaciada del editor",
"Either \"text\" or \"json\"": "Puede ser «text» o «json»",
"Emacs": "Emacs",
"Email": "Email",
"Emails": "Emails",
"emphasised text": "texto en cursiva",
"Enable": "Habilitar",
"Enable ++insert++ syntax": "Activar sintaxis ++insert++",
@@ -361,6 +374,7 @@
"Idle": "En reposo",
"Ignore": "Ignorar",
"Ignore TLS certificate errors": "Ignorar errores en certificados TLS",
"Images": "Imágenes",
"Import": "Importar",
"Importing from \"%s\" as \"%s\" format. Please wait...": "Importando desde «%s» como formato «%s». Por favor espere...",
"Importing notes...": "Importando notas...",
@@ -439,6 +453,7 @@
"Login with Dropbox": "Acceder con Dropbox",
"Login with OneDrive": "Acceder con OneDrive",
"Logout": "Cerrar sesión",
"Logs": "Registros",
"Make a donation": "Hacer una donación",
"Manage master password": "Administrar contraseña maestra",
"Manage master password...": "Administrar contraseña maestra...",
@@ -539,6 +554,7 @@
"Operation cancelled": "Operación cancelada",
"Options": "Opciones",
"Or create an account.": "Crea una nueva nota.O crea una cuenta.",
"Other applications...": "Otras aplicaciones...",
"Output format: %s": "Formato de destino: %s",
"Page orientation for PDF export": "Orientación de la página al exportar como PDF",
"Page size for PDF export": "Tamaño de página al exportar como PDF",
@@ -551,7 +567,6 @@
"PDF File": "Archivo PDF",
"Permission needed": "Se necesita permiso",
"Permission to use camera": "Permiso para usar la cámara",
"Please click on \"%s\" to proceed": "Por favor, haga clic en \"%s\" para proceder",
"Please confirm that you would like to re-encrypt your complete database.": "Por favor, confirme que desea volver a cifrar su base de datos completa.",
"Please enter your password in the master key list below before upgrading the key.": "Por favor, ingrese su contraseña en la lista de claves maestras a continuación antes de actualizar la clave.",
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Tenga en cuenta de que si se trata de una libreta grande, puede tardar unos minutos para que todas las notas aparezcan en el dispositivo del destinatario.",
@@ -580,6 +595,10 @@
"Previous versions of this note": "Versiones anteriores de esta nota",
"Print": "Imprimir",
"Privacy Policy": "Política de Privacidad",
"Process failed payment subscriptions": "Procesar las suscripciones de pago fallidas",
"Process oversized accounts": "Procesar cuentas de gran tamaño",
"Process user deletions": "Procesar eliminaciones de usuarios",
"Profile": "Perfil",
"Profile Version: %s": "Versión del Perfil: %s",
"Properties": "Propiedades",
"Public-private key pair:": "Par de claves públicas y privadas:",
@@ -621,6 +640,11 @@
"Revision: %s (%s)": "Revisión: %s (%s)",
"Runs the commands contained in the text file. There should be one command per line.": "Ejecuta los comandos contenidos en el archivo de texto. Debe haber un comando por línea.",
"S3": "S3",
"S3 access key": "Clave de acceso de S3",
"S3 bucket": "S3 bucket",
"S3 region": "Región de S3",
"S3 secret key": "Clave secreta de S3",
"S3 URL": "URL de S3",
"Safe mode is currently active. Note rendering and all plugins are temporarily disabled.": "El modo seguro está actualmente activo. El renderizado de notas y todos los plugins están temporalmente deshabilitados.",
"Save": "Guardar",
"Save alarm": "Establecer alarma",
@@ -637,6 +661,8 @@
"See the pre-release page for more details: %s": "Ver la página de prelanzamientos para más detalles: %s",
"Select": "Seleccione",
"Select all": "Seleccionar todo",
"Select emoji...": "Seleccionar emoji...",
"Select file...": "Seleccionar archivo...",
"Server is already running on port %d": "El servidor ya esta ejecutándose en el puerto %d",
"Server is not running.": "El servidor no se está ejecutando.",
"Server is running on port %d": "El servidor se está ejecutando en el puerto %d",
@@ -827,6 +853,8 @@
"Unsupported link or message: %s": "Enlace o mensaje no soportado: %s",
"Untitled": "Sin título",
"Update": "Actualizar",
"Update profile": "Actualizar perfil",
"Update total sizes": "Actualizar tamaños totales",
"Updated": "Actualizada",
"updated date": "fecha de actualización",
"Updated local items: %d.": "Elementos locales actualizados: %d.",
@@ -846,6 +874,7 @@
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Use esto para reparar el índice de búsqueda si hay un problema con la búsqueda. Puede tomar un largo tiempo dependiendo del número de notas.",
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Se utiliza para la mayor parte del texto en el editor markdown. Si no se encuentra, se utiliza una fuente genérica proporcional (ancho variable).",
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "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).",
"User deletions": "Eliminaciones de usuarios",
"Users": "Usuarios",
"Valid": "Válido",
"View": "Ver",
@@ -876,7 +905,6 @@
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Puede utilizar la siguiente herramienta para volver a cifrar sus datos, por ejemplo, si sabe que algunas de sus notas están cifradas con un método de cifrado obsoleto.",
"Your choice: ": "Su elección: ",
"Your data is going to be re-encrypted and synced again.": "Sus datos van a ser cifrados y sincronizados nuevamente.",
"Your master password is needed to decrypt some of your data.": "Su contraseña maestra es necesaria para descifrar algunos de sus datos.",
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Su contraseña es necesaria para descrifrar algunos de sus datos. Escriba `:e2ee decrypt` para establecerla.",
"Your permission to use your camera is required.": "Su permiso para usar su cámara es necesario.",
"Your version: %s": "Su versión: %s",

View File

@@ -51,6 +51,8 @@
"Add recipient:": "Lisää vastaanottaja:",
"Add title": "Lisää otsikko",
"Add to dictionary": "Lisää sanakirjaan",
"Admin": "Ylläpitäjä",
"Admin dashboard": "Ylläpitäjän hallintapaneeli",
"Advanced options": "Lisäasetukset",
"Advanced tools": "Lisätyökalut",
"All notes": "Kaikki muistiinpanot",
@@ -66,7 +68,7 @@
"Aritim Dark": "Aritim Dark",
"Attach file": "Liitä tiedosto",
"Attach photo": "Liitä valokuva",
"Attach...": "Liitä...",
"Attach...": "Liitä...",
"Attaches the given file to the note.": "Liittää annetun tiedoston muistiinpanoon.",
"attachment": "liite",
"Attachment conflict: \"%s\"": "Liitteen ristiriita: \"%s\"",
@@ -77,6 +79,7 @@
"Authentication was not completed (did not receive an authentication token).": "Todennusta ei suoritettu loppuun (todennustunnusta ei saatu).",
"Authorisation token:": "Valtuutuksen tunnus:",
"Auto": "Automaattinen",
"Auto-add disabled accounts for deletion": "Lisää käytöstä poistetut tilit automaattisesti poistettavaksi",
"Auto-pair braces, parenthesis, quotations, etc.": "Yhdistä sulut, sulkeet, lainaukset jne.",
"Automatically check for updates": "Tarkista päivitykset automaattisesti",
"Automatically switch theme to match system theme": "Vaihda teema automaattisesti vastaamaan järjestelmän teemaa",
@@ -101,6 +104,7 @@
"Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.": "Tunnusta ei voi päivittää: todennustiedot puuttuvat. Synkronoinnin aloittaminen uudelleen saattaa korjata ongelman.",
"Cannot save %s \"%s\" because it is larger than the allowed limit (%s)": "Tallennus ei onnistu %s \"%s\" koska se on suurempi kuin sallittu raja (%s)",
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) for this account": "Kohdetta %s \"%s\" ei voida tallentaa, koska se ylittäisi tämän tilin sallitun kokonaiskoon (%s)",
"Cannot share encrypted notebook with recipient %s because they have not enabled end-to-end encryption. They may do so from the screen Configuration > Encryption.": "Salattua muistikirjaa ei voi jakaa vastaanottajan %s kanssa, koska hän ei ole ottanut päästä päähän -salausta käyttöön. Sen voi tehdä Asetukset> Salauksen määritys.",
"Change application layout": "Sovelluksen asettelun muuttaminen",
"Change language": "Vaihda kieltä",
"Characters": "Merkit",
@@ -132,17 +136,20 @@
"Completed": "Valmis",
"Completed decryption.": "Salaus on valmis.",
"Completed: %s (%s)": "Valmis: %s (%s)",
"Configuration": "Konfigurointi",
"Compress old changes": "Pakkaa vanhat muutokset",
"Configuration": "Asetukset",
"Confirm password cannot be empty": "Vahvista salasana ei voi olla tyhjä",
"Confirm password:": "Vahvista salasana:",
"Confirmation": "Vahvistus",
"Conflicted: %d": "Ristiriitainen: %d",
"Conflicts": "Ristiriitoja",
"Conflicts (attachments)": "Ristiriidat (liitteet)",
"Content provided by %s": "Sisällön toimittaa %s",
"Convert to note": "Muunna muistiinpanoksi",
"Convert to todo": "Muunna tehtäväksi",
"Copy": "Kopioi",
"Copy dev mode command to clipboard": "Kopioi kehitystila komento leikepöydälle",
"Copy external link": "Kopioi ulkoinen linkki",
"Copy image": "Kopioi kuva",
"Copy Link Address": "Kopioi linkin osoite",
"Copy Markdown link": "Kopioi Merkinnän linkki",
@@ -151,9 +158,12 @@
"Copy token": "Kopioi tunnus",
"Could not authorise application:\n\n%s\n\nPlease try again.": "Sovelluksen valtuuttaminen epäonnistui:\n\n%s\n\nYritä uudelleen.",
"Could not connect to Joplin Server. Please check the Synchronisation options in the config screen. Full error was:\n\n%s": "Yhteyden muodostaminen Joplinin palvelimelle epäonnistui. Tarkista synkronoinnin asetukset. Koko virhe oli:\n\n%s",
"Could not connect to plugin repository.": "Yhteyden muodostaminen laajennusten arkistoon epäonnistui.",
"Could not export notes: %s": "Muistiinpanoja ei voitu viedä: %s",
"Could not install plugin: %s": "Laajennuksen asentaminen epäonnistui: %s",
"Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: \"%s\"": "Kutsuun ei voitu vastata. Yritä uudelleen tai tarkista muistikirjan omistajalta, jakaako hän sitä edelleen.\n\nVirhe oli: \"%s\"",
"Could not upgrade master key: %s": "Pääavainta ei voitu päivittää: %s",
"Could not verify the share status of this notebook - aborting. Please try again when you are connected to the internet.": "Tämän muistikirjan jaon tilaa ei voitu vahvistaa - keskeytetään. Yritä uudelleen, kun olet muodostanut yhteyden Internetiin.",
"Create a notebook": "Luo muistikirja",
"Create notebook": "Luo muistikirja",
"Create user": "Luo käyttäjä",
@@ -178,6 +188,7 @@
"Custom TLS certificates": "Mukautetut TLS varmenteet",
"Cut": "Leikkaa",
"Dark": "Tumma",
"Dashboard": "Koontinäyttö",
"Database v%s": "Tietokanta v%s",
"Date": "Päivämäärä",
"Date format": "Päivämäärän muoto",
@@ -189,6 +200,8 @@
"Default: %s": "Oletus: %s",
"Delete": "Poista",
"Delete attachment \"%s\"?": "Poista muistiinpano \"%s\"?",
"Delete expired sessions": "Poista vanhentuneet istunnot",
"Delete expired tokens": "Poista vanhentuneet tunnukset",
"Delete line": "Poista rivi",
"Delete local data and re-download from sync target": "Poista paikalliset tiedot ja lataa uudelleen synkronointikohteesta",
"Delete note \"%s\"?": "Poista muistiinpano \"%s\"?",
@@ -226,6 +239,7 @@
"Displays version information": "Näyttää versiotiedot",
"Do it now": "Hae se nyt",
"Do not ask for confirmation.": "Älä kysy vahvistusta.",
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": "Älä kadota salasanaa, sillä tämä on turvallisuussyistä *ainoa* tapa purkaa tietojen salaus! Voit ottaa salauksen käyttöön kirjoittamalla salasanasi alle.",
"Download": "Lataa",
"Download and install the relevant extension for your browser:": "Lataa ja asenna laajennus selaimeesi:",
"Downloaded": "Ladattu",
@@ -241,7 +255,7 @@
"Duplicate line": "Rivin kaksoiskappale",
"Duplicates the notes matching <note> to [notebook]. If no notebook is specified the note is duplicated in the current notebook.": "Kopioi muistiinpanot, jotka vastaavat <note> arvoon [notebook]. Jos mitään muistikirjaa ei ole määritetty, muistiinpano kopioidaan nykyiseen muistikirjaan.",
"Edit": "Muokkaa",
"Edit in external editor": "Muokkaa ulkoisessa editorissa",
"Edit in external editor": "Muokkaa ulkoisessa tekstieditorissa",
"Edit note.": "Muokkaa muistiinpanoa.",
"Edit notebook": "Muokkaa muistikirjaa",
"Editor": "Editoija",
@@ -279,6 +293,7 @@
"Enable Web Clipper Service": "Ota käyttöön Web Clipper työkalu",
"Enable ~sub~ syntax": "Ota käyttöön ~sub~ syntax",
"Enabled": "Käytössä",
"Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target.": "Salauksen ottaminen käyttöön tarkoittaa, että *kaikki* muistiinpanosi ja liitteesi synkronoidaan uudelleen ja lähetetään salattuina synkronointikohteeseen.",
"Encrypted": "Salattu",
"Encrypted items cannot be modified": "Salattuja kohteita ei voi muokata",
"Encrypted notebooks cannot be renamed": "Salattuja muistikirjoja ei voi nimetä uudelleen",
@@ -287,8 +302,9 @@
"Encryption is: %s": "Salaus on: %s",
"Encryption keys": "Salausavaimet",
"Encryption:": "Salaus:",
"End-to-end encryption": "Päästä päähän salaus",
"Enter code here": "Syötä koodi tähän",
"Enter master password:": "Syötä pääsalasana:",
"Enter master password:": "Kirjoita pääsalasana:",
"Enter notebook title": "Anna muistikirjan otsikko",
"Enum": "Enum",
"Error": "Virhe",
@@ -328,11 +344,13 @@
"For information on how to customise the shortcuts please visit %s": "Lisätietoja pikavalintojen mukauttamisesta on osoitteessa %s",
"For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:": "Lisätietoja E2EE (End-To-End Encryption) -salauksesta ja sen käyttöönottoon liittyvistä ohjeista saat seuraavista ohjeista:",
"For the list of keyboard shortcuts and config options, type `help keymap`": "Saat luettelon pikanäppäimistä ja config vaihtoehtoja, kirjoita `help keymap`",
"Force path style": "Pakota polun tyyli",
"Forward": "Eteenpäin",
"Found: %d.": "Löytyi: %d.",
"FTS enabled: %d": "Koko tekstin haku (FTS) Käytössä: %d",
"Full changelog": "Täysi muutosloki",
"General": "Yleiset",
"Generated": "Luotu",
"Generating link...": "Luodaan linkki...",
"Get it now:": "Hae se nyt:",
"Get pre-releases when checking for updates": "Hanki ennakkojulkaisuja, kun etsit päivityksiä",
@@ -343,8 +361,10 @@
"Heading": "Otsikko",
"Help": "Apua",
"Hide %s": "Piilota %s",
"Hide disabled keys": "Piilota käytöstä poistetut avaimet",
"Hide Joplin": "Piilota Joplin",
"Highlight": "Korosta",
"Home": "Koti",
"Horizontal Rule": "Vaakaviiva",
"HTML Directory": "HTML hakemisto",
"HTML File": "HTML tiedosto",
@@ -383,9 +403,10 @@
"Invalid answer: %s": "Virheellinen vastaus: %s",
"Invalid command: \"%s\"": "Virheellinen komento: \"%s\"",
"Invalid option value: \"%s\". Possible values are: %s.": "Virheellinen asetusarvo: \"%s\". Mahdolliset arvot ovat: %s.",
"Invalid password": "Virheellinen salasana",
"Invalid password": "Väärä salasana",
"Italic": "Kursiivi",
"Item \"%s\" could not be downloaded: %s": "Kohdetta \"%s\" ei voitu ladata: %s",
"Items": "Kohteet",
"Items that cannot be decrypted": "Kohteet, joita ei voi purkaa",
"Items that cannot be synchronised": "Kohteet, joita ei voi synkronoida",
"Joplin can synchronise your notes using various providers. Select one from the list below.": "Joplin voi synkronoida muistiinpanosi eri palveluntarjoajien avulla. Valitse yksi alla olevasta luettelosta.",
@@ -408,12 +429,14 @@
"Keyboard Shortcut": "Pikanäppäin",
"Keyboard Shortcuts": "Pikanäppäimet",
"Keychain Supported: %s": "Avainnippu tuettu: %s",
"Keys that need upgrading": "Päivitystä vaativat avaimet",
"Landscape": "Vaakasuunta",
"Language": "Kieli",
"Last error: %s": "Viimeinen virhe: %s",
"Later": "Myöhemmin",
"Layout": "Ulkoasu",
"Layout button sequence": "Aseta painike järjestys",
"Leave notebook...": "Poistu muistikirjasta...",
"Legal": "Legal",
"Letter": "Letter",
"Light": "Vaalea",
@@ -421,6 +444,7 @@
"Link has been copied to clipboard!": "Linkki on kopioitu leikepöydälle!",
"Links with protocol \"%s\" are not supported": "Linkkejä protokollaan \"%s\" ei tueta",
"List item": "Luettelon kohde",
"Loaded": "Ladattu",
"Location": "Sijainti",
"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.": "Lukitustiedosto on jo pidossa. Jos tiedät, että synkronointia ei tapahdu, voit poistaa lukitustiedoston kohdasta \"%s\" ja jatkaa toimintaa.",
"Log": "Loki",
@@ -428,12 +452,16 @@
"Login below.": "Kirjaudu sisään alla.",
"Login with Dropbox": "Kirjaudu sisään Dropbox",
"Login with OneDrive": "Kirjaudu sisään OneDrive",
"Logout": "Uloskirjaus",
"Logs": "Lokit",
"Make a donation": "Tee lahjoitus",
"Manage master password": "Pääsalasanan hallinta",
"Manage master password...": "Pääsalasanan hallinta...",
"Manage master password...": "Hallitse pääsalasanaa...",
"Manage your plugins": "Hallitse laajennuksia",
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.": "Hallitse E2EE määrityksiä. Komennot ovat `enable`, `disable`, `decrypt`, `status`, `decrypt-file` ja `target-status`.",
"Manual": "Manuaalinen",
"Markdown": "Markdown",
"Markdown + Front Matter": "Markdown + Front Matter",
"Marks a to-do as done.": "Merkitsee tehtävän valmiiksi.",
"Marks a to-do as non-completed.": "Merkitsee tehtävän keskeneräiseksi.",
"Markup": "Merkintä",
@@ -441,6 +469,7 @@
"Master password": "Pääsalasana",
"Master password:": "Pääsalasana:",
"Max concurrent connections": "Samanaikaiset yhteydet enintään",
"Missing keys": "Puuttuvat avaimet",
"Missing Master Keys": "Puuttuvat pääavaimet",
"Missing required argument: %s": "Vaadittu argumentti puuttuu: %s",
"Mobile data - auto-sync disabled": "Mobiilidata - automaattinen synkronointi poistettu käytöstä",
@@ -481,6 +510,7 @@
"Nord": "Nord",
"Not authentified with %s. Please provide any missing credentials.": "Ei todennettu %s. Anna puuttuvat tunnistetiedot.",
"Not downloaded": "Ei ladattu",
"Not generated": "Ei luotu",
"note": "merkintä",
"Note": "Huomautus",
"Note area growth factor": "Huomautus alueen kasvutekijä",
@@ -489,7 +519,7 @@
"Note body": "Muistiinpanon kappale",
"Note does not exist: \"%s\". Create it?": "Huomautusta ei ole: \"%s\". Luodaanko se?",
"Note has been saved.": "Huomautus on tallennettu.",
"Note History": "Muistiinpanohistoria",
"Note History": "Muistiinpano historia",
"Note is not a to-do: \"%s\"": "Huomautus ei ole tehtävä: \"%s\"",
"Note list": "Muistiinpanot",
"Note list growth factor": "Huomautus luettelon kasvutekijä",
@@ -524,6 +554,7 @@
"Operation cancelled": "Toiminto peruutettu",
"Options": "Asetukset",
"Or create an account.": "Tai luo tili.",
"Other applications...": "Muut sovellukset...",
"Output format: %s": "Esitysmuoto: %s",
"Page orientation for PDF export": "Sivun suunta PDF vientiä varten",
"Page size for PDF export": "PDF viennin sivukoko",
@@ -538,6 +569,7 @@
"Permission to use camera": "Lupa käyttää kameraa",
"Please confirm that you would like to re-encrypt your complete database.": "Varmista, että haluat salata koko tietokannan uudelleen.",
"Please enter your password in the master key list below before upgrading the key.": "Kirjoita salasana alla olevaan pääavainluetteloon ennen avaimen päivittämistä.",
"Please note that if it is a large notebook, it may take a few minutes for all the notes to show up on the recipient's device.": "Huomaa, että jos kyseessä on suuri muistikirja, voi kestää minuutteja, ennen kuin kaikki muistiinpanot näkyvät vastaanottajan laitteessa.",
"Please open the following URL in your browser to authenticate the application. The application will create a directory in \"Apps/Joplin\" and will only read and write files in this directory. It will have no access to any files outside this directory nor to any other personal data. No data will be shared with any third party.": "Todenna sovellus avaamalla selaimessasi seuraava URL-osoite. Sovellus luo hakemiston \"Sovellukset / Joplin\" ja lukee ja kirjoittaa vain tiedostoja tähän hakemistoon. Sillä ei ole pääsyä tämän hakemiston ulkopuolisiin tiedostoihin eikä muihin henkilötietoihin. Tietoja ei jaeta kolmannen osapuolen kanssa.",
"Please select a notebook first.": "Valitse ensin muistikirja.",
"Please select the note or notebook to be deleted first.": "Valitse ensin poistettava muistiinpano tai muistikirja.",
@@ -563,8 +595,13 @@
"Previous versions of this note": "Tämän muistiinpanon aiemmat versiot",
"Print": "Tulosta",
"Privacy Policy": "Tietosuojakäytäntö",
"Process failed payment subscriptions": "Käsittele epäonnistuneet maksutilaukset",
"Process oversized accounts": "Käsittele ylisuuria tilejä",
"Process user deletions": "Käsittele käyttäjien poistot",
"Profile": "Profiili",
"Profile Version: %s": "Profiilin versio: %s",
"Properties": "Ominaisuudet",
"Public-private key pair:": "Julkinen ja yksityinen avainpari:",
"Publish note...": "Muistiinpanon julkaiseminen...",
"Publish Notes": "Julkaise muistiinpanot",
"Publish notes to the internet": "Julkaise muistiinpanot Internetissä",
@@ -589,6 +626,7 @@
"Rename tag:": "Nimeä tunniste uudelleen:",
"Renames the given <item> (note or notebook) to <name>.": "Nimeää annetun <item> (muistiinpano tai muistikirja) nimeksi <name>.",
"Renew token": "Uusi tunnus",
"Reset master password": "Pääsalasanan vaihtaminen",
"Resources: %d.": "Resursseja: %d.",
"Restart and upgrade": "Käynnistä uudelleen ja päivitä",
"Restart now": "Käynnistä uudelleen nyt",
@@ -601,6 +639,12 @@
"Reverses the sorting order.": "Kääntää lajittelujärjestyksen.",
"Revision: %s (%s)": "Versio: %s (%s)",
"Runs the commands contained in the text file. There should be one command per line.": "Suorittaa tekstitiedoston komennot. Riviä kohden pitäisi olla yksi komento.",
"S3": "S3",
"S3 access key": "S3 access key",
"S3 bucket": "S3 bucket",
"S3 region": "S3 region",
"S3 secret key": "S3 secret key",
"S3 URL": "S3 URL",
"Safe mode is currently active. Note rendering and all plugins are temporarily disabled.": "Turvallinen tila on tällä hetkellä aktiivinen. Huomautusten renderöinti ja kaikki laajennukset on tilapäisesti poistettu käytöstä.",
"Save": "Tallenna",
"Save alarm": "Tallenna hälytys",
@@ -617,12 +661,14 @@
"See the pre-release page for more details: %s": "Lisätietoja on ennakkojulkaisusivulla: %s",
"Select": "Valitse",
"Select all": "Valitse kaikki",
"Select emoji...": "Valitse emoji...",
"Select file...": "Valitse tiedosto...",
"Server is already running on port %d": "Palvelin on jo käynnissä portissa %d",
"Server is not running.": "Palvelin ei ole käynnissä.",
"Server is running on port %d": "Palvelin on käynnissä portissa %d",
"Set alarm": "Aseta hälytys",
"Set alarm:": "Aseta hälytys:",
"Set it to 0 to make it take the complete available space. Recommended width is 600.": "Aseta se arvoksi 0, jotta se vie koko käytettävissä olevan tilan. Suositeltu leveys on 600.",
"Set the password": "Aseta salasana",
"Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s": "Asettaa ominaisuuden <name> annetusta <note> annettuun [arvoon]. Mahdolliset ominaisuudet ovat:\n\n%s",
"Share": "Jaa",
@@ -633,7 +679,9 @@
"Show Advanced Settings": "Näytä lisäasetukset",
"Show all": "Näytä kaikki",
"Show completed to-dos": "Näytä valmiit tehtävät",
"Show disabled keys": "Näytä käytöstä poistetut avaimet",
"Show note counts": "Näytä muistiinpanojen määrä",
"Show sort order buttons": "Näytä lajittelujärjestyspainikkeet",
"Show tray icon": "Näytä kuvake tehtäväpalkissa",
"Sidebar": "Sivupalkki",
"Size": "Koko",
@@ -669,7 +717,7 @@
"Step 2: Enter the code provided by Dropbox:": "Vaihe 2: Kirjoita Dropboxin tarjoama koodi:",
"Step 2: Install the extension": "Vaihe 2: Asenna laajennus",
"Stop": "Seis",
"Stop external editing": "Lopeta ulkoinen muokkaus",
"Stop external editing": "Lopeta ulkoisen tekstieditorin muokkaus",
"Strikethrough": "Yliviivaus",
"strong text": "vahva teksti",
"Submit": "Lähetä",
@@ -704,6 +752,7 @@
"Tagged: %d.": "Merkityt: %d.",
"Tags": "Tunnisteet",
"Take photo": "Ota valokuva",
"Tasks": "Tehtävät",
"Text editor command": "Tekstieditorin komento",
"Thank you! Your Joplin Cloud account is now setup and ready to use.": "Kiitos! Your Joplin Cloud tilisi on nyt määritetty ja käyttövalmis.",
"The app is now going to close. Please relaunch it to complete the process.": "Sovellus suljetaan nyt. Käynnistä se uudelleen prosessin loppuun saamiseksi.",
@@ -719,14 +768,19 @@
"The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.": "Editorikomento (voi sisältää argumentteja), jota käytetään muistiinpanon avaamiseen. Jos mitään ei ole annettu, se yrittää tunnistaa oletuseditorin automaattisesti.",
"The factor property sets how the item will grow or shrink to fit the available space in its container with respect to the other items. Thus an item with a factor of 2 will take twice as much space as an item with a factor of 1.Restart app to see changes.": "Kerroin ominaisuus määrittää, miten kohde kasvaa tai kutistuu niin, että se sopii säiliössä olevaan tilaan suhteessa muihin kohteisiin. Näin ollen kohde, jonka kerroin on 2, vie kaksi kertaa enemmän tilaa kuin kohde, jonka kerroin on 1.Restart sovellus muutosten näkemistä varten.",
"The following attachments are being watched for changes:": "Seuraavia liitteitä tarkkaillaan muutosten vuoksi:",
"The following keys use an out-dated encryption algorithm and it is recommended to upgrade them. The upgraded key will still be able to decrypt and encrypt your data as usual.": "Seuraavat avaimet käyttävät vanhentunutta salausalgoritmia, ja ne on suositeltavaa päivittää. Päivitetty avain pystyy edelleen purkamaan ja salaamaan tietosi tavalliseen tapaan.",
"The Joplin mobile app does not currently support this type of link: %s": "Joplin mobiilisovellus ei tällä hetkellä tue tämän tyyppistä linkkiä: %s",
"The Joplin team has vetted this plugin and it meets our standards for security and performance.": "Joplin tiimi on käynyt läpi tämän laajennuksen ja se täyttää turvallisuus- ja suorituskykystandardimme.",
"The keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.": "Näillä tunnuksilla varustettuja avaimia käytetään joidenkin kohteiden salaamiseen, mutta sovelluksella ei tällä hetkellä ole pääsyä niihin. On todennäköistä, että ne lopulta ladataan synkronoinnin avulla.",
"The master key has been upgraded successfully!": "Pääavain on päivitetty onnistuneesti!",
"The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.": "Näillä tunnuksilla varustettuja pääavaimia käytetään joidenkin kohteiden salaamiseen, mutta sovelluksella ei tällä hetkellä ole pääsyä niihin. On todennäköistä, että ne lopulta ladataan synkronoinnin avulla.",
"The note \"%s\" has been successfully restored to the notebook \"%s\".": "Muistiinpano \"%s\" on palautettu muistikirjaan \"%s\".",
"The notebook could not be saved: %s": "Muistikirjaa ei voitu tallentaa: %s",
"The notes have been imported: %s": "Muistiinpanot on tuotu: %s",
"The possible commands are:": "Mahdolliset komennot ovat:",
"The recipient could not be removed from the list. Please try again.\n\nThe error was: \"%s\"": "Vastaanottajaa ei voitu poistaa luettelosta. Yritä uudelleen.\n\nVirhe oli: \"%s\"",
"The sync target needs to be upgraded before Joplin can sync. The operation may take a few minutes to complete and the app needs to be restarted. To proceed please click on the link.": "Synkronointikohde on päivitettävä, ennen kuin Joplin voidaan synkronoida. Toiminnon suorittamiseen voi mennä muutama minuutti, ja sovellus on käynnistettävä uudelleen. Jatka napsauttamalla linkkiä.",
"The sync target needs to be upgraded. Press this banner to proceed.": "Synkronointikohde on päivitettävä. Jatka painamalla tätä banneria.",
"The tag \"%s\" already exists. Please choose a different name.": "Tunniste \"%s\" on jo olemassa. Valitse toinen nimi.",
"The target to synchronise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).": "Kohde, johon synkronoidaan. Jokaisella synkronointikohteella voi olla muita parametreja, jotka on nimetty `sync.NUM.NAME` (kaikki dokumentoitu jäljempänä).",
"The Web Clipper needs your authorisation to access your data.": "Web Clipper tarvitsee valtuutuksen tietojen käyttöön.",
@@ -753,10 +807,12 @@
"This service allows the browser extension to communicate with Joplin. When enabling it your firewall may ask you to give permission to Joplin to listen to a particular port.": "Tämän palvelun avulla selainlaajennus voi olla yhteydessä Jopliniin. Kun se otetaan käyttöön, palomuuri voi pyytää sinua antamaan Joplinille luvan kuunnella tiettyä porttia.",
"This will allow Joplin to run in the background. It is recommended to enable this setting so that your notes are constantly being synchronised, thus reducing the number of conflicts.": "Antaa Joplinille mahdollisuuden toimia taustalla. On suositeltavaa ottaa tämä asetus käyttöön, jotta muistiinpanoja synkronoidaan jatkuvasti, näin vähennetään ristiriitojen määrää.",
"This will open a new screen. Save your current changes?": "Tämä avaa uuden ikkunan. Tallennetaanko nykyiset muutokset?",
"This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?": "Tämä poistaa muistikirjan kokoelmasta, etkä voi enää käyttää sen sisältöä. Haluatko jatkaa?",
"Time format": "Ajan muoto",
"title": "otsikko",
"Title": "Otsikko",
"To allow Joplin to synchronise with Dropbox, please follow the steps below:": "Jos haluat, että Joplin voi synkronoida Dropboxin kanssa, toimi seuraavasti:",
"To continue, please enter your master password below.": "Jatka kirjoittamalla pääsalasanasi alle.",
"To delete a tag, untag the associated notes.": "Tunnisteen poistaminen, poista merkinnät muistiinpanoista.",
"To delete: %d": "Poistettu: %d",
"To enter command line mode, press \":\"": "Siirry komentorivitilaan painamalla \":\"",
@@ -771,10 +827,12 @@
"Toggle development tools": "Vaihda kehitystyökalut",
"Toggle editor layout": "Vaihda editorin asettelua",
"Toggle editors": "Vaihda editoria",
"Toggle external editing": "Vaihda ulkoiseen muokkaukseen",
"Toggle external editing": "Vaihda ulkoiseen tekstieditoriin",
"Toggle note list": "Muistiinpanoluettelon vaihtaminen",
"Toggle own sort order": "Vaihda omaa lajittelujärjestystä",
"Toggle safe mode": "Vaihda turvalliseen tilaan",
"Toggle sidebar": "Näytä sivupalkki",
"Toggle sort order field": "Vaihda lajittelujärjestyskenttää",
"Token has been copied to the clipboard!": "Tunnus on kopioitu leikepöydälle!",
"Tools": "Työkalut",
"Total: %d/%d": "Yhteensä: %d/%d",
@@ -795,6 +853,8 @@
"Unsupported link or message: %s": "Linkkiä tai viestiä ei tueta: %s",
"Untitled": "Nimetön",
"Update": "Päivitä",
"Update profile": "Päivitä profiili",
"Update total sizes": "Päivitä kokonaismäärät",
"Updated": "Päivitetty",
"updated date": "päivitetty päivämäärä",
"Updated local items: %d.": "Päivitetyt paikalliset kohteet: %d.",
@@ -814,6 +874,7 @@
"Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.": "Tämän avulla voit muodostaa hakuindeksin uudelleen, jos haussa on ongelma. Se voi kestää kauan muistiinpanojen määrästä riippuen.",
"Used for most text in the markdown editor. If not found, a generic proportional (variable width) font is used.": "Käytetään useimmissa teksteissä markdown editorissa. Jos sitä ei löydy, käytetään yleistä suhteellista fonttia (vaihtelevaa leveyttä).",
"Used where a fixed width font is needed to lay out text legibly (e.g. tables, checkboxes, code). If not found, a generic monospace (fixed width) font is used.": "Käytetään, kun tekstiin tarvitaan kiinteäleveyksinen fontti (esim. taulukot, valintaruudut, koodi). Jos sitä ei löydy, käytetään yleistä monospace fonttia (kiinteäleveyksinen).",
"User deletions": "Käyttäjien poisto",
"Users": "Käyttäjät",
"Valid": "Kelvollinen",
"View": "Näytä",
@@ -844,6 +905,7 @@
"You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.": "Voit käyttää alla olevaa työkalua tietojen salaamiseen uudelleen, esimerkiksi jos tiedät, että osa muistiinpanoistasi on salattu vanhentuneella salausmenetelmällä.",
"Your choice: ": "Sinun valintasi: ",
"Your data is going to be re-encrypted and synced again.": "Tietosi salataan ja synkronoidaan uudelleen.",
"Your password is needed to decrypt some of your data. Type `:e2ee decrypt` to set it.": "Salasanaa tarvitaan joidenkin tietojen salauksen purkamiseen. Aseta se kirjoittamalla `:e2ee decrypt`.",
"Your permission to use your camera is required.": "Tarvitset luvan käyttää kameraa.",
"Your version: %s": "Sinun versiosi: %s",
"Zoom In": "Lähennä",

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