1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-29 07:46:13 +02:00

Compare commits

...

100 Commits

Author SHA1 Message Date
Laurent Cozic
99b5847244 update 2021-09-06 16:50:37 +01:00
Laurent Cozic
4ad7e1b245 movile 2021-09-06 14:37:26 +01:00
Laurent Cozic
b6f889baca update 2021-09-06 14:31:40 +01:00
Laurent Cozic
54fb4b0946 theme to css 2021-09-05 19:17:47 +01:00
Laurent Cozic
80762572cf Chore: Moved app state to separate file 2021-09-04 18:11:29 +01:00
Marph
8e5d209d3c All: Translation: Update de_DE.po (#5419) 2021-09-04 13:07:09 -04:00
reportxx
f71dad6d09 All: Translation: Update sv.po (#5418) 2021-09-04 12:05:59 -04:00
Laurent Cozic
f5891dfae8 Tools: Fixed build 2021-09-04 15:15:25 +01:00
Laurent Cozic
736bbbd8ed Plugins: Fixed import API
Ref: https://discourse.joplinapp.org/t/prompt-when-the-plugin-is-running-typeerror-this-module-oninit-is-not-a-function/20009/14
2021-09-04 15:07:38 +01:00
Laurent Cozic
973121addd Fixed missing command 2021-09-04 14:36:52 +01:00
Laurent Cozic
95ad4c3177 Tools: Ignore commands/index files for linting 2021-09-04 14:29:43 +01:00
Laurent Cozic
71c470f59d Desktop: Fixes #5417: Handle invalid search index in Goto Anything 2021-09-04 14:26:29 +01:00
Laurent Cozic
c529b972e3 Chore: Automatically create command index for desktop app 2021-09-04 13:43:25 +01:00
Laurent Cozic
e6bff3f2e0 Chore: Moved desktop app reducer to separate file so that it can be unit tested 2021-09-04 12:37:22 +01:00
Laurent Cozic
1bc674a1f9 Tools: Update ignored files when running desktop app in dev mode 2021-09-03 16:43:06 +01:00
Laurent Cozic
f371bb8e59 Revert "Merge branch 'desktop-protocol' of https://github.com/roman-r-m/joplin into dev"
This reverts commit 7c85889c1f, reversing
changes made to ab134807ea.

Reason: Messed up and accidentally merged a pull request.
2021-09-03 15:03:17 +01:00
Laurent Cozic
7c85889c1f Merge branch 'desktop-protocol' of https://github.com/roman-r-m/joplin into dev 2021-09-03 15:00:31 +01:00
Piotr Banasik
ab134807ea Server: Enable multi platform builds (amd64, armv7 and arm64) (#5338) 2021-09-03 14:54:10 +01:00
Laurent Cozic
a5b3bb6058 Doc: Add Joplin Cloud links 2021-09-03 14:50:21 +01:00
Laurent Cozic
8ab1cd984c Server v2.4.3 2021-09-02 18:49:29 +01:00
Laurent Cozic
e387d9a91b Merge branch 'dev' into release-2.4 2021-09-02 18:48:43 +01:00
Laurent Cozic
0793b1be59 Releasing sub-packages 2021-09-02 18:40:37 +01:00
Laurent Cozic
19225abbcf Android 2.4.1 2021-09-02 18:40:37 +01:00
Laurent Cozic
85fa3288ab Desktop release v2.4.4 2021-09-02 18:40:36 +01:00
Laurent Cozic
17f82c426a Chore: Added server test 2021-09-02 11:44:13 +01:00
Laurent Cozic
82331c9b93 Server: Display note title as page title when sharing note 2021-09-02 11:37:53 +01:00
Roman
886b6d1126 Add stuff to maybe get it to work on MacOS 2021-09-01 22:46:44 +01:00
Roman
1a703c4ecd Rename ProtocolUtils -> callbackUrlUtils 2021-09-01 22:28:33 +01:00
Roman
1126899769 Code review changes 2021-09-01 22:27:24 +01:00
James Wu
a571d38862 installer script regex match bracket fix in Debian version check (#5405) 2021-09-01 13:27:16 -06:00
Laurent Cozic
4ab2fb73b7 Doc: Added social links in footer 2021-09-01 15:21:56 +01:00
Laurent Cozic
78c7b79299 Fixed margin 2021-09-01 12:47:35 +01:00
Laurent Cozic
d97ba57dda Desktop: Sort plugin results according to recommended property, and display Recommended tag 2021-09-01 12:17:20 +01:00
Laurent Cozic
9c44133bd0 Plugin Repo: Allow overridding manifest properties and added recommended plugins 2021-08-31 22:37:56 +01:00
Laurent Cozic
dc008ecf64 Doc: typo 2021-08-31 17:24:40 +01:00
Laurent Cozic
93a4ad09bb Server: Fixed calculation of max sizes for Postgres 2021-08-31 16:15:20 +01:00
Laurent Cozic
9c1dc7898a Doc: Add more links to FAQ 2021-08-31 15:39:02 +01:00
Laurent Cozic
6520a481ca Server: Added Help page for Joplin Cloud 2021-08-31 13:46:46 +01:00
Laurent Cozic
5805a41249 Server: Added icon next to profile button 2021-08-31 12:16:57 +01:00
Laurent Cozic
b88b747ba6 Api: Resolves #5199: Add support for "events" end point to retrieve info about latest note changes 2021-08-30 18:53:24 +01:00
Laurent Cozic
ce89ee5bab All: Add support for single master password, to simplify handling of multiple encryption keys 2021-08-30 14:15:35 +01:00
SiderealArt
596f679b1f All: Translation: Update zh_TW.po (#5398) 2021-08-30 07:36:38 -04:00
Laurent Cozic
78d5fd1385 Server v2.4.2 2021-08-28 18:46:45 +01:00
Helmut K. C. Tessarek
0a98854e43 Update translations 2021-08-28 11:50:02 -04:00
Helmut K. C. Tessarek
a8a0dd2dd6 All: Translation: Update da_DK.po (thanks ERYpTION) 2021-08-28 11:49:01 -04:00
Laurent Cozic
ad931a738f Desktop release v2.4.3 2021-08-28 15:52:09 +01:00
Laurent Cozic
3b6e6e45cf Desktop: Resolves #5161: Display link to browse plugins when repository cannot be reached 2021-08-28 15:45:27 +01:00
Laurent Cozic
dec0a08954 Desktop, Mobile: Fixes #5391: Fixed crash when a required master key does not exist 2021-08-28 15:36:05 +01:00
Roman
b269c2fdb9 Simplify protocol setup 2021-08-28 14:26:54 +01:00
Laurent Cozic
ad51090cdb Chore: Convert changelog tool to TypeScript 2021-08-28 10:49:31 +01:00
Aaron
bd4714037c Doc: Fix committer name in Joplin Server changelog (#5390)
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-08-28 10:27:21 +01:00
Laurent Cozic
75d118b19e Desktop release v2.4.2 2021-08-27 17:21:05 +01:00
Laurent Cozic
9296b0ae36 Merge branch 'release-2.4' of github.com:laurent22/joplin into release-2.4 2021-08-27 17:19:31 +01:00
Arda Kılıçdağı
1862030cff All: Translation: Update tr_TR.po (#5372) 2021-08-27 17:18:26 +01:00
小骏
8a3eb8616e All: Translation: Update zh_CN.po (#5371) 2021-08-27 17:18:26 +01:00
Laurent Cozic
4c72de8bd8 Desktop: Fixes #5380: Prevent it from crashing with too long search queries 2021-08-27 17:16:09 +01:00
Laurent Cozic
d6da4299ad Desktop: Fixes #5346: "Move to notebook" would break with empty input 2021-08-27 16:58:42 +01:00
Laurent Cozic
f19c4ab434 Desktop: Allow specific deprecated plugins to still work 2021-08-27 16:34:39 +01:00
Laurent Cozic
70efaddeaf All: Do not display master key upgrade warnings for new master keys 2021-08-27 11:30:21 +01:00
Laurent Cozic
814f602bd6 Chore: Moved EncryptionService under e2ee directory 2021-08-23 18:47:07 +01:00
Laurent Cozic
da6a5e3bb8 Doc: Added CVE number for server CSRF vulnerability 2021-08-23 14:53:41 +01:00
Laurent Cozic
385b50d6ce Tools: Create test user with flag 2021-08-23 14:52:07 +01:00
Laurent Cozic
43943299f3 Server: Display user flags in profile when logged in as admin 2021-08-23 14:52:07 +01:00
Laurent Cozic
f11ba29112 Server v2.4.1 2021-08-23 14:51:28 +01:00
Arda Kılıçdağı
edf801a1ef All: Translation: Update tr_TR.po (#5372) 2021-08-23 09:16:54 -04:00
小骏
f441177f7a All: Translation: Update zh_CN.po (#5371) 2021-08-23 09:15:31 -04:00
Laurent Cozic
1503a4e393 Merge branch 'release-2.4' of github.com:laurent22/joplin into release-2.4 2021-08-23 09:59:02 +01:00
Laurent Cozic
3ce23c75e6 Server v2.4.1 2021-08-23 09:58:00 +01:00
Laurent Cozic
5dd8862380 Server v2.4.1 2021-08-23 09:54:12 +01:00
Laurent Cozic
091945d9d6 Merge branch 'release-2.4' of github.com:laurent22/joplin into release-2.4 2021-08-23 09:52:25 +01:00
Laurent Cozic
30c7410cbe Desktop release v2.4.1 2021-08-23 09:51:49 +01:00
Piotr Banasik
9b2094f688 Server: Switch to node:16-bullseye base image (#5202)
This uses newer libs which are needed to build some of the dependencies which are not available pre-built on non-amd64 architectures.

Also to instal sqlite from source python seems to be needed.
2021-08-23 09:51:26 +01:00
Caleb John
4ba417a2f4 Desktop: Various improvements to Markdown import and export (#5290)
In preparation for #5224
2021-08-23 00:35:45 +01:00
Caleb John
e3bd24f819 Desktop: Resolves #5364: Disable inline code background in vim mode (#5370)
Also ensure that highlight marks are shown above inline code
2021-08-23 00:34:04 +01:00
Laurent
84d95c6dcb Doc: Add more pull request guidelines 2021-08-22 22:10:59 +01:00
Laurent Cozic
6e087bcb23 Server: Handle flags for accounts over limit 2021-08-22 13:10:29 +01:00
Laurent Cozic
f922e9a239 Server: Moved database types to separate file 2021-08-22 11:43:41 +01:00
Laurent Cozic
82b157b491 Server: Add support for user flags 2021-08-22 11:28:15 +01:00
Trent Larson
2ae51acd29 Doc: grammar fix (#5366) 2021-08-22 10:37:55 +01:00
Roman
f42fd0ecce Fix enum usage 2021-08-20 21:43:37 +01:00
Roman
62c5f433d7 Rename enum values 2021-08-20 21:24:16 +01:00
Roman Musin
e73a4b7286 Merge branch 'dev' into desktop-protocol 2021-08-20 12:02:11 +01:00
Roman
6c18c6ddc7 Use url-parse 2021-08-15 13:48:32 +01:00
Roman
20d1f74ee4 Use enum 2021-08-14 23:30:19 +01:00
Roman
2386abea3e Rename parseUrl -> parseCallbackUrl 2021-08-14 23:18:58 +01:00
Roman
90621a8417 Fix linter errors 2021-08-14 23:07:22 +01:00
Roman
b02baa6891 Update ignore files 2021-08-14 22:34:47 +01:00
Roman
ee46978389 Review comments - throw an error if callback url is not valid 2021-08-14 21:59:54 +01:00
Roman
2b6b4dd916 Rename initialUrl -> initialCallbackUrl 2021-08-14 21:55:47 +01:00
Roman
f0361bf80d Review comments - escape vars in url 2021-08-14 21:53:52 +01:00
Roman
ecf718005d Code review comments 2021-08-14 21:01:03 +01:00
Roman
305d0ffc49 Rename copy folder URL -> copy notebook URL 2021-08-14 20:57:01 +01:00
Roman
f454c4e33b Add a function to check for valid callback url 2021-08-14 20:20:16 +01:00
Roman
047883bd27 Read initial url from a field 2021-08-14 20:12:14 +01:00
Roman
f118f5250f Handle openFolder and openTag too; change the URL format; extract ULR functions to a separate file 2021-08-14 13:33:45 +01:00
Roman
61161039c8 Open the note from URL even if Joplin isn't running 2021-08-13 23:21:14 +01:00
Roman
00504898f2 Cleanup 2021-08-13 22:50:49 +01:00
Roman
0a6390ed96 Actually open the note 2021-08-13 22:45:13 +01:00
Roman
f909fe6670 Trying to get it to work on Linux 2021-08-13 22:06:01 +01:00
Roman
b17d8bc533 Register to handle joplin:// links 2021-08-12 21:51:03 +01:00
305 changed files with 6568 additions and 3722 deletions

View File

@@ -64,6 +64,7 @@ packages/tools/PortableAppsLauncher
packages/fork-*
plugin_types/
readme/
**/commands/index.ts
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
packages/app-cli/app/LinkSelector.d.ts
@@ -84,9 +85,6 @@ packages/app-cli/tests/HtmlToMd.js.map
packages/app-cli/tests/MdToHtml.d.ts
packages/app-cli/tests/MdToHtml.js
packages/app-cli/tests/MdToHtml.js.map
packages/app-cli/tests/MdToMd.d.ts
packages/app-cli/tests/MdToMd.js
packages/app-cli/tests/MdToMd.js.map
packages/app-cli/tests/services/keychain/KeychainService.d.ts
packages/app-cli/tests/services/keychain/KeychainService.js
packages/app-cli/tests/services/keychain/KeychainService.js.map
@@ -120,6 +118,12 @@ packages/app-desktop/InteropServiceHelper.js.map
packages/app-desktop/app.d.ts
packages/app-desktop/app.js
packages/app-desktop/app.js.map
packages/app-desktop/app.reducer.d.ts
packages/app-desktop/app.reducer.js
packages/app-desktop/app.reducer.js.map
packages/app-desktop/app.reducer.test.d.ts
packages/app-desktop/app.reducer.test.js
packages/app-desktop/app.reducer.test.js.map
packages/app-desktop/bridge.d.ts
packages/app-desktop/bridge.js
packages/app-desktop/bridge.js.map
@@ -138,6 +142,9 @@ packages/app-desktop/commands/exportNotes.js.map
packages/app-desktop/commands/focusElement.d.ts
packages/app-desktop/commands/focusElement.js
packages/app-desktop/commands/focusElement.js.map
packages/app-desktop/commands/index.d.ts
packages/app-desktop/commands/index.js
packages/app-desktop/commands/index.js.map
packages/app-desktop/commands/openProfileDirectory.d.ts
packages/app-desktop/commands/openProfileDirectory.js
packages/app-desktop/commands/openProfileDirectory.js.map
@@ -243,6 +250,9 @@ packages/app-desktop/gui/MainScreen/commands/gotoAnything.js.map
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.d.ts
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js.map
packages/app-desktop/gui/MainScreen/commands/index.d.ts
packages/app-desktop/gui/MainScreen/commands/index.js
packages/app-desktop/gui/MainScreen/commands/index.js.map
packages/app-desktop/gui/MainScreen/commands/moveToFolder.d.ts
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js.map
@@ -399,21 +409,24 @@ packages/app-desktop/gui/NoteEditor/NoteEditor.js.map
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.d.ts
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js.map
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.d.ts
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js.map
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.d.ts
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js.map
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.d.ts
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js.map
packages/app-desktop/gui/NoteEditor/commands/index.d.ts
packages/app-desktop/gui/NoteEditor/commands/index.js
packages/app-desktop/gui/NoteEditor/commands/index.js.map
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.d.ts
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js.map
packages/app-desktop/gui/NoteEditor/commands/showRevisions.d.ts
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js.map
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.d.ts
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js.map
packages/app-desktop/gui/NoteEditor/styles/index.d.ts
packages/app-desktop/gui/NoteEditor/styles/index.js
packages/app-desktop/gui/NoteEditor/styles/index.js.map
@@ -468,12 +481,18 @@ packages/app-desktop/gui/NoteList/NoteList.js.map
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.d.ts
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
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/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
packages/app-desktop/gui/NoteListControls/commands/focusSearch.d.ts
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js.map
packages/app-desktop/gui/NoteListControls/commands/index.d.ts
packages/app-desktop/gui/NoteListControls/commands/index.js
packages/app-desktop/gui/NoteListControls/commands/index.js.map
packages/app-desktop/gui/NoteListItem.d.ts
packages/app-desktop/gui/NoteListItem.js
packages/app-desktop/gui/NoteListItem.js.map
@@ -570,12 +589,18 @@ packages/app-desktop/gui/Sidebar/Sidebar.js.map
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.d.ts
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js.map
packages/app-desktop/gui/Sidebar/commands/index.d.ts
packages/app-desktop/gui/Sidebar/commands/index.js
packages/app-desktop/gui/Sidebar/commands/index.js.map
packages/app-desktop/gui/Sidebar/styles/index.d.ts
packages/app-desktop/gui/Sidebar/styles/index.js
packages/app-desktop/gui/Sidebar/styles/index.js.map
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
packages/app-desktop/gui/StatusScreen/StatusScreen.js
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.d.ts
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
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
@@ -864,6 +889,9 @@ packages/lib/JoplinServerApi.js.map
packages/lib/Logger.d.ts
packages/lib/Logger.js
packages/lib/Logger.js.map
packages/lib/ObjectUtils.d.ts
packages/lib/ObjectUtils.js
packages/lib/ObjectUtils.js.map
packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map
@@ -897,6 +925,9 @@ packages/lib/commands/historyBackward.js.map
packages/lib/commands/historyForward.d.ts
packages/lib/commands/historyForward.js
packages/lib/commands/historyForward.js.map
packages/lib/commands/index.d.ts
packages/lib/commands/index.js
packages/lib/commands/index.js.map
packages/lib/commands/synchronize.d.ts
packages/lib/commands/synchronize.js
packages/lib/commands/synchronize.js.map
@@ -939,6 +970,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/hooks/useAsyncEffect.d.ts
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useAsyncEffect.js.map
packages/lib/hooks/useElementSize.d.ts
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useElementSize.js.map
@@ -993,6 +1027,9 @@ packages/lib/models/Folder.test.js.map
packages/lib/models/ItemChange.d.ts
packages/lib/models/ItemChange.js
packages/lib/models/ItemChange.js.map
packages/lib/models/ItemChange.test.d.ts
packages/lib/models/ItemChange.test.js
packages/lib/models/ItemChange.test.js.map
packages/lib/models/MasterKey.d.ts
packages/lib/models/MasterKey.js
packages/lib/models/MasterKey.js.map
@@ -1095,12 +1132,6 @@ packages/lib/services/CommandService.test.js.map
packages/lib/services/DecryptionWorker.d.ts
packages/lib/services/DecryptionWorker.js
packages/lib/services/DecryptionWorker.js.map
packages/lib/services/EncryptionService.d.ts
packages/lib/services/EncryptionService.js
packages/lib/services/EncryptionService.js.map
packages/lib/services/EncryptionService.test.d.ts
packages/lib/services/EncryptionService.test.js
packages/lib/services/EncryptionService.test.js.map
packages/lib/services/ExternalEditWatcher.d.ts
packages/lib/services/ExternalEditWatcher.js
packages/lib/services/ExternalEditWatcher.js.map
@@ -1194,6 +1225,12 @@ packages/lib/services/database/types.js.map
packages/lib/services/debug/populateDatabase.d.ts
packages/lib/services/debug/populateDatabase.js
packages/lib/services/debug/populateDatabase.js.map
packages/lib/services/e2ee/EncryptionService.d.ts
packages/lib/services/e2ee/EncryptionService.js
packages/lib/services/e2ee/EncryptionService.js.map
packages/lib/services/e2ee/EncryptionService.test.d.ts
packages/lib/services/e2ee/EncryptionService.test.js
packages/lib/services/e2ee/EncryptionService.test.js.map
packages/lib/services/e2ee/types.d.ts
packages/lib/services/e2ee/types.js
packages/lib/services/e2ee/types.js.map
@@ -1248,6 +1285,9 @@ packages/lib/services/interop/InteropService_Importer_Jex.js.map
packages/lib/services/interop/InteropService_Importer_Md.d.ts
packages/lib/services/interop/InteropService_Importer_Md.js
packages/lib/services/interop/InteropService_Importer_Md.js.map
packages/lib/services/interop/InteropService_Importer_Md.test.d.ts
packages/lib/services/interop/InteropService_Importer_Md.test.js
packages/lib/services/interop/InteropService_Importer_Md.test.js.map
packages/lib/services/interop/InteropService_Importer_Raw.d.ts
packages/lib/services/interop/InteropService_Importer_Raw.js
packages/lib/services/interop/InteropService_Importer_Raw.js.map
@@ -1404,6 +1444,12 @@ packages/lib/services/rest/actionApi.desktop.js.map
packages/lib/services/rest/routes/auth.d.ts
packages/lib/services/rest/routes/auth.js
packages/lib/services/rest/routes/auth.js.map
packages/lib/services/rest/routes/events.d.ts
packages/lib/services/rest/routes/events.js
packages/lib/services/rest/routes/events.js.map
packages/lib/services/rest/routes/events.test.d.ts
packages/lib/services/rest/routes/events.test.js
packages/lib/services/rest/routes/events.test.js.map
packages/lib/services/rest/routes/folders.d.ts
packages/lib/services/rest/routes/folders.js
packages/lib/services/rest/routes/folders.js.map
@@ -1485,6 +1531,21 @@ packages/lib/services/spellChecker/SpellCheckerService.js.map
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.d.ts
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js.map
packages/lib/services/style/cssToTheme.d.ts
packages/lib/services/style/cssToTheme.js
packages/lib/services/style/cssToTheme.js.map
packages/lib/services/style/cssToTheme.test.d.ts
packages/lib/services/style/cssToTheme.test.js
packages/lib/services/style/cssToTheme.test.js.map
packages/lib/services/style/loadCssToTheme.d.ts
packages/lib/services/style/loadCssToTheme.js
packages/lib/services/style/loadCssToTheme.js.map
packages/lib/services/style/themeToCss.d.ts
packages/lib/services/style/themeToCss.js
packages/lib/services/style/themeToCss.js.map
packages/lib/services/style/themeToCss.test.d.ts
packages/lib/services/style/themeToCss.test.js
packages/lib/services/style/themeToCss.test.js.map
packages/lib/services/synchronizer/ItemUploader.d.ts
packages/lib/services/synchronizer/ItemUploader.js
packages/lib/services/synchronizer/ItemUploader.js.map
@@ -1635,6 +1696,12 @@ packages/plugin-repo-cli/lib/gitCompareUrl.js.map
packages/plugin-repo-cli/lib/gitCompareUrl.test.d.ts
packages/plugin-repo-cli/lib/gitCompareUrl.test.js
packages/plugin-repo-cli/lib/gitCompareUrl.test.js.map
packages/plugin-repo-cli/lib/overrideUtils.d.ts
packages/plugin-repo-cli/lib/overrideUtils.js
packages/plugin-repo-cli/lib/overrideUtils.js.map
packages/plugin-repo-cli/lib/overrideUtils.test.d.ts
packages/plugin-repo-cli/lib/overrideUtils.test.js
packages/plugin-repo-cli/lib/overrideUtils.test.js.map
packages/plugin-repo-cli/lib/types.d.ts
packages/plugin-repo-cli/lib/types.js
packages/plugin-repo-cli/lib/types.js.map
@@ -1644,6 +1711,9 @@ packages/plugin-repo-cli/lib/updateReadme.js.map
packages/plugin-repo-cli/lib/updateReadme.test.d.ts
packages/plugin-repo-cli/lib/updateReadme.test.js
packages/plugin-repo-cli/lib/updateReadme.test.js.map
packages/plugin-repo-cli/lib/utils.d.ts
packages/plugin-repo-cli/lib/utils.js
packages/plugin-repo-cli/lib/utils.js.map
packages/plugins/ToggleSidebars/api/index.d.ts
packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/index.js.map
@@ -1716,6 +1786,9 @@ packages/renderer/MdToHtml/setupLinkify.js.map
packages/renderer/MdToHtml/validateLinks.d.ts
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/headerAnchor.d.ts
packages/renderer/headerAnchor.js
packages/renderer/headerAnchor.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
@@ -1734,12 +1807,18 @@ packages/renderer/utils.js.map
packages/tools/buildServerDocker.d.ts
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.js.map
packages/tools/convertThemesToCss.d.ts
packages/tools/convertThemesToCss.js
packages/tools/convertThemesToCss.js.map
packages/tools/generate-database-types.d.ts
packages/tools/generate-database-types.js
packages/tools/generate-database-types.js.map
packages/tools/generate-images.d.ts
packages/tools/generate-images.js
packages/tools/generate-images.js.map
packages/tools/git-changelog.d.ts
packages/tools/git-changelog.js
packages/tools/git-changelog.js.map
packages/tools/lerna-add.d.ts
packages/tools/lerna-add.js
packages/tools/lerna-add.js.map

View File

@@ -35,6 +35,14 @@ jobs:
sudo apt-get update || true
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
# the next line enables multi-architecture support for docker, it basically makes it use qemu for non native platforms
# See https://hub.docker.com/r/tonistiigi/binfmt for more info
docker run --privileged --rm tonistiigi/binfmt --install all
# this just prints the info about what platforms are supported in the builder (can help debugging if something isn't working right)
# and also proves the above worked properly
sudo docker buildx ls
- uses: actions/checkout@v2
- uses: olegtarasov/get-tag@v2.1
- uses: actions/setup-node@v2

103
.gitignore vendored
View File

@@ -49,6 +49,7 @@ packages/tools/commit_hook.txt
packages/tools/github_oauth_token.txt
lerna-debug.log
.env
docs/**/*.mustache
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
packages/app-cli/app/LinkSelector.d.ts
@@ -69,9 +70,6 @@ packages/app-cli/tests/HtmlToMd.js.map
packages/app-cli/tests/MdToHtml.d.ts
packages/app-cli/tests/MdToHtml.js
packages/app-cli/tests/MdToHtml.js.map
packages/app-cli/tests/MdToMd.d.ts
packages/app-cli/tests/MdToMd.js
packages/app-cli/tests/MdToMd.js.map
packages/app-cli/tests/services/keychain/KeychainService.d.ts
packages/app-cli/tests/services/keychain/KeychainService.js
packages/app-cli/tests/services/keychain/KeychainService.js.map
@@ -105,6 +103,12 @@ packages/app-desktop/InteropServiceHelper.js.map
packages/app-desktop/app.d.ts
packages/app-desktop/app.js
packages/app-desktop/app.js.map
packages/app-desktop/app.reducer.d.ts
packages/app-desktop/app.reducer.js
packages/app-desktop/app.reducer.js.map
packages/app-desktop/app.reducer.test.d.ts
packages/app-desktop/app.reducer.test.js
packages/app-desktop/app.reducer.test.js.map
packages/app-desktop/bridge.d.ts
packages/app-desktop/bridge.js
packages/app-desktop/bridge.js.map
@@ -123,6 +127,9 @@ packages/app-desktop/commands/exportNotes.js.map
packages/app-desktop/commands/focusElement.d.ts
packages/app-desktop/commands/focusElement.js
packages/app-desktop/commands/focusElement.js.map
packages/app-desktop/commands/index.d.ts
packages/app-desktop/commands/index.js
packages/app-desktop/commands/index.js.map
packages/app-desktop/commands/openProfileDirectory.d.ts
packages/app-desktop/commands/openProfileDirectory.js
packages/app-desktop/commands/openProfileDirectory.js.map
@@ -228,6 +235,9 @@ packages/app-desktop/gui/MainScreen/commands/gotoAnything.js.map
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.d.ts
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js
packages/app-desktop/gui/MainScreen/commands/hideModalMessage.js.map
packages/app-desktop/gui/MainScreen/commands/index.d.ts
packages/app-desktop/gui/MainScreen/commands/index.js
packages/app-desktop/gui/MainScreen/commands/index.js.map
packages/app-desktop/gui/MainScreen/commands/moveToFolder.d.ts
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js
packages/app-desktop/gui/MainScreen/commands/moveToFolder.js.map
@@ -384,21 +394,24 @@ packages/app-desktop/gui/NoteEditor/NoteEditor.js.map
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.d.ts
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js.map
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.d.ts
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js
packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js.map
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.d.ts
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js.map
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.d.ts
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js.map
packages/app-desktop/gui/NoteEditor/commands/index.d.ts
packages/app-desktop/gui/NoteEditor/commands/index.js
packages/app-desktop/gui/NoteEditor/commands/index.js.map
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.d.ts
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js.map
packages/app-desktop/gui/NoteEditor/commands/showRevisions.d.ts
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js.map
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.d.ts
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js.map
packages/app-desktop/gui/NoteEditor/styles/index.d.ts
packages/app-desktop/gui/NoteEditor/styles/index.js
packages/app-desktop/gui/NoteEditor/styles/index.js.map
@@ -453,12 +466,18 @@ packages/app-desktop/gui/NoteList/NoteList.js.map
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.d.ts
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
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/NoteListControls/NoteListControls.d.ts
packages/app-desktop/gui/NoteListControls/NoteListControls.js
packages/app-desktop/gui/NoteListControls/NoteListControls.js.map
packages/app-desktop/gui/NoteListControls/commands/focusSearch.d.ts
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js.map
packages/app-desktop/gui/NoteListControls/commands/index.d.ts
packages/app-desktop/gui/NoteListControls/commands/index.js
packages/app-desktop/gui/NoteListControls/commands/index.js.map
packages/app-desktop/gui/NoteListItem.d.ts
packages/app-desktop/gui/NoteListItem.js
packages/app-desktop/gui/NoteListItem.js.map
@@ -555,12 +574,18 @@ packages/app-desktop/gui/Sidebar/Sidebar.js.map
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.d.ts
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js.map
packages/app-desktop/gui/Sidebar/commands/index.d.ts
packages/app-desktop/gui/Sidebar/commands/index.js
packages/app-desktop/gui/Sidebar/commands/index.js.map
packages/app-desktop/gui/Sidebar/styles/index.d.ts
packages/app-desktop/gui/Sidebar/styles/index.js
packages/app-desktop/gui/Sidebar/styles/index.js.map
packages/app-desktop/gui/StatusScreen/StatusScreen.d.ts
packages/app-desktop/gui/StatusScreen/StatusScreen.js
packages/app-desktop/gui/StatusScreen/StatusScreen.js.map
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.d.ts
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
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
@@ -849,6 +874,9 @@ packages/lib/JoplinServerApi.js.map
packages/lib/Logger.d.ts
packages/lib/Logger.js
packages/lib/Logger.js.map
packages/lib/ObjectUtils.d.ts
packages/lib/ObjectUtils.js
packages/lib/ObjectUtils.js.map
packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map
@@ -882,6 +910,9 @@ packages/lib/commands/historyBackward.js.map
packages/lib/commands/historyForward.d.ts
packages/lib/commands/historyForward.js
packages/lib/commands/historyForward.js.map
packages/lib/commands/index.d.ts
packages/lib/commands/index.js
packages/lib/commands/index.js.map
packages/lib/commands/synchronize.d.ts
packages/lib/commands/synchronize.js
packages/lib/commands/synchronize.js.map
@@ -924,6 +955,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/hooks/useAsyncEffect.d.ts
packages/lib/hooks/useAsyncEffect.js
packages/lib/hooks/useAsyncEffect.js.map
packages/lib/hooks/useElementSize.d.ts
packages/lib/hooks/useElementSize.js
packages/lib/hooks/useElementSize.js.map
@@ -978,6 +1012,9 @@ packages/lib/models/Folder.test.js.map
packages/lib/models/ItemChange.d.ts
packages/lib/models/ItemChange.js
packages/lib/models/ItemChange.js.map
packages/lib/models/ItemChange.test.d.ts
packages/lib/models/ItemChange.test.js
packages/lib/models/ItemChange.test.js.map
packages/lib/models/MasterKey.d.ts
packages/lib/models/MasterKey.js
packages/lib/models/MasterKey.js.map
@@ -1080,12 +1117,6 @@ packages/lib/services/CommandService.test.js.map
packages/lib/services/DecryptionWorker.d.ts
packages/lib/services/DecryptionWorker.js
packages/lib/services/DecryptionWorker.js.map
packages/lib/services/EncryptionService.d.ts
packages/lib/services/EncryptionService.js
packages/lib/services/EncryptionService.js.map
packages/lib/services/EncryptionService.test.d.ts
packages/lib/services/EncryptionService.test.js
packages/lib/services/EncryptionService.test.js.map
packages/lib/services/ExternalEditWatcher.d.ts
packages/lib/services/ExternalEditWatcher.js
packages/lib/services/ExternalEditWatcher.js.map
@@ -1179,6 +1210,12 @@ packages/lib/services/database/types.js.map
packages/lib/services/debug/populateDatabase.d.ts
packages/lib/services/debug/populateDatabase.js
packages/lib/services/debug/populateDatabase.js.map
packages/lib/services/e2ee/EncryptionService.d.ts
packages/lib/services/e2ee/EncryptionService.js
packages/lib/services/e2ee/EncryptionService.js.map
packages/lib/services/e2ee/EncryptionService.test.d.ts
packages/lib/services/e2ee/EncryptionService.test.js
packages/lib/services/e2ee/EncryptionService.test.js.map
packages/lib/services/e2ee/types.d.ts
packages/lib/services/e2ee/types.js
packages/lib/services/e2ee/types.js.map
@@ -1233,6 +1270,9 @@ packages/lib/services/interop/InteropService_Importer_Jex.js.map
packages/lib/services/interop/InteropService_Importer_Md.d.ts
packages/lib/services/interop/InteropService_Importer_Md.js
packages/lib/services/interop/InteropService_Importer_Md.js.map
packages/lib/services/interop/InteropService_Importer_Md.test.d.ts
packages/lib/services/interop/InteropService_Importer_Md.test.js
packages/lib/services/interop/InteropService_Importer_Md.test.js.map
packages/lib/services/interop/InteropService_Importer_Raw.d.ts
packages/lib/services/interop/InteropService_Importer_Raw.js
packages/lib/services/interop/InteropService_Importer_Raw.js.map
@@ -1389,6 +1429,12 @@ packages/lib/services/rest/actionApi.desktop.js.map
packages/lib/services/rest/routes/auth.d.ts
packages/lib/services/rest/routes/auth.js
packages/lib/services/rest/routes/auth.js.map
packages/lib/services/rest/routes/events.d.ts
packages/lib/services/rest/routes/events.js
packages/lib/services/rest/routes/events.js.map
packages/lib/services/rest/routes/events.test.d.ts
packages/lib/services/rest/routes/events.test.js
packages/lib/services/rest/routes/events.test.js.map
packages/lib/services/rest/routes/folders.d.ts
packages/lib/services/rest/routes/folders.js
packages/lib/services/rest/routes/folders.js.map
@@ -1470,6 +1516,21 @@ packages/lib/services/spellChecker/SpellCheckerService.js.map
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.d.ts
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js.map
packages/lib/services/style/cssToTheme.d.ts
packages/lib/services/style/cssToTheme.js
packages/lib/services/style/cssToTheme.js.map
packages/lib/services/style/cssToTheme.test.d.ts
packages/lib/services/style/cssToTheme.test.js
packages/lib/services/style/cssToTheme.test.js.map
packages/lib/services/style/loadCssToTheme.d.ts
packages/lib/services/style/loadCssToTheme.js
packages/lib/services/style/loadCssToTheme.js.map
packages/lib/services/style/themeToCss.d.ts
packages/lib/services/style/themeToCss.js
packages/lib/services/style/themeToCss.js.map
packages/lib/services/style/themeToCss.test.d.ts
packages/lib/services/style/themeToCss.test.js
packages/lib/services/style/themeToCss.test.js.map
packages/lib/services/synchronizer/ItemUploader.d.ts
packages/lib/services/synchronizer/ItemUploader.js
packages/lib/services/synchronizer/ItemUploader.js.map
@@ -1620,6 +1681,12 @@ packages/plugin-repo-cli/lib/gitCompareUrl.js.map
packages/plugin-repo-cli/lib/gitCompareUrl.test.d.ts
packages/plugin-repo-cli/lib/gitCompareUrl.test.js
packages/plugin-repo-cli/lib/gitCompareUrl.test.js.map
packages/plugin-repo-cli/lib/overrideUtils.d.ts
packages/plugin-repo-cli/lib/overrideUtils.js
packages/plugin-repo-cli/lib/overrideUtils.js.map
packages/plugin-repo-cli/lib/overrideUtils.test.d.ts
packages/plugin-repo-cli/lib/overrideUtils.test.js
packages/plugin-repo-cli/lib/overrideUtils.test.js.map
packages/plugin-repo-cli/lib/types.d.ts
packages/plugin-repo-cli/lib/types.js
packages/plugin-repo-cli/lib/types.js.map
@@ -1629,6 +1696,9 @@ packages/plugin-repo-cli/lib/updateReadme.js.map
packages/plugin-repo-cli/lib/updateReadme.test.d.ts
packages/plugin-repo-cli/lib/updateReadme.test.js
packages/plugin-repo-cli/lib/updateReadme.test.js.map
packages/plugin-repo-cli/lib/utils.d.ts
packages/plugin-repo-cli/lib/utils.js
packages/plugin-repo-cli/lib/utils.js.map
packages/plugins/ToggleSidebars/api/index.d.ts
packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/index.js.map
@@ -1701,6 +1771,9 @@ packages/renderer/MdToHtml/setupLinkify.js.map
packages/renderer/MdToHtml/validateLinks.d.ts
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/headerAnchor.d.ts
packages/renderer/headerAnchor.js
packages/renderer/headerAnchor.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
@@ -1719,12 +1792,18 @@ packages/renderer/utils.js.map
packages/tools/buildServerDocker.d.ts
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.js.map
packages/tools/convertThemesToCss.d.ts
packages/tools/convertThemesToCss.js
packages/tools/convertThemesToCss.js.map
packages/tools/generate-database-types.d.ts
packages/tools/generate-database-types.js
packages/tools/generate-database-types.js.map
packages/tools/generate-images.d.ts
packages/tools/generate-images.js
packages/tools/generate-images.js.map
packages/tools/git-changelog.d.ts
packages/tools/git-changelog.js
packages/tools/git-changelog.js.map
packages/tools/lerna-add.d.ts
packages/tools/lerna-add.js
packages/tools/lerna-add.js.map

View File

@@ -29,7 +29,7 @@ ol, ul {
#main-container {
position: relative;
min-height: 100vh;
padding-bottom: 225px;
padding-bottom: 127px; /* Needs to be the height of the footer */
}
.help-page-container img {
@@ -583,7 +583,7 @@ div.navbar-mobile-content a.sponsor-button {
/* footer section */
footer {
padding-top: 50px;
padding-top: 30px;
padding-bottom: 30px;
position: absolute;
bottom: 0;
@@ -594,6 +594,7 @@ footer a,
footer p {
color: #90b1d9;
text-decoration: none;
margin-bottom: 0;
}
footer a:hover {
@@ -617,6 +618,35 @@ footer .right-links a {
margin-left: 15px;
}
footer .footer-right {
margin-left: 30px;
}
footer .social-links {
display: flex;
flex-direction: row;
justify-content: center;
padding-bottom: 20px;
border-bottom: 1px solid #315885;
}
footer .social-links a:hover {
color: #e8f3ff;
}
footer .social-links i {
margin-left: 15px;
font-size: 22px;
}
footer .bottom-links-row {
padding-top: 20px;
}
footer .bottom-links-row p {
font-size: 1.1em;
}
/*****************************************************************
IN THE PRESS
The "In the press" section height needs to be adjusted as the
@@ -679,7 +709,7 @@ footer .right-links a {
#main-container {
position: relative;
min-height: 100vh;
padding-bottom: 260px;
padding-bottom: 130px;
}
.front-page h1 {
@@ -866,7 +896,7 @@ footer .right-links a {
.help-page-container {
padding-top: 90px;
padding-bottom: 50px;
padding-bottom: 70px;
}
.donate-links {
@@ -880,7 +910,7 @@ footer .right-links a {
border-radius: 20px;
padding: 30px 20px;
padding-bottom: 30px;
margin-bottom: 50px;
margin-bottom: 20px;
margin-top: 40px;
}
@@ -923,6 +953,11 @@ footer .right-links a {
justify-content: center;
}
.plan-group .joplin-cloud-login-info {
margin-bottom: 40px;
text-align: center;
}
.plan-group .plan-price-yearly-per-year {
display: flex;
justify-content: flex-end;

View File

@@ -368,40 +368,7 @@
</div>
</div>
<footer class="darkblue-bg">
<div class="container">
<div class="row">
<div class="col-3 d-none d-md-block">
<img src="{{imageBaseUrl}}/logo-text.svg" alt="" width="150" />
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<a href="{{baseUrl}}">
<img
src="{{imageBaseUrl}}/logo-text.svg"
width="120"
class="img-center d-block d-md-none"
alt=""
/>
</a>
<br class="d-block d-md-none" />
<p class="text-center-sm">Copyright &copy; 2016-{{yyyy}} Laurent Cozic</p>
</div>
<div class="col-12 col-md-6">
<p class="text-right text-center-sm right-links">
<a href="https://github.com/laurent22/joplin/" class="github-link"><i class="fab fa-github"></i> GitHub Repository</a>
<a href="{{baseUrl}}/privacy/">Privacy Policy</a>
</p>
</div>
</div>
</div>
</footer>
{{> footer}}
</div>
<script

View File

@@ -75,38 +75,8 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
</div>
</div>
<footer class="darkblue-bg">
<div class="container">
<div class="row">
<div class="col-3 d-none d-md-block">
<img src="{{imageBaseUrl}}/logo-text.svg" alt="" width="150" />
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<img
src="{{imageBaseUrl}}/logo-text.svg"
width="120"
class="img-center d-block d-md-none"
alt=""
/>
<br class="d-block d-md-none" />
<p class="text-center-sm">Copyright &copy; 2016-{{yyyy}} Laurent Cozic</p>
</div>
<div class="col-12 col-md-6 right-links">
<p class="text-right text-center-sm">
<a href="https://github.com/laurent22/joplin/" class="github-link"><i class="fab fa-github"></i> GitHub Repository</a>
<a href="{{baseUrl}}/privacy/">Privacy Policy</a>
</p>
</div>
</div>
</div>
</footer>
{{> footer}}
</div>
<script src="{{jsBaseUrl}}/script.js?t={{buildTime}}"></script>

View File

@@ -0,0 +1,26 @@
<footer class="darkblue-bg">
<div class="container">
<div class="row">
<div class="col-12 col-md-12 social-links">
<a href="https://twitter.com/joplinapp" title="Twitter feed"><i class="fab fa-twitter"></i></a>
<a href="https://github.com/laurent22/joplin/" title="GitHub repository"><i class="fab fa-github"></i></a>
<a href="https://www.patreon.com/joplin" title="Patreon blog"><i class="fab fa-patreon"></i></a>
<a href="https://discordapp.com/invite/d2HMPwE" title="Discord chat"><i class="fab fa-discord"></i></a>
<a href="https://www.reddit.com/r/joplinapp/" title="Subreddit"><i class="fab fa-reddit"></i></a>
</div>
</div>
<div class="row bottom-links-row">
<div class="col-12 col-md-6">
<p class="text-center-sm">Copyright &copy; 2016-{{yyyy}} Laurent Cozic</p>
</div>
<div class="col-12 col-md-6">
<p class="text-right text-center-sm right-links">
<span class="footer-right">
<a href="{{baseUrl}}/privacy/">Privacy Policy</a>
</span>
</p>
</div>
</div>
</div>
</footer>

View File

@@ -6,7 +6,7 @@
Joplin Cloud <span class="frame-bg frame-bg-yellow">plans</span>
</h1>
<p class="text-center sub-title">
Joplin Cloud allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
</p>
</div>
</div>
@@ -45,6 +45,8 @@
{{#plans.business}}
{{> plan}}
{{/plans.business}}
<p class="joplin-cloud-login-info">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
</div>
<div class="row">

View File

@@ -31,12 +31,14 @@ Joplin is available in multiple languages thanks to the help of its users. You c
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
- Explain WHY you want to add this change. Explain it inside the pull request and you may link to an issue for additional information, but the PR should gives a clear overview of why you want to add this.
- Bug fixes are always welcome. Start by reviewing the [list of bugs](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
- A good way to easily start contributing is to pick and work on a [good first issue](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). We try to make these issues as clear as possible and provide basic info on how the code should be changed, and if something is unclear feel free to ask for more information on the issue.
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
- All the applications share the same backend (database, synchronisation, settings, models, business logic, etc.) so if you change something in the backend in one app, makes sure it still work in the other apps. Usually it does, but keep this in mind.
- Pull requests that make many changes using an automated tool, like for spell fixing, styling, etc. will not be accepted. An exception would be if the changes have been discussed in the forum and someone has agreed to review **and test** the pull request.
- Pull requests that make address multiple issues will most likely stall and eventually be closed. This is because we might be fine with one of the changes but not with others and untangling that kind of pull request is too much hassle both for maintainers and the person who submitted it. So most of the time someone gives up and the PR gets closed. So please keep the pull request focused on one issue.
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/dev/BUILD.md) for more details.

View File

@@ -1,6 +1,11 @@
# https://versatile.nl/blog/deploying-lerna-web-apps-with-docker
FROM node:16
FROM node:16-bullseye
RUN apt-get update \
&& apt-get install -y \
python \
&& rm -rf /var/lib/apt/lists/*
RUN echo "Node: $(node --version)"
RUN echo "Npm: $(npm --version)"

View File

@@ -184,7 +184,7 @@ if command -v lsb_release &> /dev/null; then
# Check for "The SUID sandbox helper binary was found, but is not configured correctly" problem.
# It is present in Debian 1X. A (temporary) patch will be applied at .desktop file
# Linux Mint 4 Debbie is based on Debian 10 and requires the same param handling.
if [ $DISTVER =~ Debian1. ] || [ "$DISTVER" = "Linuxmint4" ] && [ "$DISTCODENAME" = "debbie" ]
if [[ $DISTVER =~ Debian1. ]] || [ "$DISTVER" = "Linuxmint4" ] && [ "$DISTCODENAME" = "debbie" ]
then
SANDBOXPARAM=" --no-sandbox"
fi

View File

@@ -511,7 +511,7 @@ Current translations:
<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) | jmontane, 2019 | 95%
<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) | 96%
<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) | 95%
<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) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 96%
<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) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 99%
<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) | [Atalanttore](mailto:atalanttore@googlemail.com) | 95%
<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) | | 54%
<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%
@@ -527,7 +527,7 @@ Current translations:
<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) | | 87%
<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) | 90%
<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 | 96%
<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) | 68%
<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) | 67%
<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) | 90%
<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) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 96%
<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) | 90%
@@ -536,13 +536,13 @@ Current translations:
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 42%
<img src="https://joplinapp.org/images/flags/country-4x3/vi.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 96%
<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) | 95%
<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) | 99%
<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) | 89%
<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) | 92%
<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) | 90%
<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) | | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [南宫小骏](mailto:jackytsu@vip.qq.com) | 96%
<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) | [Po-Chiang Chao](mailto:BobChao%29%20%28bobchao@gmail.com) | 95%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [南宫小骏](mailto:jackytsu@vip.qq.com) | 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) | [Po-Chiang Chao](mailto:BobChao%29%20%28bobchao@gmail.com) | 94%
<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) | 95%
<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) | 95%
<!-- LOCALE-TABLE-AUTO-GENERATED -->

View File

@@ -5,6 +5,7 @@ const tasks = {
// copyLib: require('./packages/tools/gulp/tasks/copyLib'),
// tsc: require('./packages/tools/gulp/tasks/tsc'),
updateIgnoredTypeScriptBuild: require('./packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
// deleteBuildDirs: require('./packages/tools/gulp/tasks/deleteBuildDirs'),
completePublishAll: {
fn: async () => {

View File

@@ -12,6 +12,7 @@
"bootstrapServerOnly": "lerna bootstrap --force-local --no-ci --include-dependents --include-dependencies --scope @joplin/server",
"build": "lerna run build && npm run tsc",
"buildApiDoc": "npm start --prefix=packages/app-cli -- apidoc ../../readme/api/references/rest_api.md",
"buildCommandIndex": "gulp buildCommandIndex",
"buildDoc": "./packages/tools/build-all.sh",
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out docs/api/references/plugin_api packages/lib/services/plugins/api/",
"buildSettingJsonSchema": "npm start --prefix=packages/app-cli -- settingschema ../../docs/schema/settings.json",

View File

@@ -381,6 +381,30 @@ async function fetchAllNotes() {
}
}
{
const tableFields = reg.db().tableFields('item_changes', { includeDescription: true });
lines.push('# Events');
lines.push('');
lines.push('This end point can be used to retrieve the latest note changes. Currently only note changes are tracked.');
lines.push('');
lines.push('## Properties');
lines.push('');
lines.push(this.createPropertiesTable(tableFields));
lines.push('');
lines.push('## GET /events');
lines.push('');
lines.push('Returns a paginated list of recent events. A `cursor` property should be provided, which tells from what point in time the events should be returned. The API will return a `cursor` property, to tell from where to resume retrieving events, as well as an `has_more` (tells if more changes can be retrieved) and `items` property, which will contain the list of events. Events are kept for up to 90 days.');
lines.push('');
lines.push('If no `cursor` property is provided, the API will respond with the latest change ID. That can be used to retrieve future events later on.');
lines.push('');
lines.push('The results are paginated so will need to may multiple calls to retrieve all the events. Use the `has_more` property to know if more can be retrieved.');
lines.push('');
lines.push('## GET /events/:id');
lines.push('');
lines.push('Returns the event with the given ID.');
}
const outFilePath = args['file'];
await shim.fsDriver().writeFile(outFilePath, lines.join('\n'), 'utf8');

View File

@@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
import { _ } from '@joplin/lib/locale';
import EncryptionService from '@joplin/lib/services/EncryptionService';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
import BaseItem from '@joplin/lib/models/BaseItem';
import Setting from '@joplin/lib/models/Setting';

View File

@@ -27,7 +27,7 @@ const { shimInit } = require('@joplin/lib/shim-init-node.js');
const shim = require('@joplin/lib/shim').default;
const { _ } = require('@joplin/lib/locale');
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local.js');
const EncryptionService = require('@joplin/lib/services/EncryptionService').default;
const EncryptionService = require('@joplin/lib/services/e2ee/EncryptionService').default;
const envFromArgs = require('@joplin/lib/envFromArgs');
const env = envFromArgs(process.argv);

View File

@@ -1,50 +0,0 @@
const mdImporterService = require('@joplin/lib/services/interop/InteropService_Importer_Md').default;
const Note = require('@joplin/lib/models/Note').default;
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
const importer = new mdImporterService();
describe('InteropService_Importer_Md: importLocalImages', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should import linked files and modify tags appropriately', async function() {
const tagNonExistentFile = '![does not exist](does_not_exist.png)';
const note = await importer.importFile(`${__dirname}/md_to_md/sample.md`, 'notebook');
const items = await Note.linkedItems(note.body);
expect(items.length).toBe(2);
const inexistentLinkUnchanged = note.body.includes(tagNonExistentFile);
expect(inexistentLinkUnchanged).toBe(true);
});
it('should only create 1 resource for duplicate links, all tags should be updated', async function() {
const note = await importer.importFile(`${__dirname}/md_to_md/sample-duplicate-links.md`, 'notebook');
const items = await Note.linkedItems(note.body);
expect(items.length).toBe(1);
const reg = new RegExp(items[0].id, 'g');
const matched = note.body.match(reg);
expect(matched.length).toBe(2);
});
it('should import linked files and modify tags appropriately when link is also in alt text', async function() {
const note = await importer.importFile(`${__dirname}/md_to_md/sample-link-in-alt-text.md`, 'notebook');
const items = await Note.linkedItems(note.body);
expect(items.length).toBe(1);
});
it('should passthrough unchanged if no links present', async function() {
const note = await importer.importFile(`${__dirname}/md_to_md/sample-no-links.md`, 'notebook');
const items = await Note.linkedItems(note.body);
expect(items.length).toBe(0);
expect(note.body).toContain('Unidentified vessel travelling at sub warp speed, bearing 235.7. Fluctuations in energy readings from it, Captain. All transporters off.');
});
it('should import linked image with special characters in name', async function() {
const note = await importer.importFile(`${__dirname}/md_to_md/sample-special-chars.md`, 'notebook');
const items = await Note.linkedItems(note.body);
expect(items.length).toBe(1);
});
it('should import resources for files', async function() {
const note = await importer.importFile(`${__dirname}/md_to_md/sample-files.md`, 'notebook');
const items = await Note.linkedItems(note.body);
expect(items.length).toBe(4);
});
});

View File

@@ -1,2 +0,0 @@
![link 1](../support/photo.jpg)
![link 2](../support/photo.jpg)

View File

@@ -1,9 +0,0 @@
# Markdown file test
![../support/photo.jpg](../support/photo.jpg)
[welcome.pdf](../support/welcome.pdf)
[sample.md](sample.md)
[sample2.md](./sample.md)

View File

@@ -1,3 +0,0 @@
# Markdown
![../support/photo.jpg](../support/photo.jpg) should put resource link inside () not []
![../support/photo.jpg]( ../support/photo.jpg ) this case (spaces before/after link but within parens) is not currently covered

View File

@@ -1 +0,0 @@
![link special chars](../support/photo-åäö.jpg)

View File

@@ -1,13 +0,0 @@
# Markdown
lorem ipsum ![alt text here](../support/photo.jpg)
- [ ] check!
- [ ] boxes!
![alt text here](../support/photo-two.jpg)ipsum lorem
**strong text**
![does not exist](does_not_exist.png) lorem ipsum
**some directory**
![a directory](../support) lorem ipsum

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,3 @@
# Test Spaces
I hope this get's imported correctly!

View File

@@ -0,0 +1 @@
[Section 1](./sample-no-links.md#markdown)

View File

@@ -0,0 +1,3 @@
# Markdown file test
[sample.md](sample-cycles-b.md)

View File

@@ -0,0 +1,4 @@
# Markdown file test
[sample.md](./sample-cycles-a.md)

View File

@@ -0,0 +1,2 @@
![link 1](../../photo.jpg)
![link 2](../../photo.jpg)

View File

@@ -0,0 +1 @@
![sample](file://../../photo.jpg)

View File

@@ -0,0 +1,9 @@
# Markdown file test
![../../photo.jpg](../../photo.jpg)
[welcome.pdf](../../welcome.pdf)
[sample.md](sample.md)
[sample2.md](./sample.md)

View File

@@ -0,0 +1,3 @@
# Markdown
![../../photo.jpg](../../photo.jpg) should put resource link inside () not []
![../../photo.jpg]( ../../photo.jpg ) this case (spaces before/after link but within parens) is not currently covered

View File

@@ -0,0 +1,3 @@
![Alt text](../../photo.jpg "Scott Joplin")
![Worst Case](<../../photo sample.jpg> "title")
[Worst Case](<./sample spaces.md> "title")

View File

@@ -0,0 +1 @@
I am here, but am I alive?

View File

@@ -0,0 +1,3 @@
# Some Title
[link](./sample-md)

View File

@@ -0,0 +1,4 @@
![link special chars](../../photo-åäö.jpg)
[sample photo](../../photo%20sample.jpg)
[sample.md](./sample%20spaces.md)
[sample special syntax](<../../photo sample.jpg>)

View File

@@ -0,0 +1,4 @@
<img src="../../photo.jpg">
<img src='../../photo-two.jpg'>
<img src='does-not-exist' alt="../../photo.jpg">
<a href="./sample-no-links.md">

View File

@@ -0,0 +1,13 @@
# Markdown
lorem ipsum ![alt text here](../../photo.jpg)
- [ ] check!
- [ ] boxes!
![alt text here](../../photo-two.jpg)ipsum lorem
**strong text**
![does not exist](does_not_exist.png) lorem ipsum
**some directory**
![a directory](../..) lorem ipsum

View File

@@ -237,7 +237,7 @@ export default class ElectronAppWrapper {
const iid = setInterval(() => {
if (this.electronApp().isReady()) {
clearInterval(iid);
resolve();
resolve(null);
}
}, 10);
});

View File

@@ -0,0 +1,47 @@
import { AppState } from './app.reducer';
import appReducer, { createAppDefaultState } from './app.reducer';
describe('app.reducer', function() {
it('DIALOG_OPEN', async () => {
const state: AppState = createAppDefaultState({}, {});
let newState = appReducer(state, {
type: 'DIALOG_OPEN',
name: 'syncWizard',
});
expect(newState.dialogs.length).toBe(1);
expect(newState.dialogs[0].name).toBe('syncWizard');
expect(() => appReducer(newState, {
type: 'DIALOG_OPEN',
name: 'syncWizard',
})).toThrow();
newState = appReducer(newState, {
type: 'DIALOG_CLOSE',
name: 'syncWizard',
});
expect(newState.dialogs.length).toBe(0);
expect(() => appReducer(newState, {
type: 'DIALOG_CLOSE',
name: 'syncWizard',
})).toThrow();
newState = appReducer(newState, {
type: 'DIALOG_OPEN',
name: 'syncWizard',
});
newState = appReducer(newState, {
type: 'DIALOG_OPEN',
name: 'setPassword',
});
expect(newState.dialogs).toEqual([{ name: 'syncWizard' }, { name: 'setPassword' }]);
});
});

View File

@@ -0,0 +1,316 @@
import produce from 'immer';
import Setting from '@joplin/lib/models/Setting';
import { defaultState, State } from '@joplin/lib/reducer';
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
import { LayoutItem } from './gui/ResizableLayout/utils/types';
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
export interface AppStateRoute {
type: string;
routeName: string;
props: any;
}
export enum AppStateDialogName {
SyncWizard = 'syncWizard',
MasterPassword = 'masterPassword',
}
export interface AppStateDialog {
name: AppStateDialogName;
}
export interface AppState extends State {
route: AppStateRoute;
navHistory: any[];
noteVisiblePanes: string[];
windowContentSize: any;
watchedNoteFiles: string[];
lastEditorScrollPercents: any;
devToolsVisible: boolean;
visibleDialogs: any; // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
focusedField: string;
layoutMoveMode: boolean;
startupPluginsLoaded: boolean;
// Extra reducer keys go here
watchedResources: any;
mainLayout: LayoutItem;
dialogs: AppStateDialog[];
}
export function createAppDefaultState(windowContentSize: any, resourceEditWatcherDefaultState: any): AppState {
return {
...defaultState,
route: {
type: 'NAV_GO',
routeName: 'Main',
props: {},
},
navHistory: [],
noteVisiblePanes: ['editor', 'viewer'],
windowContentSize, // bridge().windowContentSize(),
watchedNoteFiles: [],
lastEditorScrollPercents: {},
devToolsVisible: false,
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
focusedField: null,
layoutMoveMode: false,
mainLayout: null,
startupPluginsLoaded: false,
dialogs: [],
...resourceEditWatcherDefaultState,
};
}
export default function(state: AppState, action: any) {
let newState = state;
try {
switch (action.type) {
case 'NAV_BACK':
case 'NAV_GO':
{
const goingBack = action.type === 'NAV_BACK';
if (goingBack && !state.navHistory.length) break;
const currentRoute = state.route;
newState = Object.assign({}, state);
const newNavHistory = state.navHistory.slice();
if (goingBack) {
let newAction = null;
while (newNavHistory.length) {
newAction = newNavHistory.pop();
if (newAction.routeName !== state.route.routeName) break;
}
if (!newAction) break;
action = newAction;
}
if (!goingBack) newNavHistory.push(currentRoute);
newState.navHistory = newNavHistory;
newState.route = action;
}
break;
case 'STARTUP_PLUGINS_LOADED':
// When all startup plugins have loaded, we also recreate the
// main layout to ensure that it is updated in the UI. There's
// probably a cleaner way to do this, but for now that will do.
if (state.startupPluginsLoaded !== action.value) {
newState = {
...newState,
startupPluginsLoaded: action.value,
mainLayout: JSON.parse(JSON.stringify(newState.mainLayout)),
};
}
break;
case 'WINDOW_CONTENT_SIZE_SET':
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
case 'NOTE_VISIBLE_PANES_TOGGLE':
{
const getNextLayout = (currentLayout: any) => {
currentLayout = panes.length === 2 ? 'both' : currentLayout[0];
let paneOptions;
if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_VIEWER) {
paneOptions = ['editor', 'viewer'];
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_SPLIT) {
paneOptions = ['editor', 'both'];
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
paneOptions = ['viewer', 'both'];
} else {
paneOptions = ['editor', 'viewer', 'both'];
}
const currentLayoutIndex = paneOptions.indexOf(currentLayout);
const nextLayoutIndex = currentLayoutIndex === paneOptions.length - 1 ? 0 : currentLayoutIndex + 1;
const nextLayout = paneOptions[nextLayoutIndex];
return nextLayout === 'both' ? ['editor', 'viewer'] : [nextLayout];
};
newState = Object.assign({}, state);
const panes = state.noteVisiblePanes.slice();
newState.noteVisiblePanes = getNextLayout(panes);
}
break;
case 'NOTE_VISIBLE_PANES_SET':
newState = Object.assign({}, state);
newState.noteVisiblePanes = action.panes;
break;
case 'MAIN_LAYOUT_SET':
newState = {
...state,
mainLayout: action.value,
};
break;
case 'MAIN_LAYOUT_SET_ITEM_PROP':
{
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
if (item.key === action.itemKey) {
(item as any)[action.propName] = action.propValue;
return false;
}
return true;
});
});
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
newState = {
...state,
mainLayout: newLayout,
};
}
break;
case 'NOTE_FILE_WATCHER_ADD':
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
newState = Object.assign({}, state);
const watchedNoteFiles = newState.watchedNoteFiles.slice();
watchedNoteFiles.push(action.id);
newState.watchedNoteFiles = watchedNoteFiles;
}
break;
case 'NOTE_FILE_WATCHER_REMOVE':
{
newState = Object.assign({}, state);
const idx = newState.watchedNoteFiles.indexOf(action.id);
if (idx >= 0) {
const watchedNoteFiles = newState.watchedNoteFiles.slice();
watchedNoteFiles.splice(idx, 1);
newState.watchedNoteFiles = watchedNoteFiles;
}
}
break;
case 'NOTE_FILE_WATCHER_CLEAR':
if (state.watchedNoteFiles.length) {
newState = Object.assign({}, state);
newState.watchedNoteFiles = [];
}
break;
case 'EDITOR_SCROLL_PERCENT_SET':
{
newState = Object.assign({}, state);
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
newPercents[action.noteId] = action.percent;
newState.lastEditorScrollPercents = newPercents;
}
break;
case 'NOTE_DEVTOOLS_TOGGLE':
newState = Object.assign({}, state);
newState.devToolsVisible = !newState.devToolsVisible;
break;
case 'NOTE_DEVTOOLS_SET':
newState = Object.assign({}, state);
newState.devToolsVisible = action.value;
break;
case 'VISIBLE_DIALOGS_ADD':
newState = Object.assign({}, state);
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
newState.visibleDialogs[action.name] = true;
break;
case 'VISIBLE_DIALOGS_REMOVE':
newState = Object.assign({}, state);
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
delete newState.visibleDialogs[action.name];
break;
case 'FOCUS_SET':
newState = Object.assign({}, state);
newState.focusedField = action.field;
break;
case 'FOCUS_CLEAR':
// A field can only clear its own state
if (action.field === state.focusedField) {
newState = Object.assign({}, state);
newState.focusedField = null;
}
break;
case 'DIALOG_OPEN':
case 'DIALOG_CLOSE':
{
let isOpen = true;
if (action.type === 'DIALOG_CLOSE') {
isOpen = false;
} else { // DIALOG_OPEN
isOpen = action.isOpen !== false;
}
newState = Object.assign({}, state);
if (isOpen) {
const newDialogs = newState.dialogs.slice();
if (newDialogs.find(d => d.name === action.name)) throw new Error(`Trying to open a dialog is already open: ${action.name}`);
newDialogs.push({
name: action.name,
});
newState.dialogs = newDialogs;
} else {
if (!newState.dialogs.find(d => d.name === action.name)) throw new Error(`Trying to close a dialog that is not open: ${action.name}`);
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
newState.dialogs = newDialogs;
}
}
break;
case 'LAYOUT_MOVE_MODE_SET':
newState = {
...state,
layoutMoveMode: action.value,
};
break;
}
} catch (error) {
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
throw error;
}
return newState;
}

View File

@@ -3,7 +3,6 @@ import CommandService from '@joplin/lib/services/CommandService';
import KeymapService from '@joplin/lib/services/KeymapService';
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import resourceEditWatcherReducer, { defaultState as resourceEditWatcherDefaultState } from '@joplin/lib/services/ResourceEditWatcher/reducer';
import { defaultState, State } from '@joplin/lib/reducer';
import PluginRunner from './services/plugins/PluginRunner';
import PlatformImplementation from './services/plugins/PlatformImplementation';
import shim from '@joplin/lib/shim';
@@ -19,13 +18,10 @@ import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerS
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
import bridge from './services/bridge';
import menuCommandNames from './gui/menuCommandNames';
import { LayoutItem } from './gui/ResizableLayout/utils/types';
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
import ResourceService from '@joplin/lib/services/ResourceService';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import produce from 'immer';
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
import appReducer, { createAppDefaultState } from './app.reducer';
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
import Folder from '@joplin/lib/models/Folder';
const fs = require('fs-extra');
@@ -40,125 +36,38 @@ const PluginManager = require('@joplin/lib/services/PluginManager');
import RevisionService from '@joplin/lib/services/RevisionService';
import MigrationService from '@joplin/lib/services/MigrationService';
import { loadCustomCss, injectCustomStyles } from '@joplin/lib/CssUtils';
import mainScreenCommands from './gui/MainScreen/commands/index';
import noteEditorCommands from './gui/NoteEditor/commands/index';
import noteListCommands from './gui/NoteList/commands/index';
import noteListControlsCommands from './gui/NoteListControls/commands/index';
import sidebarCommands from './gui/Sidebar/commands/index';
import appCommands from './commands/index';
import libCommands from '@joplin/lib/commands/index';
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
const commands = [
require('./gui/MainScreen/commands/editAlarm'),
require('./gui/MainScreen/commands/exportPdf'),
require('./gui/MainScreen/commands/gotoAnything'),
require('./gui/MainScreen/commands/commandPalette'),
require('./gui/MainScreen/commands/hideModalMessage'),
require('./gui/MainScreen/commands/moveToFolder'),
require('./gui/MainScreen/commands/newFolder'),
require('./gui/MainScreen/commands/newNote'),
require('./gui/MainScreen/commands/newSubFolder'),
require('./gui/MainScreen/commands/newTodo'),
require('./gui/MainScreen/commands/openFolder'),
require('./gui/MainScreen/commands/openNote'),
require('./gui/MainScreen/commands/openTag'),
require('./gui/MainScreen/commands/print'),
require('./gui/MainScreen/commands/renameFolder'),
require('./gui/MainScreen/commands/renameTag'),
require('./gui/MainScreen/commands/search'),
require('./gui/MainScreen/commands/setTags'),
require('./gui/MainScreen/commands/showModalMessage'),
require('./gui/MainScreen/commands/showNoteContentProperties'),
require('./gui/MainScreen/commands/showNoteProperties'),
require('./gui/MainScreen/commands/showPrompt'),
require('./gui/MainScreen/commands/showShareFolderDialog'),
require('./gui/MainScreen/commands/showShareNoteDialog'),
require('./gui/MainScreen/commands/showSpellCheckerMenu'),
require('./gui/MainScreen/commands/toggleEditors'),
require('./gui/MainScreen/commands/toggleLayoutMoveMode'),
require('./gui/MainScreen/commands/toggleNoteList'),
require('./gui/MainScreen/commands/toggleSideBar'),
require('./gui/MainScreen/commands/toggleVisiblePanes'),
require('./gui/NoteEditor/commands/focusElementNoteBody'),
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
require('./gui/NoteEditor/commands/showLocalSearch'),
require('./gui/NoteEditor/commands/showRevisions'),
require('./gui/NoteList/commands/focusElementNoteList'),
require('./gui/NoteListControls/commands/focusSearch'),
require('./gui/Sidebar/commands/focusElementSideBar'),
];
const commands = mainScreenCommands
.concat(noteEditorCommands)
.concat(noteListCommands)
.concat(noteListControlsCommands)
.concat(sidebarCommands);
// Commands that are not tied to any particular component.
// The runtime for these commands can be loaded when the app starts.
const globalCommands = [
require('./commands/copyDevCommand'),
require('./commands/exportFolders'),
require('./commands/exportNotes'),
require('./commands/focusElement'),
require('./commands/openProfileDirectory'),
require('./commands/replaceMisspelling'),
require('./commands/startExternalEditing'),
require('./commands/stopExternalEditing'),
require('./commands/toggleExternalEditing'),
require('./commands/toggleSafeMode'),
require('./commands/restoreNoteRevision'),
require('@joplin/lib/commands/historyBackward'),
require('@joplin/lib/commands/historyForward'),
require('@joplin/lib/commands/synchronize'),
];
const globalCommands = appCommands.concat(libCommands);
import editorCommandDeclarations from './gui/NoteEditor/commands/editorCommandDeclarations';
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
import ShareService from '@joplin/lib/services/share/ShareService';
import checkForUpdates from './checkForUpdates';
import { AppState } from './app.reducer';
const pluginClasses = [
require('./plugins/GotoAnything').default,
];
interface AppStateRoute {
type: string;
routeName: string;
props: any;
}
export interface AppStateDialog {
name: string;
}
export interface AppState extends State {
route: AppStateRoute;
navHistory: any[];
noteVisiblePanes: string[];
windowContentSize: any;
watchedNoteFiles: string[];
lastEditorScrollPercents: any;
devToolsVisible: boolean;
visibleDialogs: any; // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
focusedField: string;
layoutMoveMode: boolean;
startupPluginsLoaded: boolean;
// Extra reducer keys go here
watchedResources: any;
mainLayout: LayoutItem;
dialogs: AppStateDialog[];
}
const appDefaultState: AppState = {
...defaultState,
route: {
type: 'NAV_GO',
routeName: 'Main',
props: {},
},
navHistory: [],
noteVisiblePanes: ['editor', 'viewer'],
windowContentSize: bridge().windowContentSize(),
watchedNoteFiles: [],
lastEditorScrollPercents: {},
devToolsVisible: false,
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
focusedField: null,
layoutMoveMode: false,
mainLayout: null,
startupPluginsLoaded: false,
dialogs: [],
...resourceEditWatcherDefaultState,
};
const appDefaultState = createAppDefaultState(
bridge().windowContentSize(),
resourceEditWatcherDefaultState
);
class Application extends BaseApplication {
@@ -175,249 +84,9 @@ class Application extends BaseApplication {
}
reducer(state: AppState = appDefaultState, action: any) {
let newState = state;
try {
switch (action.type) {
case 'NAV_BACK':
case 'NAV_GO':
{
const goingBack = action.type === 'NAV_BACK';
if (goingBack && !state.navHistory.length) break;
const currentRoute = state.route;
newState = Object.assign({}, state);
const newNavHistory = state.navHistory.slice();
if (goingBack) {
let newAction = null;
while (newNavHistory.length) {
newAction = newNavHistory.pop();
if (newAction.routeName !== state.route.routeName) break;
}
if (!newAction) break;
action = newAction;
}
if (!goingBack) newNavHistory.push(currentRoute);
newState.navHistory = newNavHistory;
newState.route = action;
}
break;
case 'STARTUP_PLUGINS_LOADED':
// When all startup plugins have loaded, we also recreate the
// main layout to ensure that it is updated in the UI. There's
// probably a cleaner way to do this, but for now that will do.
if (state.startupPluginsLoaded !== action.value) {
newState = {
...newState,
startupPluginsLoaded: action.value,
mainLayout: JSON.parse(JSON.stringify(newState.mainLayout)),
};
}
break;
case 'WINDOW_CONTENT_SIZE_SET':
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
case 'NOTE_VISIBLE_PANES_TOGGLE':
{
const getNextLayout = (currentLayout: any) => {
currentLayout = panes.length === 2 ? 'both' : currentLayout[0];
let paneOptions;
if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_VIEWER) {
paneOptions = ['editor', 'viewer'];
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_SPLIT) {
paneOptions = ['editor', 'both'];
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
paneOptions = ['viewer', 'both'];
} else {
paneOptions = ['editor', 'viewer', 'both'];
}
const currentLayoutIndex = paneOptions.indexOf(currentLayout);
const nextLayoutIndex = currentLayoutIndex === paneOptions.length - 1 ? 0 : currentLayoutIndex + 1;
const nextLayout = paneOptions[nextLayoutIndex];
return nextLayout === 'both' ? ['editor', 'viewer'] : [nextLayout];
};
newState = Object.assign({}, state);
const panes = state.noteVisiblePanes.slice();
newState.noteVisiblePanes = getNextLayout(panes);
}
break;
case 'NOTE_VISIBLE_PANES_SET':
newState = Object.assign({}, state);
newState.noteVisiblePanes = action.panes;
break;
case 'MAIN_LAYOUT_SET':
newState = {
...state,
mainLayout: action.value,
};
break;
case 'MAIN_LAYOUT_SET_ITEM_PROP':
{
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
if (item.key === action.itemKey) {
(item as any)[action.propName] = action.propValue;
return false;
}
return true;
});
});
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
newState = {
...state,
mainLayout: newLayout,
};
}
break;
case 'NOTE_FILE_WATCHER_ADD':
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
newState = Object.assign({}, state);
const watchedNoteFiles = newState.watchedNoteFiles.slice();
watchedNoteFiles.push(action.id);
newState.watchedNoteFiles = watchedNoteFiles;
}
break;
case 'NOTE_FILE_WATCHER_REMOVE':
{
newState = Object.assign({}, state);
const idx = newState.watchedNoteFiles.indexOf(action.id);
if (idx >= 0) {
const watchedNoteFiles = newState.watchedNoteFiles.slice();
watchedNoteFiles.splice(idx, 1);
newState.watchedNoteFiles = watchedNoteFiles;
}
}
break;
case 'NOTE_FILE_WATCHER_CLEAR':
if (state.watchedNoteFiles.length) {
newState = Object.assign({}, state);
newState.watchedNoteFiles = [];
}
break;
case 'EDITOR_SCROLL_PERCENT_SET':
{
newState = Object.assign({}, state);
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
newPercents[action.noteId] = action.percent;
newState.lastEditorScrollPercents = newPercents;
}
break;
case 'NOTE_DEVTOOLS_TOGGLE':
newState = Object.assign({}, state);
newState.devToolsVisible = !newState.devToolsVisible;
break;
case 'NOTE_DEVTOOLS_SET':
newState = Object.assign({}, state);
newState.devToolsVisible = action.value;
break;
case 'VISIBLE_DIALOGS_ADD':
newState = Object.assign({}, state);
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
newState.visibleDialogs[action.name] = true;
break;
case 'VISIBLE_DIALOGS_REMOVE':
newState = Object.assign({}, state);
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
delete newState.visibleDialogs[action.name];
break;
case 'FOCUS_SET':
newState = Object.assign({}, state);
newState.focusedField = action.field;
break;
case 'FOCUS_CLEAR':
// A field can only clear its own state
if (action.field === state.focusedField) {
newState = Object.assign({}, state);
newState.focusedField = null;
}
break;
case 'DIALOG_OPEN':
{
newState = Object.assign({}, state);
const newDialogs = newState.dialogs.slice();
if (newDialogs.find(d => d.name === action.name)) throw new Error(`This dialog is already opened: ${action.name}`);
newDialogs.push({
name: action.name,
});
newState.dialogs = newDialogs;
}
break;
case 'DIALOG_CLOSE':
{
newState = Object.assign({}, state);
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
newState.dialogs = newDialogs;
}
break;
case 'LAYOUT_MOVE_MODE_SET':
newState = {
...state,
layoutMoveMode: action.value,
};
break;
}
} catch (error) {
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
throw error;
}
let newState = appReducer(state, action);
newState = resourceEditWatcherReducer(newState, action);
newState = super.reducer(newState, action);
return newState;
}
@@ -873,6 +542,16 @@ class Application extends BaseApplication {
// });
// }, 2000);
// setTimeout(() => {
// this.dispatch({
// type: 'NAV_GO',
// routeName: 'Config',
// props: {
// defaultSection: 'plugins',
// },
// });
// }, 2000);
return null;
}

View File

@@ -0,0 +1,29 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as copyDevCommand from './copyDevCommand';
import * as exportFolders from './exportFolders';
import * as exportNotes from './exportNotes';
import * as focusElement from './focusElement';
import * as openProfileDirectory from './openProfileDirectory';
import * as replaceMisspelling from './replaceMisspelling';
import * as restoreNoteRevision from './restoreNoteRevision';
import * as startExternalEditing from './startExternalEditing';
import * as stopExternalEditing from './stopExternalEditing';
import * as toggleExternalEditing from './toggleExternalEditing';
import * as toggleSafeMode from './toggleSafeMode';
const index:any[] = [
copyDevCommand,
exportFolders,
exportNotes,
focusElement,
openProfileDirectory,
replaceMisspelling,
restoreNoteRevision,
startExternalEditing,
stopExternalEditing,
toggleExternalEditing,
toggleSafeMode,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -1,5 +1,5 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { AppState } from '../app';
import { AppState } from '../app.reducer';
import bridge from '../services/bridge';
export const declaration: CommandDeclaration = {

View File

@@ -7,8 +7,8 @@ import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
import ClipperServer from '@joplin/lib/ClipperServer';
import Setting from '@joplin/lib/models/Setting';
import EncryptionService from '@joplin/lib/services/EncryptionService';
import { AppState } from '../app';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { AppState } from '../app.reducer';
class ClipperConfigScreenComponent extends React.Component {
constructor() {

View File

@@ -133,6 +133,20 @@ const StyledDescription = styled.div`
line-height: 1.6em;
`;
const RecommendedBadge = styled.a`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.colorWarn};
font-size: ${props => props.theme.fontSize}px;
border: 1px solid ${props => props.theme.colorWarn};
padding: 5px;
border-radius: 50px;
opacity: 0.8;
&:hover {
opacity: 1;
}
`;
export default function(props: Props) {
const item = useMemo(() => {
return props.item ? props.item : manifestToItem(props.manifest);
@@ -144,6 +158,10 @@ export default function(props: Props) {
bridge().openExternal(manifest.homepage_url);
}, [item]);
const onRecommendedClick = useCallback(() => {
bridge().openExternal('https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins');
}, []);
// For plugins in dev mode things like enabling/disabling or
// uninstalling them doesn't make sense, as that should be done by
// adding/removing them from wherever they were loaded from.
@@ -222,11 +240,18 @@ export default function(props: Props) {
);
}
function renderRecommendedBadge() {
if (props.onToggle) return null;
if (!item.manifest._recommended) return null;
return <RecommendedBadge href="#" title={_('The Joplin team has vetted this plugin and it meets our standards for security and performance.')} onClick={onRecommendedClick}><i className="fas fa-crown"></i></RecommendedBadge>;
}
return (
<CellRoot isCompatible={props.isCompatible}>
<CellTop>
<StyledNameAndVersion mb={'5px'}><StyledName onClick={onNameClick} href="#" style={{ marginRight: 5 }}>{item.manifest.name} {item.deleted ? _('(%s)', 'Deleted') : ''}</StyledName><StyledVersion>v{item.manifest.version}</StyledVersion></StyledNameAndVersion>
{renderToggleButton()}
{renderRecommendedBadge()}
</CellTop>
<CellContent>
<StyledDescription>{item.manifest.description}</StyledDescription>

View File

@@ -299,7 +299,7 @@ export default function(props: Props) {
function renderRepoApiError() {
if (!repoApiError) return null;
return <RepoApiErrorMessage maxWidth={maxWidth} type="error">{_('Could not connect to plugin repository')} - <StyledLink href="#" onClick={() => { setFetchManifestTime(Date.now()); }}>{_('Try again')}</StyledLink></RepoApiErrorMessage>;
return <RepoApiErrorMessage maxWidth={maxWidth} type="error">{_('Could not connect to plugin repository.')}<br/><br/>- <StyledLink href="#" onClick={() => { setFetchManifestTime(Date.now()); }}>{_('Try again')}</StyledLink><br/><br/>- <StyledLink href="#" onClick={onBrowsePlugins}>{_('Browse all plugins')}</StyledLink></RepoApiErrorMessage>;
}
function renderBottomArea() {

View File

@@ -30,6 +30,14 @@ interface Props {
disabled: boolean;
}
function sortManifestResults(results: PluginManifest[]): PluginManifest[] {
return results.sort((m1, m2) => {
if (m1._recommended && !m2._recommended) return -1;
if (!m1._recommended && m2._recommended) return +1;
return m1.name.toLowerCase() < m2.name.toLowerCase() ? -1 : +1;
});
}
export default function(props: Props) {
const [searchStarted, setSearchStarted] = useState(false);
const [manifests, setManifests] = useState<PluginManifest[]>([]);
@@ -47,7 +55,7 @@ export default function(props: Props) {
setSearchResultCount(null);
} else {
const r = await props.repoApi().search(props.searchQuery);
setManifests(r);
setManifests(sortManifestResults(r));
setSearchResultCount(r.length);
}
});

View File

@@ -1,7 +1,7 @@
const React = require('react');
const { connect } = require('react-redux');
import Setting from '@joplin/lib/models/Setting';
import EncryptionService from '@joplin/lib/services/EncryptionService';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
import time from '@joplin/lib/time';
@@ -12,8 +12,16 @@ import bridge from '../services/bridge';
import shared from '@joplin/lib/components/shared/encryption-config-shared';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import { getEncryptionEnabled, masterKeyEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import { getDefaultMasterKey, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import MasterKey from '@joplin/lib/models/MasterKey';
import StyledInput from './style/StyledInput';
import Button, { ButtonLevel } from './Button/Button';
import styled from 'styled-components';
const MasterPasswordInput = styled(StyledInput)`
min-width: 300px;
align-items: center;
`;
interface Props {}
@@ -45,6 +53,10 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
private renderMasterKey(mk: MasterKeyEntity, isDefault: boolean) {
const theme = themeStyle(this.props.themeId);
const onToggleEnabledClick = () => {
return shared.onToggleEnabledClick(this, mk);
};
const passwordStyle = {
color: theme.color,
backgroundColor: theme.backgroundColor,
@@ -60,8 +72,23 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
return shared.onPasswordChange(this, mk, event.target.value);
};
const onToggleEnabledClick = () => {
return shared.onToggleEnabledClick(this, mk);
const renderPasswordInput = (masterKeyId: string) => {
if (this.state.masterPasswordKeys[masterKeyId] || !this.state.passwordChecks['master']) {
return (
<td style={{ ...theme.textStyle, color: theme.colorFaded, fontStyle: 'italic' }}>
({_('Master password')})
</td>
);
} else {
return (
<td style={theme.textStyle}>
<input type="password" style={passwordStyle} value={password} onChange={event => onPasswordChange(event)} />{' '}
<button style={theme.buttonStyle} onClick={() => onSaveClick()}>
{_('Save')}
</button>
</td>
);
}
};
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
@@ -74,12 +101,7 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
<td style={theme.textStyle}>{activeIcon}</td>
<td style={theme.textStyle}>{mk.id}<br/>{_('Source: ')}{mk.source_application}</td>
<td style={theme.textStyle}>{_('Created: ')}{time.formatMsToLocal(mk.created_time)}<br/>{_('Updated: ')}{time.formatMsToLocal(mk.updated_time)}</td>
<td style={theme.textStyle}>
<input type="password" style={passwordStyle} value={password} onChange={event => onPasswordChange(event)} />{' '}
<button style={theme.buttonStyle} onClick={() => onSaveClick()}>
{_('Save')}
</button>
</td>
{renderPasswordInput(mk.id)}
<td style={theme.textStyle}>{passwordOk}</td>
<td style={theme.textStyle}>
<button disabled={isActive || isDefault} style={theme.buttonStyle} onClick={() => onToggleEnabledClick()}>{masterKeyEnabled(mk) ? _('Disable') : _('Enable')}</button>
@@ -165,7 +187,7 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
}
const headerComp = isEnabledMasterKeys ? <h1 style={theme.h1Style}>{_('Master Keys')}</h1> : <a onClick={() => shared.toggleShowDisabledMasterKeys(this) } style={{ ...theme.urlStyle, display: 'inline-block', marginBottom: 10 }} href="#">{showTable ? _('Hide disabled master keys') : _('Show disabled master keys')}</a>;
const infoComp = isEnabledMasterKeys ? <p style={theme.textStyle}>{_('Note: Only one master key is going to be used for encryption (the one marked as "active"). Any of the keys might be used for decryption, depending on how the notes or notebooks were originally encrypted.')}</p> : null;
const infoComp = isEnabledMasterKeys ? <p style={theme.textStyle}>{'Note: Only one master key is going to be used for encryption (the one marked as "active"). Any of the keys might be used for decryption, depending on how the notes or notebooks were originally encrypted.'}</p> : null;
const tableComp = !showTable ? null : (
<table>
<tbody>
@@ -195,6 +217,39 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
return null;
}
private renderMasterPassword() {
if (!this.props.encryptionEnabled && !this.props.masterKeys.length) return null;
const theme = themeStyle(this.props.themeId);
const onMasterPasswordSave = async () => {
shared.onMasterPasswordSave(this);
if (!(await shared.masterPasswordIsValid(this, this.state.masterPasswordInput))) {
alert('Password is invalid. Please try again.');
}
};
if (this.state.passwordChecks['master']) {
return (
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<span style={theme.textStyle}>{_('Master password:')}</span>&nbsp;&nbsp;
<span style={{ ...theme.textStyle, fontWeight: 'bold' }}> {_('Loaded')}</span>
</div>
);
} else {
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span style={theme.textStyle}> {'The master password is not set or is invalid. Please type it below:'}</span>
<div style={{ display: 'flex', flexDirection: 'row', marginTop: 10 }}>
<MasterPasswordInput placeholder={_('Enter your master password')} type="password" value={this.state.masterPasswordInput} onChange={(event: any) => shared.onMasterPasswordChange(this, event.target.value)} />{' '}
<Button ml="10px" level={ButtonLevel.Secondary} onClick={onMasterPasswordSave} title={_('Save')} />
</div>
</div>
);
}
}
render() {
const theme = themeStyle(this.props.themeId);
const masterKeys: MasterKeyEntity[] = this.props.masterKeys;
@@ -215,7 +270,7 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
const onToggleButtonClick = async () => {
const isEnabled = getEncryptionEnabled();
const masterKey = MasterKey.latest();
const masterKey = getDefaultMasterKey();
let answer = null;
if (isEnabled) {
@@ -304,6 +359,7 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
<p style={theme.textStyle}>
{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong>
</p>
{this.renderMasterPassword()}
{decryptedItemsInfo}
{toggleButton}
{needUpgradeSection}
@@ -329,6 +385,7 @@ const mapStateToProps = (state: State) => {
activeMasterKeyId: syncInfo.activeMasterKeyId,
shouldReencrypt: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
notLoadedMasterKeys: state.notLoadedMasterKeys,
masterPassword: state.settings['encryption.masterPassword'],
};
};

View File

@@ -17,7 +17,7 @@ import { stateUtils } from '@joplin/lib/reducer';
import InteropServiceHelper from '../../InteropServiceHelper';
import { _ } from '@joplin/lib/locale';
import NoteListWrapper from '../NoteListWrapper/NoteListWrapper';
import { AppState } from '../../app';
import { AppState } from '../../app.reducer';
import { saveLayout, loadLayout } from '../ResizableLayout/utils/persist';
import Setting from '@joplin/lib/models/Setting';
import produce from 'immer';
@@ -29,7 +29,7 @@ import { themeStyle } from '@joplin/lib/theme';
import validateLayout from '../ResizableLayout/utils/validateLayout';
import iterateItems from '../ResizableLayout/utils/iterateItems';
import removeItem from '../ResizableLayout/utils/removeItem';
import EncryptionService from '@joplin/lib/services/EncryptionService';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import ShareFolderDialog from '../ShareFolderDialog/ShareFolderDialog';
import { ShareInvitation } from '@joplin/lib/services/share/reducer';
import ShareService from '@joplin/lib/services/share/ShareService';
@@ -37,6 +37,7 @@ import { reg } from '@joplin/lib/registry';
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import commands from './commands/index';
const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js');
@@ -110,39 +111,6 @@ const defaultLayout: LayoutItem = {
],
};
const commands = [
require('./commands/editAlarm'),
require('./commands/exportPdf'),
require('./commands/gotoAnything'),
require('./commands/commandPalette'),
require('./commands/hideModalMessage'),
require('./commands/moveToFolder'),
require('./commands/newFolder'),
require('./commands/newNote'),
require('./commands/newSubFolder'),
require('./commands/newTodo'),
require('./commands/openFolder'),
require('./commands/openNote'),
require('./commands/openTag'),
require('./commands/print'),
require('./commands/renameFolder'),
require('./commands/renameTag'),
require('./commands/search'),
require('./commands/setTags'),
require('./commands/showModalMessage'),
require('./commands/showNoteContentProperties'),
require('./commands/showNoteProperties'),
require('./commands/showPrompt'),
require('./commands/showShareFolderDialog'),
require('./commands/showShareNoteDialog'),
require('./commands/showSpellCheckerMenu'),
require('./commands/toggleEditors'),
require('./commands/toggleLayoutMoveMode'),
require('./commands/toggleNoteList'),
require('./commands/toggleSideBar'),
require('./commands/toggleVisiblePanes'),
];
class MainScreenComponent extends React.Component<Props, State> {
private waitForNotesSavedIID_: any;

View File

@@ -0,0 +1,67 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as commandPalette from './commandPalette';
import * as editAlarm from './editAlarm';
import * as exportPdf from './exportPdf';
import * as gotoAnything from './gotoAnything';
import * as hideModalMessage from './hideModalMessage';
import * as moveToFolder from './moveToFolder';
import * as newFolder from './newFolder';
import * as newNote from './newNote';
import * as newSubFolder from './newSubFolder';
import * as newTodo from './newTodo';
import * as openFolder from './openFolder';
import * as openNote from './openNote';
import * as openTag from './openTag';
import * as print from './print';
import * as renameFolder from './renameFolder';
import * as renameTag from './renameTag';
import * as search from './search';
import * as setTags from './setTags';
import * as showModalMessage from './showModalMessage';
import * as showNoteContentProperties from './showNoteContentProperties';
import * as showNoteProperties from './showNoteProperties';
import * as showPrompt from './showPrompt';
import * as showShareFolderDialog from './showShareFolderDialog';
import * as showShareNoteDialog from './showShareNoteDialog';
import * as showSpellCheckerMenu from './showSpellCheckerMenu';
import * as toggleEditors from './toggleEditors';
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
import * as toggleNoteList from './toggleNoteList';
import * as toggleSideBar from './toggleSideBar';
import * as toggleVisiblePanes from './toggleVisiblePanes';
const index:any[] = [
commandPalette,
editAlarm,
exportPdf,
gotoAnything,
hideModalMessage,
moveToFolder,
newFolder,
newNote,
newSubFolder,
newTodo,
openFolder,
openNote,
openTag,
print,
renameFolder,
renameTag,
search,
setTags,
showModalMessage,
showNoteContentProperties,
showNoteProperties,
showPrompt,
showShareFolderDialog,
showShareNoteDialog,
showSpellCheckerMenu,
toggleEditors,
toggleLayoutMoveMode,
toggleNoteList,
toggleSideBar,
toggleVisiblePanes,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -34,7 +34,7 @@ export const runtime = (comp: any): CommandRuntime => {
value: '',
autocomplete: startFolders,
onClose: async (answer: any) => {
if (answer != null) {
if (answer) {
for (let i = 0; i < noteIds.length; i++) {
await Note.moveToFolder(noteIds[i], answer.value);
}

View File

@@ -2,7 +2,7 @@ import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/
import { _ } from '@joplin/lib/locale';
import bridge from '../../../services/bridge';
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
import { AppState } from '../../../app';
import { AppState } from '../../../app.reducer';
const Menu = bridge().Menu;

View File

@@ -1,6 +1,6 @@
import { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { AppState } from '../../../app';
import { AppState } from '../../../app.reducer';
export const declaration: CommandDeclaration = {
name: 'toggleLayoutMoveMode',

View File

@@ -2,7 +2,7 @@ import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/
import { _ } from '@joplin/lib/locale';
import setLayoutItemProps from '../../ResizableLayout/utils/setLayoutItemProps';
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
import { AppState } from '../../../app';
import { AppState } from '../../../app.reducer';
export const declaration: CommandDeclaration = {
name: 'toggleNoteList',

View File

@@ -2,7 +2,7 @@ import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/
import { _ } from '@joplin/lib/locale';
import setLayoutItemProps from '../../ResizableLayout/utils/setLayoutItemProps';
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
import { AppState } from '../../../app';
import { AppState } from '../../../app.reducer';
export const declaration: CommandDeclaration = {
name: 'toggleSideBar',

View File

@@ -1,5 +1,5 @@
import { useEffect, useState, useRef, useCallback } from 'react';
import { AppState } from '../app';
import { AppState } from '../app.reducer';
import InteropService from '@joplin/lib/services/interop/InteropService';
import { stateUtils } from '@joplin/lib/reducer';
import CommandService from '@joplin/lib/services/CommandService';

View File

@@ -381,6 +381,9 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
`.CodeMirror-selected {
background: #6b6b6b !important;
}` : '';
// Vim mode draws a fat cursor in the background, we don't want to add background colors
// to the inline code in this case (it would hide the cursor)
const codeBackgroundColor = Setting.value('editor.keyboardMode') !== 'vim' ? theme.codeBackgroundColor : 'inherit';
const monospaceFonts = [];
if (Setting.value('style.editor.monospaceFontFamily')) monospaceFonts.push(`"${Setting.value('style.editor.monospaceFontFamily')}"`);
monospaceFonts.push('monospace');
@@ -476,9 +479,9 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
/* Negative margins are needed to componsate for the border */
div.CodeMirror span.cm-comment.cm-jn-inline-code {
div.CodeMirror span.cm-comment.cm-jn-inline-code:not(.cm-search-marker):not(.cm-fat-cursor-mark):not(.cm-search-marker-selected):not(.CodeMirror-selectedtext) {
border: 1px solid ${theme.codeBorderColor};
background-color: ${theme.codeBackgroundColor};
background-color: ${codeBackgroundColor};
margin-left: -1px;
margin-right: -1px;
border-radius: .25em;

View File

@@ -3,7 +3,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import ToolbarBase from '../../../ToolbarBase';
import { utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import { connect } from 'react-redux';
import { AppState } from '../../../../app';
import { AppState } from '../../../../app.reducer';
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import stateToWhenClauseContext from '../../../../services/commands/stateToWhenClauseContext';
const { buildStyle } = require('@joplin/lib/theme');

View File

@@ -20,7 +20,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import ToolbarButton from '../ToolbarButton/ToolbarButton';
import Button, { ButtonLevel } from '../Button/Button';
import eventManager from '@joplin/lib/eventManager';
import { AppState } from '../../app';
import { AppState } from '../../app.reducer';
import ToolbarButtonUtils from '@joplin/lib/services/commands/ToolbarButtonUtils';
import { _ } from '@joplin/lib/locale';
import TagList from '../TagList';

View File

@@ -0,0 +1,15 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as focusElementNoteBody from './focusElementNoteBody';
import * as focusElementNoteTitle from './focusElementNoteTitle';
import * as showLocalSearch from './showLocalSearch';
import * as showRevisions from './showRevisions';
const index:any[] = [
focusElementNoteBody,
focusElementNoteTitle,
showLocalSearch,
showRevisions,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -1,6 +1,11 @@
import { useState, useCallback } from 'react';
import Logger from '@joplin/lib/Logger';
import { SearchMarkers } from './useSearchMarkers';
const logger = Logger.create('useNoteSearchBar');
const queryMaxLength = 1000;
interface LocalSearch {
query: string;
selectedIndex: number;
@@ -24,6 +29,14 @@ export default function useNoteSearchBar() {
const [localSearch, setLocalSearch] = useState<LocalSearch>(defaultLocalSearch());
const onChange = useCallback((query: string) => {
// A query that's too long would make CodeMirror throw an exception
// which would crash the app.
// https://github.com/laurent22/joplin/issues/5380
if (query.length > queryMaxLength) {
logger.warn(`Query is longer than ${queryMaxLength} characters - it is going to be trimmed`);
query = query.substr(0, queryMaxLength);
}
setLocalSearch((prev: LocalSearch) => {
return {
query: query,

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { FormNote, ScrollOptionTypes } from './types';
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
import editorCommandDeclarations from '../editorCommandDeclarations';
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
import time from '@joplin/lib/time';
import { reg } from '@joplin/lib/registry';

View File

@@ -1,4 +1,4 @@
import { AppState } from '../../app';
import { AppState } from '../../app.reducer';
import eventManager from '@joplin/lib/eventManager';
import NoteListUtils from '../utils/NoteListUtils';
import { _ } from '@joplin/lib/locale';

View File

@@ -0,0 +1,9 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as focusElementNoteList from './focusElementNoteList';
const index:any[] = [
focusElementNoteList,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -0,0 +1,9 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as focusSearch from './focusSearch';
const index:any[] = [
focusSearch,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -1,4 +1,5 @@
import app, { AppState, AppStateDialog } from '../app';
import app from '../app';
import { AppState, AppStateDialog } from '../app.reducer';
import MainScreen from './MainScreen/MainScreen';
import ConfigScreen from './ConfigScreen/ConfigScreen';
import StatusScreen from './StatusScreen/StatusScreen';
@@ -19,6 +20,7 @@ import DialogTitle from './DialogTitle';
import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './DialogButtonRow';
import Dialog from './Dialog';
import SyncWizardDialog from './SyncWizard/Dialog';
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
const { ImportScreen } = require('./ImportScreen.min.js');
const { ResourceScreen } = require('./ResourceScreen.js');
const { Navigator } = require('./Navigator.min.js');
@@ -207,6 +209,7 @@ class RootComponent extends React.Component<Props, any> {
return (
<StyleSheetManager disableVendorPrefixes>
<ThemeProvider theme={theme}>
<StyleSheetContainer themeId={this.props.themeId}></StyleSheetContainer>
<MenuBar/>
<GlobalStyle/>
<Navigator style={navigatorStyle} screens={screens} />

View File

@@ -16,6 +16,7 @@ import { ShareUserStatus, StateShare, StateShareUser } from '@joplin/lib/service
import { State } from '@joplin/lib/reducer';
import { connect } from 'react-redux';
import { reg } from '@joplin/lib/registry';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
const logger = Logger.create('ShareFolderDialog');
@@ -100,20 +101,6 @@ interface RecipientDeleteEvent {
shareUserId: string;
}
interface AsyncEffectEvent {
cancelled: boolean;
}
function useAsyncEffect(effect: Function, dependencies: any[]) {
useEffect(() => {
const event = { cancelled: false };
effect(event);
return () => {
event.cancelled = true;
};
}, dependencies);
}
enum ShareState {
Idle = 0,
Synchronizing = 1,

View File

@@ -13,7 +13,7 @@ import { StateShare } from '@joplin/lib/services/share/reducer';
import { NoteEntity } from '@joplin/lib/services/database/types';
import Button from './Button/Button';
import { connect } from 'react-redux';
import { AppState } from '../app';
import { AppState } from '../app.reducer';
import { getEncryptionEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils';
const { clipboard } = require('electron');

View File

@@ -10,7 +10,7 @@ import InteropServiceHelper from '../../InteropServiceHelper';
import { _ } from '@joplin/lib/locale';
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
import { AppState } from '../../app';
import { AppState } from '../../app.reducer';
import { ModelType } from '@joplin/lib/BaseModel';
import BaseModel from '@joplin/lib/BaseModel';
import Folder from '@joplin/lib/models/Folder';

View File

@@ -1,7 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import layoutItemProp from '../../ResizableLayout/utils/layoutItemProp';
import { AppState } from '../../../app';
import { AppState } from '../../../app.reducer';
export const declaration: CommandDeclaration = {
name: 'focusElementSideBar',

View File

@@ -0,0 +1,9 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as focusElementSideBar from './focusElementSideBar';
const index:any[] = [
focusElementSideBar,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -0,0 +1,41 @@
// This component is perhaps a bit of a hack but the approach should be
// reliable. It converts the current (JS) theme to CSS, and add it to the HEAD
// tag. The component itself doesn't render anything where it's located (just an
// empty invisible DIV), so it means it could be put anywhere and would have the
// same effect.
//
// It's still reliable because the lifecyle of adding the CSS and removing on
// unmout is handled properly. There should only be one such component on the
// page.
import { useEffect, useState } from 'react';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import themeToCss from '@joplin/lib/services/style/themeToCss';
import { themeById } from '@joplin/lib/theme';
interface Props {
themeId: any;
}
export default function(props: Props): any {
const [styleSheetContent, setStyleSheetContent] = useState('');
useAsyncEffect(async (event: AsyncEffectEvent) => {
const theme = themeById(props.themeId);
const themeCss = themeToCss(theme);
if (event.cancelled) return;
setStyleSheetContent(themeCss);
}, [props.themeId]);
useEffect(() => {
const element = document.createElement('style');
element.setAttribute('id', 'main-theme-stylesheet-container');
document.head.appendChild(element);
element.appendChild(document.createTextNode(styleSheetContent));
return () => {
document.head.removeChild(element);
};
}, [styleSheetContent]);
return <div style={{ display: 'none' }}></div>;
}

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { useMemo } from 'react';
import { AppState } from '../app';
import { AppState } from '../app.reducer';
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');

View File

@@ -19,6 +19,7 @@ const tasks = {
},
tsc: require('@joplin/tools/gulp/tasks/tsc'),
updateIgnoredTypeScriptBuild: require('@joplin/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
buildCommandIndex: require('@joplin/tools/gulp/tasks/buildCommandIndex'),
};
utils.registerGulpTasks(gulp, tasks);
@@ -28,7 +29,8 @@ const buildParallel = [
'compilePackageInfo',
'copyPluginAssets',
'copyTinyMceLangs',
// 'updateIgnoredTypeScriptBuild',
'updateIgnoredTypeScriptBuild',
'buildCommandIndex',
];
gulp.task('build', gulp.parallel(...buildParallel));

View File

@@ -24,7 +24,7 @@ const Logger = require('@joplin/lib/Logger').default;
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
const shim = require('@joplin/lib/shim').default;
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const EncryptionService = require('@joplin/lib/services/EncryptionService').default;
const EncryptionService = require('@joplin/lib/services/e2ee/EncryptionService').default;
const bridge = require('electron').remote.require('./bridge').default;
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local.js');
const React = require('react');

View File

@@ -1,12 +1,12 @@
{
"name": "@joplin/app-desktop",
"version": "2.4.1",
"version": "2.4.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@joplin/app-desktop",
"version": "2.4.1",
"version": "2.4.4",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.13.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.4.1",
"version": "2.4.4",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { AppState } from '../app';
import { AppState } from '../app.reducer';
import CommandService, { SearchResult as CommandSearchResult } from '@joplin/lib/services/CommandService';
import KeymapService from '@joplin/lib/services/KeymapService';
import shim from '@joplin/lib/shim';
@@ -337,6 +337,10 @@ class Dialog extends React.PureComponent<Props, State> {
// @ts-ignore
const notesById = notes.reduce((obj, { id, body, markup_language }) => ((obj[[id]] = { id, body, markup_language }), obj), {});
// Filter out search results that are associated with non-existing notes.
// https://github.com/laurent22/joplin/issues/5417
results = results.filter(r => !!notesById[r.id]);
for (let i = 0; i < results.length; i++) {
const row = results[i];
const path = Folder.folderPathString(this.props.folders, row.parent_id);

View File

@@ -3,7 +3,7 @@
// general, any desktop component should import this file, and not the lib
// one.
import { AppState } from '../../app';
import { AppState } from '../../app.reducer';
import libStateToWhenClauseContext, { WhenClauseContextOptions } from '@joplin/lib/services/commands/stateToWhenClauseContext';
import layoutItemProp from '../../gui/ResizableLayout/utils/layoutItemProp';

View File

@@ -1,4 +1,4 @@
import { AppState } from '../../app';
import { AppState } from '../../app.reducer';
export interface DesktopCommandContext {
state: AppState;

View File

@@ -64,28 +64,6 @@ a {
opacity: 1;
}
/*
.note-list .list-item-container:hover {
background-color: rgba(0,160,255,0.1) !important;
}
*/
/*
.editor-toolbar .button:not(.disabled):hover,
.header .button:not(.disabled):hover {
background-color: rgba(0,160,255,0.1);
border: 1px solid rgba(0,160,255,0.5);
box-sizing: 'border-box';
}
.editor-toolbar .button:not(.disabled):active,
.header .button:not(.disabled):active {
background-color: rgba(0,160,255,0.2);
border: 1px solid rgba(0,160,255,0.7);
box-sizing: 'border-box';
}
*/
.editor-toolbar .button,
.header .button {
border: 1px solid rgba(0,160,255,0);
@@ -163,11 +141,6 @@ a {
to {transform: rotate(360deg);}
}
/* .joplin-tinymce .tox-editor-header {
padding-left: 88px;
padding-right: 150px;
} */
*:focus {
outline: none;
}

View File

@@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097648
versionName "2.4.0"
versionCode 2097649
versionName "2.4.1"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -6,7 +6,7 @@ const { BaseScreenComponent } = require('../base-screen.js');
const { themeStyle } = require('../global-style.js');
const DialogBox = require('react-native-dialogbox').default;
const { dialogs } = require('../../utils/dialogs.js');
import EncryptionService from '@joplin/lib/services/EncryptionService';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { _ } from '@joplin/lib/locale';
import time from '@joplin/lib/time';
import shared from '@joplin/lib/components/shared/encryption-config-shared';
@@ -116,15 +116,29 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent<Props> {
inputStyle.borderBottomWidth = 1;
inputStyle.borderBottomColor = theme.dividerColor;
const renderPasswordInput = (masterKeyId: string) => {
if (this.state.masterPasswordKeys[masterKeyId] || !this.state.passwordChecks['master']) {
return (
<Text style={{ ...this.styles().normalText, color: theme.colorFaded, fontStyle: 'italic' }}>({_('Master password')})</Text>
);
} else {
return (
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={password} onChangeText={(text: string) => onPasswordChange(text)} style={inputStyle}></TextInput>
<Text style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{passwordOk}</Text>
<Button title={_('Save')} onPress={() => onSaveClick()}></Button>
</View>
);
}
};
return (
<View key={mk.id}>
<Text style={this.styles().titleText}>{_('Master Key %s', mk.id.substr(0, 6))}</Text>
<Text style={this.styles().normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ flex: 0, fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{_('Password:')}</Text>
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={password} onChangeText={(text: string) => onPasswordChange(text)} style={inputStyle}></TextInput>
<Text style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{passwordOk}</Text>
<Button title={_('Save')} onPress={() => onSaveClick()}></Button>
{renderPasswordInput(mk.id)}
</View>
</View>
);
@@ -203,6 +217,43 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent<Props> {
);
}
private renderMasterPassword() {
if (!this.props.encryptionEnabled && !this.props.masterKeys.length) return null;
const theme = themeStyle(this.props.themeId);
const onMasterPasswordSave = async () => {
shared.onMasterPasswordSave(this);
if (!(await shared.masterPasswordIsValid(this, this.state.masterPasswordInput))) {
alert('Password is invalid. Please try again.');
}
};
const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color };
inputStyle.borderBottomWidth = 1;
inputStyle.borderBottomColor = theme.dividerColor;
if (this.state.passwordChecks['master']) {
return (
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ ...this.styles().normalText, flex: 0, marginRight: 5 }}>{_('Master password:')}</Text>
<Text style={{ ...this.styles().normalText, fontWeight: 'bold' }}>{_('Loaded')}</Text>
</View>
);
} else {
return (
<View style={{ display: 'flex', flexDirection: 'column', marginTop: 10 }}>
<Text style={this.styles().normalText}>{'The master password is not set or is invalid. Please type it below:'}</Text>
<View style={{ display: 'flex', flexDirection: 'row', marginTop: 10 }}>
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={this.state.masterPasswordInput} onChangeText={(text: string) => shared.onMasterPasswordChange(this, text)} style={inputStyle}></TextInput>
<Button onPress={onMasterPasswordSave} title={_('Save')} />
</View>
</View>
);
}
}
render() {
const theme = themeStyle(this.props.themeId);
const masterKeys = this.props.masterKeys;
@@ -289,6 +340,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent<Props> {
<Text style={this.styles().titleText}>{_('Status')}</Text>
<Text style={this.styles().normalText}>{_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
{decryptedItemsInfo}
{this.renderMasterPassword()}
{toggleButton}
{passwordPromptComp}
{mkComps}
@@ -315,6 +367,7 @@ const EncryptionConfigScreen = connect((state: State) => {
encryptionEnabled: syncInfo.e2ee,
activeMasterKeyId: syncInfo.activeMasterKeyId,
notLoadedMasterKeys: state.notLoadedMasterKeys,
masterPassword: state.settings['encryption.masterPassword'],
};
})(EncryptionConfigScreenComponent);

View File

@@ -46,6 +46,11 @@
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>api.joplincloud.local</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>

View File

@@ -14,7 +14,7 @@ import ResourceService from '@joplin/lib/services/ResourceService';
import KvStore from '@joplin/lib/services/KvStore';
import NoteScreen from './components/screens/Note';
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen';
import Setting from '@joplin/lib/models/Setting';
import Setting, { Env } from '@joplin/lib/models/Setting';
import RNFetchBlob from 'rn-fetch-blob';
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
import reducer from '@joplin/lib/reducer';
@@ -97,13 +97,13 @@ SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
import FsDriverRN from './utils/fs-driver-rn';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
import EncryptionService from '@joplin/lib/services/EncryptionService';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import MigrationService from '@joplin/lib/services/MigrationService';
import { clearSharedFilesCache } from './utils/ShareUtils';
import setIgnoreTlsErrors from './utils/TlsUtils';
import ShareService from '@joplin/lib/services/share/ShareService';
import setupNotifications from './utils/setupNotifications';
import { loadMasterKeysFromSettings } from '@joplin/lib/services/e2ee/utils';
import { loadMasterKeysFromSettings, migrateMasterPassword } from '@joplin/lib/services/e2ee/utils';
import SyncTargetNone from '../lib/SyncTargetNone';
let storeDispatch = function(_action: any) {};
@@ -474,15 +474,24 @@ async function initialize(dispatch: Function) {
if (Setting.value('env') == 'prod') {
await db.open({ name: 'joplin.sqlite' });
} else {
await db.open({ name: 'joplin-101.sqlite' });
await db.open({ name: 'joplin-104.sqlite' });
// await db.clearForTesting();
}
reg.logger().info('Database is ready.');
reg.logger().info('Loading settings...');
await loadKeychainServiceAndSettings(KeychainServiceDriverMobile);
await migrateMasterPassword();
if (Setting.value('env') === Env.Dev) {
// Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
// Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');
Setting.setValue('sync.10.path', 'http://api.joplincloud.local:22300');
Setting.setValue('sync.10.userContentPath', 'http://joplinusercontent.local:22300');
}
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
@@ -530,7 +539,6 @@ async function initialize(dispatch: Function) {
// ----------------------------------------------------------------
EncryptionService.fsDriver_ = fsDriver;
EncryptionService.instance().setLogger(mainLogger);
// eslint-disable-next-line require-atomic-updates
BaseItem.encryptionService_ = EncryptionService.instance();
BaseItem.shareService_ = ShareService.instance();
@@ -634,7 +642,7 @@ async function initialize(dispatch: Function) {
class AppComponent extends React.Component {
constructor() {
public constructor() {
super();
this.state = {
@@ -677,7 +685,7 @@ class AppComponent extends React.Component {
// https://github.com/laurent22/joplin/issues/3807
// https://discourse.joplinapp.org/t/webdav-config-encryption-config-randomly-lost-on-android/11364
// https://discourse.joplinapp.org/t/android-keeps-on-resetting-my-sync-and-theme/11443
async componentDidMount() {
public async componentDidMount() {
if (this.props.appState == 'starting') {
this.props.dispatch({
type: 'APP_STATE_SET',
@@ -725,15 +733,18 @@ class AppComponent extends React.Component {
setupQuickActions(this.props.dispatch, this.props.selectedFolderId);
await setupNotifications(this.props.dispatch);
// Setting.setValue('encryption.masterPassword', 'WRONG');
// setTimeout(() => NavService.go('EncryptionConfig'), 2000);
}
componentWillUnmount() {
public componentWillUnmount() {
AppState.removeEventListener('change', this.onAppStateChange_);
Linking.removeEventListener('url', this.handleOpenURL_);
if (this.unsubscribeNetInfoHandler_) this.unsubscribeNetInfoHandler_();
}
componentDidUpdate(prevProps: any) {
public componentDidUpdate(prevProps: any) {
if (this.props.showSideMenu !== prevProps.showSideMenu) {
Animated.timing(this.state.sideMenuContentOpacity, {
toValue: this.props.showSideMenu ? 0.5 : 0,
@@ -742,7 +753,7 @@ class AppComponent extends React.Component {
}
}
async backButtonHandler() {
private async backButtonHandler() {
if (this.props.noteSelectionEnabled) {
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
return true;
@@ -763,7 +774,7 @@ class AppComponent extends React.Component {
return false;
}
async handleShareData() {
private async handleShareData() {
const sharedData = await ShareExtension.data();
if (sharedData) {
reg.logger().info('Received shared data');
@@ -775,14 +786,14 @@ class AppComponent extends React.Component {
}
}
UNSAFE_componentWillReceiveProps(newProps: any) {
public UNSAFE_componentWillReceiveProps(newProps: any) {
if (newProps.syncStarted != this.lastSyncStarted_) {
if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders();
this.lastSyncStarted_ = newProps.syncStarted;
}
}
sideMenu_change(isOpen: boolean) {
private sideMenu_change(isOpen: boolean) {
// Make sure showSideMenu property of state is updated
// when the menu is open/closed.
this.props.dispatch({
@@ -790,7 +801,7 @@ class AppComponent extends React.Component {
});
}
render() {
public render() {
if (this.props.appState != 'ready') return null;
const theme = themeStyle(this.props.themeId);

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-htmlparser2",
"version": "4.1.33",
"version": "4.1.34",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-sax",
"version": "1.2.37",
"version": "1.2.38",
"lockfileVersion": 2,
"requires": true,
"packages": {

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.37",
"version": "1.2.38",
"main": "lib/sax.js",
"publishConfig": {
"access": "public"

View File

@@ -36,14 +36,14 @@ const SyncTargetNextcloud = require('./SyncTargetNextcloud.js');
const SyncTargetWebDAV = require('./SyncTargetWebDAV.js');
const SyncTargetDropbox = require('./SyncTargetDropbox.js');
const SyncTargetAmazonS3 = require('./SyncTargetAmazonS3.js');
import EncryptionService from './services/EncryptionService';
import EncryptionService from './services/e2ee/EncryptionService';
import ResourceFetcher from './services/ResourceFetcher';
import SearchEngineUtils from './services/searchengine/SearchEngineUtils';
import SearchEngine from './services/searchengine/SearchEngine';
import RevisionService from './services/RevisionService';
import ResourceService from './services/ResourceService';
import DecryptionWorker from './services/DecryptionWorker';
const { loadKeychainServiceAndSettings } = require('./services/SettingUtils');
import { loadKeychainServiceAndSettings } from './services/SettingUtils';
import MigrationService from './services/MigrationService';
import ShareService from './services/share/ShareService';
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
@@ -51,7 +51,7 @@ import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
const { toSystemSlashes } = require('./path-utils');
const { setAutoFreeze } = require('immer');
import { getEncryptionEnabled } from './services/synchronizer/syncInfoUtils';
import { loadMasterKeysFromSettings } from './services/e2ee/utils';
import { loadMasterKeysFromSettings, migrateMasterPassword } from './services/e2ee/utils';
import SyncTargetNone from './SyncTargetNone';
const appLogger: LoggerWrapper = Logger.create('App');
@@ -465,6 +465,7 @@ export default class BaseApplication {
sideEffects['timeFormat'] = sideEffects['dateFormat'];
sideEffects['locale'] = sideEffects['dateFormat'];
sideEffects['encryption.passwordCache'] = sideEffects['syncInfoCache'];
sideEffects['encryption.masterPassword'] = sideEffects['syncInfoCache'];
if (action) {
const effect = sideEffects[action.key];
@@ -768,6 +769,7 @@ export default class BaseApplication {
BaseModel.setDb(this.database_);
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
await migrateMasterPassword();
await handleSyncStartupOperation();
appLogger.info(`Client ID: ${Setting.value('clientId')}`);
@@ -825,7 +827,6 @@ export default class BaseApplication {
KvStore.instance().setDb(reg.db());
EncryptionService.instance().setLogger(globalLogger);
BaseItem.encryptionService_ = EncryptionService.instance();
BaseItem.shareService_ = ShareService.instance();
DecryptionWorker.instance().setLogger(globalLogger);

View File

@@ -1,6 +1,6 @@
import Logger from './Logger';
import Synchronizer from './Synchronizer';
import EncryptionService from './services/EncryptionService';
import EncryptionService from './services/e2ee/EncryptionService';
import shim from './shim';
import ResourceService from './services/ResourceService';
import ShareService from './services/share/ShareService';

View File

@@ -259,6 +259,14 @@ export default class JoplinDatabase extends Database {
folders: {},
resources: {},
tags: {},
item_changes: {
type: 'The type of change - either 1 (created), 2 (updated) or 3 (deleted)',
created_time: 'When the event was generated',
item_type: 'The item type (see table above for the list of item types)',
item_id: 'The item ID',
before_change_item: 'Unused',
source: 'Unused',
},
};
const baseItems = ['notes', 'folders', 'tags', 'resources'];

View File

@@ -1,6 +1,4 @@
const ObjectUtils = {};
ObjectUtils.sortByValue = function(object) {
export function sortByValue(object: any) {
const temp = [];
for (const k in object) {
if (!object.hasOwnProperty(k)) continue;
@@ -19,16 +17,16 @@ ObjectUtils.sortByValue = function(object) {
return v1 < v2 ? -1 : +1;
});
const output = {};
const output: any = {};
for (let i = 0; i < temp.length; i++) {
const item = temp[i];
output[item.key] = item.value;
}
return output;
};
}
ObjectUtils.fieldsEqual = function(o1, o2) {
export function fieldsEqual(o1: any, o2: any) {
if ((!o1 || !o2) && o1 !== o2) return false;
for (const k in o1) {
@@ -42,10 +40,10 @@ ObjectUtils.fieldsEqual = function(o1, o2) {
if (c1.length !== c2.length) return false;
return true;
};
}
ObjectUtils.convertValuesToFunctions = function(o) {
const output = {};
export function convertValuesToFunctions(o: any) {
const output: any = {};
for (const n in o) {
if (!o.hasOwnProperty(n)) continue;
output[n] = () => {
@@ -53,11 +51,26 @@ ObjectUtils.convertValuesToFunctions = function(o) {
};
}
return output;
};
}
ObjectUtils.isEmpty = function(o) {
export function isEmpty(o: any) {
if (!o) return true;
return Object.keys(o).length === 0 && o.constructor === Object;
};
}
module.exports = ObjectUtils;
// export function isStringifiable(o:any):boolean {
// if (o === null || o === undefined) return true;
// if (Array.isArray(o)) {
// for (const e of o) {
// if (!isStringifiable(e)) return false;
// }
// return true;
// }
// if (typeof o === 'object') {
// }
// return true;
// }

View File

@@ -15,7 +15,7 @@ import MasterKey from './models/MasterKey';
import BaseModel, { ModelType } from './BaseModel';
import time from './time';
import ResourceService from './services/ResourceService';
import EncryptionService from './services/EncryptionService';
import EncryptionService from './services/e2ee/EncryptionService';
import JoplinError from './JoplinError';
import ShareService from './services/share/ShareService';
import TaskQueue from './TaskQueue';

View File

@@ -0,0 +1,13 @@
// AUTO-GENERATED using `gulp buildCommandIndex`
import * as historyBackward from './historyBackward';
import * as historyForward from './historyForward';
import * as synchronize from './synchronize';
const index:any[] = [
historyBackward,
historyForward,
synchronize,
];
export default index;
// AUTO-GENERATED using `gulp buildCommandIndex`

View File

@@ -1,4 +1,4 @@
import EncryptionService from '../../services/EncryptionService';
import EncryptionService from '../../services/e2ee/EncryptionService';
import { _ } from '../../locale';
import BaseItem from '../../models/BaseItem';
import Setting from '../../models/Setting';
@@ -8,6 +8,7 @@ import shim from '../../shim';
import { MasterKeyEntity } from '../../services/e2ee/types';
import time from '../../time';
import { masterKeyEnabled, setMasterKeyEnabled } from '../../services/synchronizer/syncInfoUtils';
import { findMasterKeyPassword } from '../../services/e2ee/utils';
class Shared {
@@ -16,12 +17,16 @@ class Shared {
public initialize(comp: any, props: any) {
comp.state = {
passwordChecks: {},
// Master keys that can be decrypted with the master password
// (normally all of them, but for legacy support we need this).
masterPasswordKeys: {},
stats: {
encrypted: null,
total: null,
},
passwords: Object.assign({}, props.passwords),
showDisabledMasterKeys: false,
masterPasswordInput: '',
};
comp.isMounted_ = false;
@@ -108,15 +113,37 @@ class Shared {
}
}
public async masterPasswordIsValid(comp: any, masterPassword: string = null) {
const activeMasterKey = comp.props.masterKeys.find((mk: MasterKeyEntity) => mk.id === comp.props.activeMasterKeyId);
masterPassword = masterPassword === null ? comp.props.masterPassword : masterPassword;
if (activeMasterKey && masterPassword) {
return EncryptionService.instance().checkMasterKeyPassword(activeMasterKey, masterPassword);
}
return false;
}
public async checkPasswords(comp: any) {
const passwordChecks = Object.assign({}, comp.state.passwordChecks);
const masterPasswordKeys = Object.assign({}, comp.state.masterPasswordKeys);
for (let i = 0; i < comp.props.masterKeys.length; i++) {
const mk = comp.props.masterKeys[i];
const password = comp.state.passwords[mk.id];
const password = await findMasterKeyPassword(EncryptionService.instance(), mk);
const ok = password ? await EncryptionService.instance().checkMasterKeyPassword(mk, password) : false;
passwordChecks[mk.id] = ok;
masterPasswordKeys[mk.id] = password === comp.props.masterPassword;
}
comp.setState({ passwordChecks: passwordChecks });
passwordChecks['master'] = await this.masterPasswordIsValid(comp);
comp.setState({ passwordChecks, masterPasswordKeys });
}
public masterPasswordStatus(comp: any) {
// Don't translate for now because that's temporary - later it should
// always be set and the label should be replaced by a "Change master
// password" button.
return comp.props.masterPassword ? 'Master password is set' : 'Master password is not set';
}
public decryptedStatText(comp: any) {
@@ -138,6 +165,14 @@ class Shared {
comp.checkPasswords();
}
public onMasterPasswordChange(comp: any, value: string) {
comp.setState({ masterPasswordInput: value });
}
public onMasterPasswordSave(comp: any) {
Setting.setValue('encryption.masterPassword', comp.state.masterPasswordInput);
}
public onPasswordChange(comp: any, mk: MasterKeyEntity, password: string) {
const passwords = Object.assign({}, comp.state.passwords);
passwords[mk.id] = password;

View File

@@ -77,7 +77,7 @@ export default class Database {
throw new Error(`Invalid field format: ${field}`);
}
escapeFields(fields: string[] | string): string[] | string {
public escapeFields(fields: string[] | string): string[] | string {
if (fields == '*') return '*';
const output = [];
@@ -87,6 +87,16 @@ export default class Database {
return output;
}
public escapeFieldsToString(fields: string[] | string): string {
if (fields === '*') return '*';
const output = [];
for (let i = 0; i < fields.length; i++) {
output.push(this.escapeField(fields[i]));
}
return output.join(',');
}
async tryCall(callName: string, inputSql: StringOrSqlQuery, inputParams: SqlParams) {
let sql: string = null;
let params: SqlParams = null;

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