1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-30 20:39:46 +02:00

Compare commits

..

117 Commits

Author SHA1 Message Date
Laurent Cozic
812666957c update 2025-02-12 18:13:27 +00:00
Laurent Cozic
dabb7e08b4 init 2025-02-12 15:30:21 +00:00
Henry Heino
91ac4f8526 Chore: Resolves #11800: Web: Improve reload behavior in dev mode (#11803) 2025-02-11 21:15:01 +00:00
Laurent Cozic
3603350287 lock file 2025-02-11 18:12:11 +00:00
Henry Heino
bcde346ebe Web: Fixes #11800: Fix "Only one client can be open at a time" shown in dev mode (#11819) 2025-02-11 15:31:39 +00:00
renovate[bot]
9803d7985d Update dependency @types/node-fetch to v2.6.12 (#11817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 15:52:17 +00:00
renovate[bot]
30f6b3ecb2 Update dependency @axe-core/playwright to v4.10.1 (#11816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 15:52:07 +00:00
Self Not Found
0b287d1113 Website: Fix compatibility issues of Mermaid graphs (#11809) 2025-02-09 09:39:42 +00:00
renovate[bot]
be18655ceb Update Rust crate thiserror to v1.0.69 (#11808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-09 09:37:04 +00:00
Henry Heino
be43ff42c9 Web: Upgrade react-native-web to v0.19.13 (#11804) 2025-02-08 10:02:35 +00:00
pedr
1230e1b30c Desktop: Accessibility: Add a new shortcut to set focus to editor toolbar (#11764) 2025-02-07 20:41:22 +00:00
pedr
f5d168b16a Desktop: Accessibility: Fixes focus going to start of document when Note History is open (#11769) 2025-02-07 20:41:03 +00:00
Henry Heino
7055d3db18 Windows: Re-enable the beta "auto-update" feature flag (#11802) 2025-02-07 20:18:40 +00:00
mrjo118
18a9c3f841 Desktop, Mobile: Harden failsafe logic to check for the presence of info.json, rather than just the item count (#11750) 2025-02-07 19:08:01 +00:00
pedr
a4ab197c42 Desktop: Accessibility: Add label to the delete buttons of the Note Attachments (#11749) 2025-02-07 19:04:37 +00:00
Henry Heino
2b5881a103 Mobile: Accessibility: Make it possible to create and edit profiles with a screen reader (#11797) 2025-02-07 19:00:24 +00:00
Henry Heino
e9dee4cd99 Desktop,Mobile: Resolves #11790: Render strikethrough text in the editor (#11795) 2025-02-07 19:00:02 +00:00
Henry Heino
dd948f5c95 Desktop: Fixes #11783: Plugins: Fix toast notifications don't reappear unless parameters are changed (#11786) 2025-02-07 18:59:54 +00:00
Laurent Cozic
798e1b8f4f Desktop, Mobile, Cli: Move S3 sync target out of beta 2025-02-06 22:22:28 +00:00
Self Not Found
b3f69794b1 All: Add specification document for new encryption methods (#11754) 2025-02-06 18:14:51 +00:00
Henry Heino
f25e1a5e80 Desktop,Mobile: Plugins: Legacy editor API: Fix delayed crash caused by out-of-bounds inputs (#11714) 2025-02-06 18:12:43 +00:00
Henry Heino
786e55c972 Desktop: Performance: Improve performance when changing window state (#11720) 2025-02-06 18:12:16 +00:00
mrjo118
cd9155514c Mobile: Fixes #11571: Use alternative fix to set the sqlite CursorWindow size to 50mb (#11726) 2025-02-06 18:11:51 +00:00
Henry Heino
3e9e669642 Desktop: Built-in plugins: Update Freehand Drawing to v2.15.0 (#11735) 2025-02-06 18:11:10 +00:00
pedr
e36a30eb1a Desktop: Fix datetime values not appearing on note properties when the picker is open (#11748) 2025-02-06 18:10:59 +00:00
Henry Heino
1975ebd438 Desktop,Mobile: Highlight ==marked== text in the Markdown editor (#11794) 2025-02-06 18:04:15 +00:00
Henry Heino
94bff77313 Mobile: Update js-draw to version 1.27.2 (#11788) 2025-02-06 18:03:58 +00:00
Henry Heino
6e3258a5d8 Mobile: Plugins: Support the showToast API (#11787) 2025-02-06 18:03:50 +00:00
Henry Heino
c55c8d62ec Desktop: Fixes #11662: Prevent the default note title from being "&nbsp;" (#11785) 2025-02-06 18:00:40 +00:00
Henry Heino
c7031568a8 Mobile: Fix homescreen new-note shortcuts are re-applied after switching notes (#11779) 2025-02-06 17:57:27 +00:00
Henry Heino
c2c72215b9 Desktop: Accessibility: Add accessibility information to the warning banner (#11775) 2025-02-06 17:57:12 +00:00
Henry Heino
cc09f92d3b Mobile: Toolbar: Show only half of last button to indicate scroll (#11772) 2025-02-06 17:56:56 +00:00
Henry Heino
8312196faa Mobile,Desktop,Cli: Logging: Log less information at level warn when a decryption error occurs (#11771) 2025-02-06 17:56:16 +00:00
Henry Heino
a16a66c37b Desktop,Mobile: Sync: Fix share not marked as readonly if the recipient has an outgoing share (#11770) 2025-02-06 17:55:54 +00:00
Henry Heino
a8210225a0 Desktop: Improve font picker accessibility (#11763) 2025-02-06 17:40:01 +00:00
Henry Heino
cd50454664 Android: Fix error logged when opening the note editor (#11761) 2025-02-06 17:39:48 +00:00
Henry Heino
986163721d Chore: Slightly stronger types in ResourceService.ts (#11756) 2025-02-06 17:39:33 +00:00
Laurent Cozic
e41dcb9bc9 Doc: Archived GSoC 2024 documents 2025-02-05 16:45:07 +00:00
Henry Heino
f90e642f43 Desktop: Fix crash when closing a secondary window with the Rich Text Editor open (#11737) 2025-02-04 00:09:12 +00:00
renovate[bot]
67d1dd36be Update Rust crate thiserror to v1.0.68 (#11768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2025-02-03 11:30:54 +00:00
renovate[bot]
2cba693905 Update dependency @types/node to v18.19.64 (#11747)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 00:44:16 +00:00
renovate[bot]
a226ede5d7 Update Rust crate thiserror to v1.0.67 (#11766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 00:41:50 +00:00
Laurent Cozic
7994c0bc79 Desktop: Disable featureFlag.autoUpdaterServiceEnabled for now 2025-02-03 00:14:35 +00:00
Joplin Bot
d589891a86 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-02-01 00:54:39 +00:00
renovate[bot]
fe6c949cc1 Update Rust crate thiserror to v1.0.66 (#11755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 08:21:55 +00:00
Henry Heino
4e677d2baf Desktop: Accessibility: Fix incorrect note viewer accessibility label (#11744) 2025-01-30 06:58:53 -08:00
Henry Heino
25aab57af5 Desktop: Fix secondary windows not removed from state if closed while focused (#11740) 2025-01-30 06:58:12 -08:00
pedr
db81064c98 Desktop: Fixes #11738: Context menu was empty for notes on Trash folder (#11743) 2025-01-30 06:57:55 -08:00
renovate[bot]
9b82578253 Update dependency @types/node to v18.19.61 (#11736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 20:07:22 +00:00
Henry Heino
bb513c83ac Desktop: Accessibility: Rich Text Editor: Make it possible to edit code blocks with a keyboard or touchscreen (#11727) 2025-01-27 12:05:29 -08:00
Henry Heino
662185816d Desktop: Accessibility: Allow toggling between tab navigation and indentation (#11717) 2025-01-27 10:34:58 -08:00
Henry Heino
cc1582d535 Desktop,Mobile,Cli: Fixes #11630: Adjust how items are queried by ID (#11734) 2025-01-27 10:34:46 -08:00
Henry Heino
aa6348a127 Setup new release 3.3 2025-01-27 09:35:44 -08:00
pedr
68f4b8ed0c Desktop: Accessibility: Fix input fields not associated with labels (#11700) 2025-01-27 08:52:11 -08:00
Henry Heino
98540493e0 Desktop: Accessibility: Improve scrollbar contrast (#11708) 2025-01-27 08:23:00 -08:00
Henry Heino
762daa5a68 Desktop: Accessibility: Improve "change application layout" screen keyboard accessibility (#11718) 2025-01-27 08:18:37 -08:00
renovate[bot]
827233605e Update dependency @types/node to v18.19.60 (#11731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 15:53:13 +00:00
renovate[bot]
31b13defb6 Update dependency @types/adm-zip to v0.5.6 (#11730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 15:52:06 +00:00
pedr
8611391d01 Desktop: Accessibility: Replacing library used for datetime with native input element (#11725) 2025-01-27 07:50:38 -08:00
renovate[bot]
5a3d57e39a Update dependency @react-native-clipboard/clipboard to v1.14.3 (#11724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-26 02:30:00 +00:00
Joplin Bot
e22ccd6edf Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-24 00:49:56 +00:00
renovate[bot]
8aec0ae445 Update dependency @react-native-community/slider to v4.5.5 (#11719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 23:55:20 +00:00
Henry Heino
24a2f5452c Merge branch 'release-3.2' into dev 2025-01-23 15:42:51 -08:00
Henry Heino
d6f1ca4ba4 Desktop release v3.2.12 2025-01-23 14:57:15 -08:00
Henry Heino
2a058ed809 Desktop,Mobile: Security: Improve comment escaping (#11706) 2025-01-23 14:38:52 -08:00
renovate[bot]
877123bda7 Update Rust crate encoding_rs to v0.8.35 (#11704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 14:35:01 +00:00
Henry Heino
d621e631f7 Desktop: Fix error when a note has no history (#11702) 2025-01-22 13:39:21 +00:00
Laurent Cozic
64d1da9773 Doc: Removed Twitter links 2025-01-21 22:12:25 +00:00
Joplin Bot
2643bb9b32 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-21 18:23:39 +00:00
Laurent Cozic
5c737b3ccd Doc: Update sponsors 2025-01-21 17:08:34 +00:00
Laurent Cozic
23f75f8784 Desktop: Added shortcut Cmd+Option+N to open note in new window, and added command to menu bar 2025-01-21 15:08:20 +00:00
Joplin Bot
60b2f69620 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-21 12:26:51 +00:00
renovate[bot]
1d00b7a68e Update dependency @types/node to v18.19.59 (#11693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 12:08:11 +00:00
pedr
d0b783c595 Desktop: Resolves #11642: Add alt text to welcome notes (#11643) 2025-01-21 10:29:21 +00:00
pedr
9c446b03da Desktop: Resolves #11654: Add scrollbar to Note Revision to allow usage on zoomed interface (#11689) 2025-01-21 10:28:38 +00:00
renovate[bot]
0603c56446 Update Rust crate thiserror to v1.0.65 (#11688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 10:27:33 +00:00
Laurent Cozic
4223864302 Desktop, Mobile: Do not add double newlines around attached files (#11690) 2025-01-21 10:25:28 +00:00
summoner001
0ddf5732a8 Update hu_HU.po (#11682) 2025-01-20 17:45:25 +00:00
renovate[bot]
a5ffc11831 Update dependency @types/node to v18.19.58 (#11685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 17:36:49 +00:00
pedr
157ad2c0cd Desktop: Resolves #11621: Accessibility: Add status update after update (#11634) 2025-01-20 17:34:36 +00:00
Laurent Cozic
0ac710ecf9 Revert "Tools: Make settings in settings.json recommended only"
This reverts commit d3046582e1.

Reason: not how it works
2025-01-20 08:55:52 +00:00
Laurent Cozic
d3046582e1 Tools: Make settings in settings.json recommended only
Ref: https://discourse.joplinapp.org/t/vscode-settings-json-shouldnt-be-under-version-control/43184/6?u=laurent
2025-01-20 08:52:41 +00:00
qx100
9642640cda Translation: Update Simplified Chinese Translation (#11679) 2025-01-18 22:47:13 +00:00
Henry Heino
dab2438df0 Mobile: Resolves #10824: Allow adjusting the default note viewer font size (#11633) 2025-01-18 12:43:07 +00:00
Henry Heino
dc7871b655 Desktop,Mobile: Accessibility: Improve contrast of faded URLs in Markdown editor (#11635) 2025-01-18 12:42:19 +00:00
Henry Heino
ff465767ab Desktop: Accessibility: Make Markdown toolbar scrollable when low on space (#11636) 2025-01-18 12:41:56 +00:00
Henry Heino
c58aac9387 Desktop: Accessibility: Improve sidebar content contrast (#11638) 2025-01-18 12:41:45 +00:00
pedr
29e55b8231 Desktop: Fixes #11640: Pressing Shift+Tab when focus is on notebook list would jump straight to editor (#11641) 2025-01-18 12:41:18 +00:00
pedr
dc10ff6215 Desktop: Resolves #11644: Add proper type to search input (#11645) 2025-01-18 12:38:45 +00:00
Henry Heino
e8e3ef36ed Desktop: Accessibility: Improve sync wizard accessibility (#11649) 2025-01-18 12:38:23 +00:00
Henry Heino
e1b41cff5f Desktop: Accessibility: Mark secondary screen navigation bars as navigation regions (#11650) 2025-01-18 12:38:11 +00:00
Henry Heino
5782ee6ba1 Desktop: Upgrade to TinyMCE v6 (#11652) 2025-01-18 12:37:46 +00:00
cedecode
cbf81d1257 Translation: Update de_DE.po (#11653) 2025-01-18 12:37:11 +00:00
Henry Heino
c357b77a48 Desktop: Accessibility: Fix unlabelled toolbar button in the Rich Text Editor (#11655) 2025-01-18 12:37:03 +00:00
Arda Kılıçdağı
64c14fe76f Translation: Turkish translations updated (#11659) 2025-01-18 12:36:56 +00:00
Henry Heino
b2c1d7a2ba Desktop: Upgrade to Electron 34 (#11665) 2025-01-18 12:36:39 +00:00
ERYpTION
dbd4cffef3 Update da_DK.po (#11668) 2025-01-18 12:36:25 +00:00
renovate[bot]
d190463325 Update dependency @types/node to v18.19.57 (#11669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-18 12:36:15 +00:00
Henry Heino
2c1aa5d620 Desktop,Mobile: Allow internal links to target elements using the name attribute (#11671) 2025-01-18 12:36:09 +00:00
Henry Heino
52d255352a Chore: Upgrade Webpack to v5.97.1 (#11672) 2025-01-18 12:35:30 +00:00
Henry Heino
76274033db Android: Fixes #11292: Fix unable to sync with multiple Nextcloud accounts in different profiles (#11674) 2025-01-18 12:35:22 +00:00
Liffindra Angga Zaaldian
92abfac3af update Indonesian translation (#11677) 2025-01-18 12:35:04 +00:00
Laurent Cozic
c6956df1c9 Desktop: Resolves #11664: Double click to open a note in a new window 2025-01-17 12:16:00 +00:00
Joplin Bot
0bd1e202a2 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-16 18:22:47 +00:00
Laurent Cozic
f602ad8a63 Desktop: Resolves #11664: Reorganised note list context menu 2025-01-16 17:43:10 +00:00
Laurent Cozic
6a1b498e96 Merge branch 'release-3.2' into dev 2025-01-16 15:24:19 +00:00
pedr
519f3f5898 Desktop, Mobile: Resolves #11647: Improve Welcome Notes with clearer instructions (#11656)
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
2025-01-16 15:22:46 +00:00
renovate[bot]
907b1e969e Update dependency @types/node to v18.19.56 (#11658)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 01:12:59 +00:00
Joplin Bot
b59774a763 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-15 18:22:33 +00:00
Laurent Cozic
0494719e4f Doc: Update sponsors 2025-01-15 13:28:19 +00:00
Joplin Bot
0e1b81685a Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-15 12:26:36 +00:00
Laurent Cozic
c157cd0cb3 Doc: Update sponsors 2025-01-15 11:57:41 +00:00
Joplin Bot
5c711df2e4 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-14 18:23:59 +00:00
Laurent Cozic
e520a695a6 Doc: Add news item for release 3.2 2025-01-14 15:45:04 +00:00
renovate[bot]
5ee8a9a454 Update dependency @react-native-community/slider to v4.5.4 (#11632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 11:35:49 +00:00
Joplin Bot
6b73879512 Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-14 00:51:08 +00:00
Joplin Bot
f08235f05c Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-01-13 18:23:39 +00:00
253 changed files with 7251 additions and 3957 deletions

View File

@@ -170,6 +170,7 @@ packages/app-desktop/commands/switchProfile2.js
packages/app-desktop/commands/switchProfile3.js
packages/app-desktop/commands/toggleExternalEditing.js
packages/app-desktop/commands/toggleSafeMode.js
packages/app-desktop/commands/toggleTabMovesFocus.js
packages/app-desktop/gui/Button/Button.js
packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js
@@ -201,6 +202,7 @@ packages/app-desktop/gui/FolderIconBox.js
packages/app-desktop/gui/HelpButton.js
packages/app-desktop/gui/IconButton.js
packages/app-desktop/gui/ImportScreen.js
packages/app-desktop/gui/InlineCombobox.js
packages/app-desktop/gui/ItemList.js
packages/app-desktop/gui/JoplinCloudConfigScreen.js
packages/app-desktop/gui/JoplinCloudLoginScreen.js
@@ -249,13 +251,15 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVis
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/enableTextAreaTab.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.test.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
@@ -263,10 +267,12 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/StatusBar.js
packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js
packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.js
packages/app-desktop/gui/NoteEditor/commands/index.js
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
@@ -312,6 +318,7 @@ packages/app-desktop/gui/NoteList/utils/useItemCss.js
packages/app-desktop/gui/NoteList/utils/useMoveNote.js
packages/app-desktop/gui/NoteList/utils/useOnKeyDown.js
packages/app-desktop/gui/NoteList/utils/useOnNoteClick.js
packages/app-desktop/gui/NoteList/utils/useOnNoteDoubleClick.js
packages/app-desktop/gui/NoteList/utils/useScroll.js
packages/app-desktop/gui/NoteList/utils/useVisibleRange.test.js
packages/app-desktop/gui/NoteList/utils/useVisibleRange.js
@@ -354,6 +361,7 @@ packages/app-desktop/gui/PasswordInput/types.js
packages/app-desktop/gui/PdfViewer.js
packages/app-desktop/gui/PluginNotification/PluginNotification.js
packages/app-desktop/gui/PromptDialog.js
packages/app-desktop/gui/ResizableLayout/LayoutItemContainer.js
packages/app-desktop/gui/ResizableLayout/MoveButtons.js
packages/app-desktop/gui/ResizableLayout/ResizableLayout.js
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.js
@@ -448,7 +456,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/revealResourceFile.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/search.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showEditorPlugin.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.js
@@ -457,7 +464,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareFolderDialog
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.test.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.js
@@ -490,6 +496,7 @@ packages/app-desktop/gui/style/StyledInput.js
packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/utils/NoteListUtils.test.js
packages/app-desktop/gui/utils/NoteListUtils.js
packages/app-desktop/gui/utils/announceForAccessibility.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
@@ -499,6 +506,8 @@ packages/app-desktop/gulpfile.js
packages/app-desktop/integration-tests/goToAnything.spec.js
packages/app-desktop/integration-tests/main.spec.js
packages/app-desktop/integration-tests/markdownEditor.spec.js
packages/app-desktop/integration-tests/models/ChangeAppLayoutScreen.js
packages/app-desktop/integration-tests/models/EditorCodeDialog.js
packages/app-desktop/integration-tests/models/GoToAnything.js
packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
@@ -507,6 +516,7 @@ packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/models/Sidebar.js
packages/app-desktop/integration-tests/noteList.spec.js
packages/app-desktop/integration-tests/pluginApi.spec.js
packages/app-desktop/integration-tests/resizableLayout.spec.js
packages/app-desktop/integration-tests/richTextEditor.spec.js
packages/app-desktop/integration-tests/settings.spec.js
packages/app-desktop/integration-tests/sidebar.spec.js
@@ -563,6 +573,7 @@ packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/initializeCommandService.js
packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
@@ -607,6 +618,7 @@ packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromSta
packages/app-mobile/components/EditorToolbar/utils/isSelected.js
packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.js
packages/app-mobile/components/EditorToolbar/utils/toolbarButtonsFromState.js
packages/app-mobile/components/EditorToolbar/utils/useButtonSize.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
@@ -684,6 +696,7 @@ packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginNotification.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
@@ -857,6 +870,7 @@ packages/app-mobile/utils/testing/getWebViewWindowById.js
packages/app-mobile/utils/testing/setupGlobalStore.js
packages/app-mobile/utils/types.js
packages/app-mobile/web/serviceWorker.js
packages/app-mobile/web/webpack.config.js
packages/default-plugins/build.js
packages/default-plugins/buildDefaultPlugins.js
packages/default-plugins/commands/buildAll.js
@@ -883,6 +897,10 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/markdown/MarkdownHighlightExtension.test.js
packages/editor/CodeMirror/markdown/MarkdownHighlightExtension.js
packages/editor/CodeMirror/markdown/MarkdownMathExtension.test.js
packages/editor/CodeMirror/markdown/MarkdownMathExtension.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/defaultLanguage.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/lookUpLanguage.js
@@ -894,8 +912,6 @@ packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.js
packages/editor/CodeMirror/markdown/markdownCommands.test.js
packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
@@ -906,6 +922,7 @@ packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testUtil/createEditorControl.js
packages/editor/CodeMirror/testUtil/createEditorSettings.js
packages/editor/CodeMirror/testUtil/createTestEditor.js
packages/editor/CodeMirror/testUtil/findNodesWithName.js
packages/editor/CodeMirror/testUtil/forceFullParse.js
packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
@@ -1002,7 +1019,9 @@ packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/renderMarkup.test.js
packages/lib/commands/renderMarkup.js
packages/lib/commands/showEditorPlugin.js
packages/lib/commands/synchronize.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
@@ -1284,6 +1303,7 @@ packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
packages/lib/services/plugins/utils/getPluginSettingValue.js
packages/lib/services/plugins/utils/getShownPluginEditorView.js
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
packages/lib/services/plugins/utils/isCompatible/index.test.js
packages/lib/services/plugins/utils/isCompatible/index.js

30
.gitignore vendored
View File

@@ -145,6 +145,7 @@ packages/app-desktop/commands/switchProfile2.js
packages/app-desktop/commands/switchProfile3.js
packages/app-desktop/commands/toggleExternalEditing.js
packages/app-desktop/commands/toggleSafeMode.js
packages/app-desktop/commands/toggleTabMovesFocus.js
packages/app-desktop/gui/Button/Button.js
packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js
@@ -176,6 +177,7 @@ packages/app-desktop/gui/FolderIconBox.js
packages/app-desktop/gui/HelpButton.js
packages/app-desktop/gui/IconButton.js
packages/app-desktop/gui/ImportScreen.js
packages/app-desktop/gui/InlineCombobox.js
packages/app-desktop/gui/ItemList.js
packages/app-desktop/gui/JoplinCloudConfigScreen.js
packages/app-desktop/gui/JoplinCloudLoginScreen.js
@@ -224,13 +226,15 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVis
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/enableTextAreaTab.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.test.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
@@ -238,10 +242,12 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
packages/app-desktop/gui/NoteEditor/NoteEditor.js
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
packages/app-desktop/gui/NoteEditor/StatusBar.js
packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js
packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.js
packages/app-desktop/gui/NoteEditor/commands/index.js
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
@@ -287,6 +293,7 @@ packages/app-desktop/gui/NoteList/utils/useItemCss.js
packages/app-desktop/gui/NoteList/utils/useMoveNote.js
packages/app-desktop/gui/NoteList/utils/useOnKeyDown.js
packages/app-desktop/gui/NoteList/utils/useOnNoteClick.js
packages/app-desktop/gui/NoteList/utils/useOnNoteDoubleClick.js
packages/app-desktop/gui/NoteList/utils/useScroll.js
packages/app-desktop/gui/NoteList/utils/useVisibleRange.test.js
packages/app-desktop/gui/NoteList/utils/useVisibleRange.js
@@ -329,6 +336,7 @@ packages/app-desktop/gui/PasswordInput/types.js
packages/app-desktop/gui/PdfViewer.js
packages/app-desktop/gui/PluginNotification/PluginNotification.js
packages/app-desktop/gui/PromptDialog.js
packages/app-desktop/gui/ResizableLayout/LayoutItemContainer.js
packages/app-desktop/gui/ResizableLayout/MoveButtons.js
packages/app-desktop/gui/ResizableLayout/ResizableLayout.js
packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.js
@@ -423,7 +431,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/revealResourceFile.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/search.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showEditorPlugin.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.js
@@ -432,7 +439,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareFolderDialog
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.test.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.js
@@ -465,6 +471,7 @@ packages/app-desktop/gui/style/StyledInput.js
packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/utils/NoteListUtils.test.js
packages/app-desktop/gui/utils/NoteListUtils.js
packages/app-desktop/gui/utils/announceForAccessibility.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
@@ -474,6 +481,8 @@ packages/app-desktop/gulpfile.js
packages/app-desktop/integration-tests/goToAnything.spec.js
packages/app-desktop/integration-tests/main.spec.js
packages/app-desktop/integration-tests/markdownEditor.spec.js
packages/app-desktop/integration-tests/models/ChangeAppLayoutScreen.js
packages/app-desktop/integration-tests/models/EditorCodeDialog.js
packages/app-desktop/integration-tests/models/GoToAnything.js
packages/app-desktop/integration-tests/models/MainScreen.js
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
@@ -482,6 +491,7 @@ packages/app-desktop/integration-tests/models/SettingsScreen.js
packages/app-desktop/integration-tests/models/Sidebar.js
packages/app-desktop/integration-tests/noteList.spec.js
packages/app-desktop/integration-tests/pluginApi.spec.js
packages/app-desktop/integration-tests/resizableLayout.spec.js
packages/app-desktop/integration-tests/richTextEditor.spec.js
packages/app-desktop/integration-tests/settings.spec.js
packages/app-desktop/integration-tests/sidebar.spec.js
@@ -538,6 +548,7 @@ packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/initializeCommandService.js
packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
@@ -582,6 +593,7 @@ packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromSta
packages/app-mobile/components/EditorToolbar/utils/isSelected.js
packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.js
packages/app-mobile/components/EditorToolbar/utils/toolbarButtonsFromState.js
packages/app-mobile/components/EditorToolbar/utils/useButtonSize.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
@@ -659,6 +671,7 @@ packages/app-mobile/components/buttons/index.js
packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/global-style.js
packages/app-mobile/components/plugins/PluginNotification.js
packages/app-mobile/components/plugins/PluginRunner.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
@@ -832,6 +845,7 @@ packages/app-mobile/utils/testing/getWebViewWindowById.js
packages/app-mobile/utils/testing/setupGlobalStore.js
packages/app-mobile/utils/types.js
packages/app-mobile/web/serviceWorker.js
packages/app-mobile/web/webpack.config.js
packages/default-plugins/build.js
packages/default-plugins/buildDefaultPlugins.js
packages/default-plugins/commands/buildAll.js
@@ -858,6 +872,10 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/markdown/MarkdownHighlightExtension.test.js
packages/editor/CodeMirror/markdown/MarkdownHighlightExtension.js
packages/editor/CodeMirror/markdown/MarkdownMathExtension.test.js
packages/editor/CodeMirror/markdown/MarkdownMathExtension.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/defaultLanguage.js
packages/editor/CodeMirror/markdown/codeBlockLanguages/lookUpLanguage.js
@@ -869,8 +887,6 @@ packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.js
packages/editor/CodeMirror/markdown/markdownCommands.test.js
packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/markdown/markdownCommands.js
packages/editor/CodeMirror/markdown/markdownMathParser.test.js
packages/editor/CodeMirror/markdown/markdownMathParser.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
@@ -881,6 +897,7 @@ packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testUtil/createEditorControl.js
packages/editor/CodeMirror/testUtil/createEditorSettings.js
packages/editor/CodeMirror/testUtil/createTestEditor.js
packages/editor/CodeMirror/testUtil/findNodesWithName.js
packages/editor/CodeMirror/testUtil/forceFullParse.js
packages/editor/CodeMirror/testUtil/loadLanguages.js
packages/editor/CodeMirror/testUtil/pressReleaseKey.js
@@ -977,7 +994,9 @@ packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/renderMarkup.test.js
packages/lib/commands/renderMarkup.js
packages/lib/commands/showEditorPlugin.js
packages/lib/commands/synchronize.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
@@ -1259,6 +1278,7 @@ packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
packages/lib/services/plugins/utils/getPluginSettingValue.js
packages/lib/services/plugins/utils/getShownPluginEditorView.js
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
packages/lib/services/plugins/utils/isCompatible/index.test.js
packages/lib/services/plugins/utils/isCompatible/index.js

View File

@@ -0,0 +1,37 @@
diff --git a/platforms/android/src/main/java/org/pgsqlite/SQLitePlugin.java b/platforms/android/src/main/java/org/pgsqlite/SQLitePlugin.java
index 4f2391b..f7cc433 100644
--- a/platforms/android/src/main/java/org/pgsqlite/SQLitePlugin.java
+++ b/platforms/android/src/main/java/org/pgsqlite/SQLitePlugin.java
@@ -8,11 +8,14 @@
package org.pgsqlite;
import android.annotation.SuppressLint;
+import android.database.AbstractWindowedCursor;
import android.database.Cursor;
+import android.database.CursorWindow;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.content.Context;
+import android.os.Build;
import android.util.Base64;
import java.io.Closeable;
@@ -808,6 +811,17 @@ public class SQLitePlugin extends ReactContextBaseJavaModule {
throw ex;
}
+ // To try to fix the error "Row too big to fit into CursorWindow" when using sqlite binary bundled with the device
+ // https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-526423153
+ // https://github.com/laurent22/joplin/issues/1767#issuecomment-515617991
+
+ if (cur != null && Build.VERSION.SDK_INT >= 28) {
+ CursorWindow cw = new CursorWindow(null, 50 * 1024 * 1024);
+ AbstractWindowedCursor ac = (AbstractWindowedCursor) cur;
+ ac.setWindow(cw);
+ cur = ac;
+ }
+
// If query result has rows
if (cur != null && cur.moveToFirst()) {
WritableArray rowsArrayResult = Arguments.createArray();

View File

@@ -8,7 +8,6 @@
import { Node } from '@ephox/dom-globals';
import { Arr, Option } from '@ephox/katamari';
import { HTMLElement } from '@ephox/sand';
import DomQuery from 'tinymce/core/api/dom/DomQuery';
import Editor from 'tinymce/core/api/Editor';
import Tools from 'tinymce/core/api/util/Tools';
import * as NodeType from './NodeType';
@@ -49,7 +48,7 @@ const findParentListItemsNodes = function (editor, elms) {
return parentLi ? parentLi : elm;
});
return DomQuery.unique(listItemsElms);
return [...new Set(listItemsElms)];
};
const getSelectedListItems = function (editor) {
@@ -89,7 +88,7 @@ const getSelectedListRoots = (editor: Editor): Node[] => {
const getUniqueListRoots = (editor: Editor, lists: Node[]): Node[] => {
const listRoots = Arr.map(lists, (list) => findLastParentListNode(editor, list).getOr(list));
return DomQuery.unique(listRoots);
return [...new Set(listRoots)];
};
const isList = (editor: Editor): boolean => {

View File

@@ -48,8 +48,7 @@ const listState = function (editor: Editor, listName, options:any = {}) {
const register = function (editor: Editor) {
const hasPlugin = function (editor, plugin) {
const plugins = editor.settings.plugins ? editor.settings.plugins : '';
return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1;
return editor.hasPlugin(plugin);
};
const _ = Settings.getLocalizationFunction(editor);

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Tue, 17 Dec 2024 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Tue, 17 Dec 2024 00:00:00 GMT</pubDate><item><title><![CDATA[Project 4: Handwritten Text Recognition (HTR) for Joplin]]></title><description><![CDATA[<p>Joplin is partnering with a French government institution to bring you innovative new features! We will work on accessibility, voice typing, HTR and add Rocketbook integration. Today we'll present the planned HTR integration:</p>
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Jan 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.2]]></title><description><![CDATA[<h2>Import OneNote Archives<a name="import-onenote-archives" href="#import-onenote-archives" class="heading-anchor">🔗</a></h2>
<p>Joplin now supports importing OneNote archives, a significant step for users transitioning from OneNote. Microsoft has long made it challenging to leave OneNote, offering limited export options and complex formats that make it difficult for app developers to support it. Despite these hurdles, @pedr tackled these issues head-on, developing an import tool that simplifies the process. This addition makes Joplin a practical choice for those looking to move away from OneNote's ecosystem.</p>
<p>To use this feature, select <strong>File</strong> =&gt; <strong>Import</strong> =&gt; <strong>ZIP - OneNote Notebook</strong></p>
<h2>Multi-window support<a name="multi-window-support" href="#multi-window-support" class="heading-anchor">🔗</a></h2>
<p>We're excited to introduce Multi-Window Support, a highly requested feature that makes managing multiple notes easier than ever. With this update, you can open notes in different windows and each window operates independently, allowing you to compare notes, reference content, and organise projects with greater flexibility.</p>
<p>To use this feature, right-click on a note, select <strong>Open in...</strong> and select <strong>Edit in new window</strong></p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250114-multi-window.png" alt=""></p>
<h2>Customisable toolbar on mobile<a name="customisable-toolbar-on-mobile" href="#customisable-toolbar-on-mobile" class="heading-anchor">🔗</a></h2>
<p>The new customisable toolbar on mobile is now draggable, making it easier to access the buttons you need. You can also choose which buttons to display by tapping the Cog button, allowing for a more personalised and efficient note-editing experience!</p>
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250114-mobile-toolbar.png" alt=""></p>
<h2>Enhanced Accessibility<a name="enhanced-accessibility" href="#enhanced-accessibility" class="heading-anchor">🔗</a></h2>
<p>In order to implement the <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0 guidelines</a>, accessibility has seen a substantial upgrade thanks to the efforts of @personalizedrefrigerator. The desktop and mobile apps now offer better keyboard navigation, including improved functionality in dropdown menus and settings. Focus indicators have been made more visible, while ARIA labels have been added to boost compatibility with screen readers. Specific areas like note attachments, sidebars, and dialogs have also been optimised to ensure accessibility for all users.</p>
<h2>Refined Drawing and Markdown Editing<a name="refined-drawing-and-markdown-editing" href="#refined-drawing-and-markdown-editing" class="heading-anchor">🔗</a></h2>
<p>Joplin's drawing and editing features have been fine-tuned for a smoother experience. Freehand Drawing on mobile and desktop has been updated to version 2.14.0, addressing several usability issues and bugs. Additionally, the Freehand Drawing plugin is now part of the desktop app by default. These changes enhance the reliability and integration of the drawing tool.</p>
<h2>Faster and more secure encryption<a name="faster-and-more-secure-encryption" href="#faster-and-more-secure-encryption" class="heading-anchor">🔗</a></h2>
<p>For GSoC 2024, @wh201906 worked on optimising the encryption and decryption processes, boosting speed for mobile devices in particular (but desktop too). Additionally, the encryption security was improved by transitioning to a more robust 256-bit key.</p>
<p>As of now this feature is optional and can be enabled by going to the <strong>Configuration Screen</strong>, then <strong>Synchronisation</strong> =&gt; <strong>Advanced options</strong> =&gt; <strong>Use beta encryption</strong>.</p>
<h2>Expanded Plugin Support<a name="expanded-plugin-support" href="#expanded-plugin-support" class="heading-anchor">🔗</a></h2>
<p>Developers will appreciate the updates to Joplin's plugin ecosystem. A new API has been introduced to create <a href="https://joplinapp.org/api/references/plugin_api/classes/joplinviewsdialogs.html#showtoast">toast notifications</a>, alongside updates to CodeMirror packages. A new <a href="https://github.com/laurent22/joplin/blob/5ee8a9a45493683c72a36b52e1460b5acdd4f1ac/packages/lib/commands/renderMarkup.ts#L23"><code>renderMarkup</code></a> command has been introduced to allow you to render Markdown content to HTML using the Joplin built-in API.</p>
<h1>Full changelogs<a name="full-changelogs" href="#full-changelogs" class="heading-anchor">🔗</a></h1>
<p>This is just an overview of the main features. The full changelogs are available there:</p>
<ul>
<li>Desktop: <a href="https://joplinapp.org/help/about/changelog/desktop">https://joplinapp.org/help/about/changelog/desktop</a></li>
<li>Android: <a href="https://joplinapp.org/help/about/changelog/android/">https://joplinapp.org/help/about/changelog/android/</a></li>
<li>iOS: <a href="https://joplinapp.org/help/about/changelog/ios/">https://joplinapp.org/help/about/changelog/ios/</a></li>
</ul>
]]></description><link>https://joplinapp.org/news/20250114-release-3-2</link><guid isPermaLink="false">20250114-release-3-2</guid><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><twitter-text>What&apos;s new in Joplin 3.2</twitter-text></item><item><title><![CDATA[Project 4: Handwritten Text Recognition (HTR) for Joplin]]></title><description><![CDATA[<p>Joplin is partnering with a French government institution to bring you innovative new features! We will work on accessibility, voice typing, HTR and add Rocketbook integration. Today we'll present the planned HTR integration:</p>
<p>Currently, Joplin’s OCR (Optical Character Recognition) feature is designed to recognise printed text, which works great for scanning documents with standard fonts. However, we’re looking to expand this functionality to support handwritten text recognition (HTR), which would be beneficial to handle scanned, handwritten documents, as well as for the upcoming Rocketbook integration.</p>
<p>Handwritten text recognition is complex task, requiring significant processing power and large machine learning models. Because of this, we plan to implement HTR via a server, possibly integrated with Joplin Cloud or Joplin Server. The beauty of this approach is that handwritten text recognition is a rapidly evolving field, so we can continuously improve the server-side model. This means that every Joplin app can benefit from these updates without needing to redeploy or update the app itself.</p>
<p>For the Rocketbook integration, this integration will make a significant difference. Right now, your handwritten documents would be scanned as images, but with HTR, Joplin will be able to recognise the actual text you’ve written. Not only will your handwritten notes become searchable, but you’ll also be able to copy and paste the text into other documents.</p>
@@ -393,15 +419,4 @@ sys 0m38.013s</p>
<p>Renovate on the other hand upgrades packages one at a time, and run our test units to ensure everything is still working as expected. It also upgrades multiple instances of the same package across the monorepo, which is convenient to keep our code consistent. It also has a number of options to make our life easier, such as the ability to automatically merge a pull request for patch releases since this is usually safe (when a package is, for example upgraded from 1.0.1 to 1.0.3).</p>
<p>Although Renovate automates the package upgrades it doesn't mean all upgrades are straightforward - our tests won't catch all issues, so the apps might end up being broken or cannot be compiled anymore. So there's manual work involved to get everything working after certain upgrades - for the most part this has been done and the apps appear to be stable so far.</p>
<p>This will however be an important part of pre-release 2.10 (or should it be 3.0?) - we hope that everything works but we may need your support to try this version and report any glitch you may have found. As always pre-release regressions have the highest priority so we aim to fix them as quickly as possible.</p>
]]></description><link>https://joplinapp.org/news/20221115-renovate</link><guid isPermaLink="false">20221115-renovate</guid><pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate><twitter-text>Modernising and securing Joplin, one package at a time</twitter-text></item><item><title><![CDATA[Joplin Cloud is now part of the Joplin company]]></title><description><![CDATA[<p>As some of you may know Joplin Cloud so far has been operating under my own single-person limited company in the UK. This was mostly for convenience since it meant I could get things going quickly without having to setup a special structure for it.</p>
<p>Now that Joplin Cloud is becoming more mature however a proper company, simply called Joplin, has been created. This company will be based in France, and will be used mainly to handle the commercial part of the project, which currently is mostly Joplin Cloud. I'm still heading the company so there won't be any major change to the way the project is managed.</p>
<h2>What does it mean for Joplin Cloud?<a name="what-does-it-mean-for-joplin-cloud" href="#what-does-it-mean-for-joplin-cloud" class="heading-anchor">🔗</a></h2>
<p>There will be no significant change - the website ownership simply moves from one company in the UK to one in France. The new company is still owned by myself so I will keep following the same roadmap.</p>
<h2>What does it mean for the open source apps?<a name="what-does-it-mean-for-the-open-source-apps" href="#what-does-it-mean-for-the-open-source-apps" class="heading-anchor">🔗</a></h2>
<p>On the short term, the only visible change will be moving the non-open source assets, such as logo or trademark from the UK company to the French one. So expect a few changes in copyright notices here and there.</p>
<p>In the medium to long term, I would like to hire one or two software developers to help me with the Joplin Cloud development, because we reached a point where managing the whole project is difficult for a single person, so some help is needed. Some of their work might also touch the open source apps since both are quite related - but of course that work will remain open source too.</p>
<p>As a general rule, there will be a permanent commitment to keep the apps open source and to derive value from Joplin Cloud/Server.</p>
<p>Longer term I would like to create a non-profit organisation to handle the open source applications and to make decisions about the project, as well as to decide how to allocate any funding we receive (for example from GSoC).</p>
<h2>Looking forward<a name="looking-forward" href="#looking-forward" class="heading-anchor">🔗</a></h2>
<p>Those past 6 years of developing Joplin have been an exciting and rewarding experience, thank you to all of you of the friendly and vibrant Joplin community for your contribution toward making Joplin the software it is today, and looking forward to continuing the journey together!</p>
]]></description><link>https://joplinapp.org/news/20221012-Joplin-Company</link><guid isPermaLink="false">20221012-Joplin-Company</guid><pubDate>Wed, 12 Oct 2022 00:00:00 GMT</pubDate><twitter-text>Joplin Cloud is now operated by the Joplin company! More info on the announcement post.</twitter-text></item></channel></rss>
]]></description><link>https://joplinapp.org/news/20221115-renovate</link><guid isPermaLink="false">20221115-renovate</guid><pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate><twitter-text>Modernising and securing Joplin, one package at a time</twitter-text></item></channel></rss>

View File

@@ -1,7 +1,6 @@
<div class="row">
<div class="col-12 col-md-12 social-links">
<a class="social-link-bluesky" href="https://bsky.app/profile/joplinapp.bsky.social" title="Joplin Bluesky feed"><i class="fa-brands fa-bluesky"></i></a>
<a class="social-link-twitter" href="https://twitter.com/joplinapp" title="Joplin Twitter feed"><i class="fab fa-twitter"></i></a>
<a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
<a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
<a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>

View File

@@ -1,3 +1 @@
<!-- <a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500 twitter-link"><i class="fab fa-twitter"></i></a> -->
<a href="https://bsky.app/profile/joplinapp.bsky.social" title="Joplin Bluesky feed" class="fw500 twitter-link"><i class="fa-brands fa-bluesky"></i></a>

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://buyyoutubviews.com"><img title="BYTV" width="256" src="https://joplinapp.org/images/sponsors/BYTV.png"/></a> <a href="https://casinoreviews.net"><img title="Casino Reviews" width="256" src="https://joplinapp.org/images/sponsors/CasinoReviews.png"/></a> <a href="https://useviral.com.br/"><img title="Comprar seguidores Instagram" width="256" src="https://joplinapp.org/images/sponsors/Useviral.png"/></a> <a href="https://ca.edubirdie.com/"><img title="Achieve academic success with Edubirdie — your trusted partner for expert writing assistance and resources!" width="256" src="https://joplinapp.org/images/sponsors/Edubirdie.png" alt="EduBirdie"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://buyyoutubviews.com"><img title="BYTV" width="256" src="https://joplinapp.org/images/sponsors/BYTV.png"/></a> <a href="https://casinoreviews.net"><img title="Casino Reviews" width="256" src="https://joplinapp.org/images/sponsors/CasinoReviews.png"/></a> <a href="https://useviral.com.br/"><img title="Comprar seguidores Instagram" width="256" src="https://joplinapp.org/images/sponsors/Useviral.png"/></a> <a href="https://ca.edubirdie.com/"><img title="Achieve academic success with Edubirdie — your trusted partner for expert writing assistance and resources!" width="256" src="https://joplinapp.org/images/sponsors/Edubirdie.png" alt="EduBirdie"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="web design agency"/></a> <a href="https://realgambling.ca/"><img title="RealGambling.ca" width="256" src="https://joplinapp.org/images/sponsors/RealGambling.png" alt="RealGambling.ca"/></a> <a href="https://essaypro.com/"><img title="write an essay online with EssayPro" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="write an essay online with EssayPro"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a>
<!-- SPONSORS-ORG -->
* * *
@@ -51,7 +51,6 @@ Name | Description
--- | ---
[Support Forum](https://discourse.joplinapp.org/) | This is the main place for general discussion about Joplin, user support, software development questions, and to discuss new features. Also where the latest beta versions are released and discussed.
[Bluesky feed](https://bsky.app/profile/joplinapp.bsky.social) | Follow us on Bluesky
[Twitter feed](https://twitter.com/joplinapp) | Follow us on Twitter
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
[Discord server](https://discord.gg/VSj7AFHvpq) | Our chat server

View File

@@ -114,6 +114,7 @@
"react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch",
"rn-fetch-blob@0.12.0": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch",
"app-builder-lib@26.0.0-alpha.7": "patch:app-builder-lib@npm%3A26.0.0-alpha.7#./.yarn/patches/app-builder-lib-npm-26.0.0-alpha.7-e1b3dca119.patch",
"app-builder-lib@24.13.3": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch"
"app-builder-lib@24.13.3": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch",
"react-native-sqlite-storage@6.0.1": "patch:react-native-sqlite-storage@npm%3A6.0.1#./.yarn/patches/react-native-sqlite-storage-npm-6.0.1-8369d747bd.patch"
}
}

View File

@@ -35,15 +35,15 @@
],
"owner": "Laurent Cozic"
},
"version": "3.2.3",
"version": "3.3.0",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@joplin/lib": "~3.2",
"@joplin/renderer": "~3.2",
"@joplin/utils": "~3.2",
"@joplin/lib": "~3.3",
"@joplin/renderer": "~3.3",
"@joplin/utils": "~3.3",
"aws-sdk": "2.1340.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",
@@ -69,10 +69,10 @@
"yargs-parser": "21.1.1"
},
"devDependencies": {
"@joplin/tools": "~3.2",
"@joplin/tools": "~3.3",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.12",
"@types/node": "18.19.55",
"@types/node": "18.19.64",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",

View File

@@ -1 +1 @@
Should keep this comment: <!-- keep this &amp; that -->
Should keep this comment: <!-- keep this & that -->

View File

@@ -1,2 +1,3 @@
<img src="test/" class="jop-noMdConv"/>
<img src="http://example.com/test.png" class="jop-noMdConv"/>
<img src="test/" id="getElementById" class="jop-noMdConv"/>
<img src="http://example.com/test.png" id="getElementById" class="jop-noMdConv"/>
<img id="test2" src="http://example.com/test.png" class="jop-noMdConv"/>

View File

@@ -1,3 +1,5 @@
<img name=getElementById src=test/>
<IMG NAME="getElementById" SRC="http://example.com/test.png">
<IMG NAME="getElementById" SRC="http://example.com/test.png">
<IMG NAME="test" ID="test2" SRC="http://example.com/test.png">

View File

@@ -0,0 +1 @@
<math class="jop-noMdConv"><p class="jop-noMdConv"><style class="jop-noMdConv"><!--&lt;/style&gt;&lt;img src onerror=alert(1)&gt;--></style>

View File

@@ -0,0 +1 @@
<math><p><style><!--</style><img src onerror=alert(1)>--></style>

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Joplin Web Clipper [DEV]",
"version": "3.2.0",
"version": "3.3.0",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": {

View File

@@ -23,7 +23,7 @@
"react-redux": "9.0.4",
"redux": "5.0.1",
"style-loader": "3.3.3",
"webpack": "5.89.0",
"webpack": "5.97.1",
"webpack-cli": "5.1.4"
}
},
@@ -1928,10 +1928,11 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
@@ -1961,148 +1962,163 @@
"dev": true
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
"integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
"dev": true
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
"dev": true
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
"dev": true
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"dev": true
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
"integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/wasm-gen": "1.14.1"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
"dev": true
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/helper-wasm-section": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-opt": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6",
"@webassemblyjs/wast-printer": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/helper-wasm-section": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-opt": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1",
"@webassemblyjs/wast-printer": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
"integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
"integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
"integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
"integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
}
},
@@ -2154,19 +2170,22 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
"dev": true,
"license": "Apache-2.0"
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -2174,15 +2193,6 @@
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-assertions": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -2393,9 +2403,9 @@
"dev": true
},
"node_modules/browserslist": {
"version": "4.22.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
"dev": true,
"funding": [
{
@@ -2411,11 +2421,12 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001565",
"electron-to-chromium": "^1.4.601",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.1"
},
"bin": {
"browserslist": "cli.js"
@@ -2441,9 +2452,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001574",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001574.tgz",
"integrity": "sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg==",
"version": "1.0.30001692",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
"integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
"dev": true,
"funding": [
{
@@ -2458,7 +2469,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "2.4.2",
@@ -2736,16 +2748,18 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.623",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz",
"integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==",
"dev": true
"version": "1.5.83",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.83.tgz",
"integrity": "sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==",
"dev": true,
"license": "ISC"
},
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
"integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -2782,10 +2796,11 @@
"dev": true
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
@@ -3017,7 +3032,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/globals": {
"version": "11.12.0",
@@ -3433,10 +3449,11 @@
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
"license": "MIT"
},
"node_modules/nth-check": {
"version": "2.1.1",
@@ -3504,10 +3521,11 @@
"dev": true
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/pkg-dir": {
"version": "4.2.0",
@@ -4254,9 +4272,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
"dev": true,
"funding": [
{
@@ -4272,9 +4290,10 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -4314,10 +4333,11 @@
"dev": true
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -4327,34 +4347,34 @@
}
},
"node_modules/webpack": {
"version": "5.89.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
"integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"version": "5.97.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
"@webassemblyjs/ast": "^1.11.5",
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.14.0",
"browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.15.0",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9",
"graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
"terser-webpack-plugin": "^5.3.10",
"watchpack": "^2.4.1",
"webpack-sources": "^3.2.3"
},
"bin": {
@@ -5830,9 +5850,9 @@
}
},
"@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true
},
"@types/html-minifier-terser": {
@@ -5863,148 +5883,148 @@
"dev": true
},
"@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
"integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"dev": true,
"requires": {
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
}
},
"@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"dev": true
},
"@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"dev": true
},
"@webassemblyjs/helper-buffer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"dev": true
},
"@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"dev": true,
"requires": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
"@xtuc/long": "4.2.2"
}
},
"@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"dev": true
},
"@webassemblyjs/helper-wasm-section": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
"integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/wasm-gen": "1.14.1"
}
},
"@webassemblyjs/ieee754": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"dev": true,
"requires": {
"@xtuc/ieee754": "^1.2.0"
}
},
"@webassemblyjs/leb128": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"dev": true,
"requires": {
"@xtuc/long": "4.2.2"
}
},
"@webassemblyjs/utf8": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"dev": true
},
"@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/helper-wasm-section": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-opt": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6",
"@webassemblyjs/wast-printer": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/helper-wasm-section": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-opt": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1",
"@webassemblyjs/wast-printer": "1.14.1"
}
},
"@webassemblyjs/wasm-gen": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
"integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"@webassemblyjs/wasm-opt": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
"integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1"
}
},
"@webassemblyjs/wasm-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
"integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"@webassemblyjs/wast-printer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
"integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
}
},
@@ -6042,18 +6062,11 @@
"dev": true
},
"acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true
},
"acorn-import-assertions": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"requires": {}
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -6213,15 +6226,15 @@
"dev": true
},
"browserslist": {
"version": "4.22.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001565",
"electron-to-chromium": "^1.4.601",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.1"
}
},
"buffer-from": {
@@ -6241,9 +6254,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001574",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001574.tgz",
"integrity": "sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg==",
"version": "1.0.30001692",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
"integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
"dev": true
},
"chalk": {
@@ -6449,15 +6462,15 @@
}
},
"electron-to-chromium": {
"version": "1.4.623",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz",
"integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==",
"version": "1.5.83",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.83.tgz",
"integrity": "sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==",
"dev": true
},
"enhanced-resolve": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
"integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
@@ -6483,9 +6496,9 @@
"dev": true
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true
},
"escape-string-regexp": {
@@ -6937,9 +6950,9 @@
}
},
"node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true
},
"nth-check": {
@@ -6996,9 +7009,9 @@
"dev": true
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"pkg-dir": {
@@ -7520,13 +7533,13 @@
"dev": true
},
"update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
"dev": true,
"requires": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
}
},
"uri-js": {
@@ -7558,9 +7571,9 @@
"dev": true
},
"watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
"requires": {
"glob-to-regexp": "^0.4.1",
@@ -7568,34 +7581,33 @@
}
},
"webpack": {
"version": "5.89.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
"integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"version": "5.97.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
"@webassemblyjs/ast": "^1.11.5",
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.14.0",
"browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.15.0",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9",
"graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
"terser-webpack-plugin": "^5.3.10",
"watchpack": "^2.4.1",
"webpack-sources": "^3.2.3"
},
"dependencies": {

View File

@@ -23,7 +23,7 @@
"react-redux": "9.0.4",
"redux": "5.0.1",
"style-loader": "3.3.3",
"webpack": "5.89.0",
"webpack": "5.97.1",
"webpack-cli": "5.1.4"
},
"browserslist": [

View File

@@ -19,7 +19,6 @@ 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 stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
import ResourceService from '@joplin/lib/services/ResourceService';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import appReducer, { createAppDefaultState } from './app.reducer';
@@ -35,29 +34,15 @@ const PluginManager = require('@joplin/lib/services/PluginManager');
import RevisionService from '@joplin/lib/services/RevisionService';
import MigrationService from '@joplin/lib/services/MigrationService';
import { loadCustomCss } from '@joplin/lib/CssUtils';
import mainScreenCommands from './gui/WindowCommandsAndDialogs/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 { homedir } from 'os';
import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo';
const electronContextMenu = require('./services/electron-context-menu');
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
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 = appCommands.concat(libCommands);
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService';
import ShareService from '@joplin/lib/services/share/ShareService';
import checkForUpdates from './checkForUpdates';
@@ -74,6 +59,7 @@ import SearchEngine from '@joplin/lib/services/search/SearchEngine';
import { PackageInfo } from '@joplin/lib/versionInfo';
import { CustomProtocolHandler } from './utils/customProtocols/handleCustomProtocols';
import { refreshFolders } from '@joplin/lib/folders-screen-utils';
import initializeCommandService from './utils/initializeCommandService';
const pluginClasses = [
require('./plugins/GotoAnything').default,
@@ -492,20 +478,7 @@ class Application extends BaseApplication {
PerFolderSortOrderService.initialize();
CommandService.instance().initialize(this.store(), Setting.value('env') === 'dev', stateToWhenClauseContext);
for (const command of commands) {
CommandService.instance().registerDeclaration(command.declaration);
}
for (const command of globalCommands) {
CommandService.instance().registerDeclaration(command.declaration);
CommandService.instance().registerRuntime(command.declaration.name, command.runtime());
}
for (const declaration of editorCommandDeclarations) {
CommandService.instance().registerDeclaration(declaration);
}
initializeCommandService(this.store(), Setting.value('env') === 'dev');
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
@@ -583,6 +556,12 @@ class Application extends BaseApplication {
value: Setting.value('flagOpenDevTools'),
});
// Always disable on Mac for now - and disable too for the few apps that may have the flag enabled.
// At present, it only seems to work on Windows.
if (shim.isMac()) {
Setting.setValue('featureFlag.autoUpdaterServiceEnabled', false);
}
// Note: Auto-update is a misnomer in the code.
// The code below only checks, if a new version is available.
// We only allow Windows and macOS users to automatically check for updates

View File

@@ -16,6 +16,7 @@ export const runtime = (): CommandRuntime => {
if (target === 'noteList') return CommandService.instance().execute('focusElementNoteList');
if (target === 'sideBar') return CommandService.instance().execute('focusElementSideBar');
if (target === 'noteTitle') return CommandService.instance().execute('focusElementNoteTitle', options);
if (target === 'toolbar') return CommandService.instance().execute('focusElementToolbar', options);
throw new Error(`Invalid focus target: ${target}`);
},
};

View File

@@ -18,6 +18,7 @@ import * as switchProfile2 from './switchProfile2';
import * as switchProfile3 from './switchProfile3';
import * as toggleExternalEditing from './toggleExternalEditing';
import * as toggleSafeMode from './toggleSafeMode';
import * as toggleTabMovesFocus from './toggleTabMovesFocus';
const index: any[] = [
copyDevCommand,
@@ -39,6 +40,7 @@ const index: any[] = [
switchProfile3,
toggleExternalEditing,
toggleSafeMode,
toggleTabMovesFocus,
];
export default index;

View File

@@ -7,7 +7,7 @@ import Setting from '@joplin/lib/models/Setting';
export const declaration: CommandDeclaration = {
name: 'openNoteInNewWindow',
label: () => _('Edit in new window'),
label: () => _('Open in new window'),
iconName: 'icon-share',
};

View File

@@ -7,7 +7,7 @@ const bridge = require('@electron/remote').require('./bridge').default;
export const declaration: CommandDeclaration = {
name: 'startExternalEditing',
label: () => _('Edit in external editor'),
label: () => _('Open in external editor'),
iconName: 'icon-share',
};

View File

@@ -0,0 +1,20 @@
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { DesktopCommandContext } from '../services/commands/types';
import Setting from '@joplin/lib/models/Setting';
export const declaration: CommandDeclaration = {
name: 'toggleTabMovesFocus',
label: () => _('Toggle editor tab key navigation'),
iconName: 'fas fa-keyboard',
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: DesktopCommandContext, enabled: boolean = null) => {
const newValue = enabled ?? !Setting.value('editor.tabMovesFocus');
Setting.setValue('editor.tabMovesFocus', newValue);
},
enabledCondition: 'oneNoteSelected',
};
};

View File

@@ -18,27 +18,21 @@ export enum ButtonSize {
Normal = 2,
}
interface Props {
type ReactButtonProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
interface Props extends Omit<ReactButtonProps, 'onClick'> {
title?: string;
iconName?: string;
level?: ButtonLevel;
iconLabel?: string;
className?: string;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onClick?: Function;
onClick?: ()=> void;
color?: string;
iconAnimation?: string;
tooltip?: string;
disabled?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
style?: any;
size?: ButtonSize;
isSquare?: boolean;
iconOnly?: boolean;
fontSize?: number;
'aria-controls'?: string;
'aria-expanded'?: string;
}
const StyledTitle = styled.span`
@@ -215,54 +209,52 @@ function buttonClass(level: ButtonLevel) {
return StyledButtonSecondary;
}
const Button = React.forwardRef(({
iconName, iconLabel, iconAnimation, color, title, level, fontSize, isSquare, tooltip, disabled, onClick: propsOnClick, ...unusedProps
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
const Button = React.forwardRef((props: Props, ref: any) => {
const iconOnly = props.iconName && !props.title;
}: Props, ref: any) => {
const iconOnly = iconName && !title;
const StyledButton = buttonClass(props.level);
const StyledButton = buttonClass(level);
function renderIcon() {
if (!props.iconName) return null;
if (!iconName) return null;
return <StyledIcon
aria-label={props.iconLabel ?? undefined}
aria-hidden={!props.iconLabel}
animation={props.iconAnimation}
aria-label={iconLabel ?? undefined}
aria-hidden={!iconLabel}
animation={iconAnimation}
mr={iconOnly ? '0' : '6px'}
color={props.color}
className={props.iconName}
color={color}
className={iconName}
role='img'
/>;
}
function renderTitle() {
if (!props.title) return null;
return <StyledTitle color={props.color}>{props.title}</StyledTitle>;
if (!title) return null;
return <StyledTitle color={color}>{title}</StyledTitle>;
}
function onClick() {
if (props.disabled) return;
props.onClick();
if (disabled) return;
propsOnClick();
}
return (
<StyledButton
ref={ref}
fontSize={props.fontSize}
isSquare={props.isSquare}
size={props.size}
style={props.style}
disabled={props.disabled}
title={props.tooltip}
className={props.className}
fontSize={fontSize}
isSquare={isSquare}
disabled={disabled}
title={tooltip}
iconOnly={iconOnly}
onClick={onClick}
// When there's no title, the button needs a label. In this case, fall back
// to the tooltip.
aria-label={props.title ? undefined : props.tooltip}
aria-disabled={props.disabled}
aria-expanded={props['aria-expanded']}
aria-controls={props['aria-controls']}
aria-label={title ? undefined : tooltip}
aria-disabled={disabled}
{...unusedProps}
>
{renderIcon()}
{renderTitle()}

View File

@@ -17,7 +17,7 @@ interface Props {
onApplyClick?: Function;
}
export const StyledRoot = styled.div`
const StyledRoot = styled.nav`
display: flex;
align-items: center;
padding: 10px;

View File

@@ -164,7 +164,7 @@ export default function Sidebar(props: Props) {
}
return (
<StyledRoot role='tablist'>
<StyledRoot className='settings-sidebar _scrollbar2' role='tablist'>
{buttons}
</StyledRoot>
);

View File

@@ -1,8 +1,8 @@
import React = require('react');
import { useMemo, useState, useCallback, CSSProperties, useEffect, useRef } from 'react';
import * as React from 'react';
import { useState, useCallback, CSSProperties, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import { SettingItemSubType } from '@joplin/lib/models/Setting';
import { focus } from '@joplin/lib/utils/focusHandler';
import InlineCombobox from '../../InlineCombobox';
interface Props {
type: string;
@@ -17,14 +17,8 @@ interface Props {
const FontSearch = (props: Props) => {
const { type, style, value, availableFonts, onChange, subtype } = props;
const [filteredAvailableFonts, setFilteredAvailableFonts] = useState(availableFonts);
const [inputText, setInputText] = useState(value);
const [showList, setShowList] = useState(false);
const [isListHovered, setIsListHovered] = useState(false);
const [isFontSelected, setIsFontSelected] = useState(value !== '');
const [visibleFonts, setVisibleFonts] = useState<string[]>([]);
const [isMonoBoxChecked, setIsMonoBoxChecked] = useState(false);
const isLoadingFonts = filteredAvailableFonts.length === 0;
const fontInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (subtype === SettingItemSubType.MonospaceFontFamily) {
@@ -41,112 +35,34 @@ const FontSearch = (props: Props) => {
setFilteredAvailableFonts(localMonospacedFonts);
}, [isMonoBoxChecked, availableFonts]);
const displayedFonts = useMemo(() => {
if (isFontSelected) return filteredAvailableFonts;
return filteredAvailableFonts.filter((font: string) =>
font.toLowerCase().startsWith(inputText.toLowerCase()),
);
}, [filteredAvailableFonts, inputText, isFontSelected]);
useEffect(() => {
setVisibleFonts(displayedFonts.slice(0, 20));
}, [displayedFonts]);
// Lazy loading
const handleListScroll: React.UIEventHandler<HTMLDivElement> = useCallback((event) => {
const scrollTop = (event.target as HTMLDivElement).scrollTop;
const scrollHeight = (event.target as HTMLDivElement).scrollHeight;
const clientHeight = (event.target as HTMLDivElement).clientHeight;
// Check if the user has scrolled to the bottom of the container
// A small buffer of 20 pixels is subtracted from the total scrollHeight to ensure new content starts loading slightly before the user reaches the absolute bottom, providing a smoother experience.
if (scrollTop + clientHeight >= scrollHeight - 20) {
// Load the next 20 fonts
const remainingFonts = displayedFonts.slice(visibleFonts.length, visibleFonts.length + 20);
setVisibleFonts([...visibleFonts, ...remainingFonts]);
}
}, [displayedFonts, visibleFonts]);
const handleTextChange: React.ChangeEventHandler<HTMLInputElement> = useCallback((event) => {
setIsFontSelected(false);
setInputText(event.target.value);
onChange(event.target.value);
}, [onChange]);
const handleFocus: React.FocusEventHandler<HTMLInputElement> = useCallback(() => setShowList(true), []);
const handleBlur: React.FocusEventHandler<HTMLInputElement> = useCallback(() => {
if (!isListHovered) {
setShowList(false);
}
}, [isListHovered]);
const handleFontClick: React.MouseEventHandler<HTMLDivElement> = useCallback((event) => {
const font = (event.target as HTMLDivElement).innerText;
setInputText(font);
setShowList(false);
onChange(font);
setIsFontSelected(true);
}, [onChange]);
const handleListHover: React.MouseEventHandler<HTMLDivElement> = useCallback(() => setIsListHovered(true), []);
const handleListLeave: React.MouseEventHandler<HTMLDivElement> = useCallback(() => setIsListHovered(false), []);
const handleMonoBoxCheck: React.ChangeEventHandler<HTMLInputElement> = useCallback(() => {
setIsMonoBoxChecked(!isMonoBoxChecked);
focus('FontSearch::fontInputRef', fontInputRef.current);
}, [isMonoBoxChecked]);
return (
<>
const comboboxControls = <>
{isLoadingFonts ? _('Loading...') : null}
<div className='monospace-checkbox'>
<input
type={type}
style={style}
value={inputText}
onChange={handleTextChange}
onFocus={handleFocus}
onBlur={handleBlur}
spellCheck={false}
id={props.inputId}
ref={fontInputRef}
type='checkbox'
checked={isMonoBoxChecked}
onChange={handleMonoBoxCheck}
id={`show-monospace-fonts_${subtype}`}
/>
<div
className={'font-search-list'}
style={{ display: showList ? 'block' : 'none' }}
onMouseEnter={handleListHover}
onMouseLeave={handleListLeave}
onScroll={handleListScroll}
>
{
isLoadingFonts ? <div>{_('Loading...')}</div> :
<>
<div className='monospace-checkbox'>
<input
type='checkbox'
checked={isMonoBoxChecked}
onChange={handleMonoBoxCheck}
id={`show-monospace-fonts_${subtype}`}
/>
<label htmlFor={`show-monospace-fonts_${subtype}`}>{_('Show monospace fonts only.')}</label>
</div>
{
visibleFonts.map((font: string) =>
<div
key={font}
style={{ fontFamily: `"${font}"` }}
onClick={handleFontClick}
className='font-search-item'
>
{font}
</div>,
)
}
</>
}
</div>
</>
<label htmlFor={`show-monospace-fonts_${subtype}`}>{_('Show monospace fonts only.')}</label>
</div>
</>;
return (
<InlineCombobox
inputType={type}
inputStyle={style}
value={value}
suggestedValues={filteredAvailableFonts}
renderOption={font => <span style={{ fontFamily: font }}>{font}</span>}
controls={comboboxControls}
onChange={onChange}
inputId={props.inputId}
/>
);
};

View File

@@ -1,4 +1,3 @@
@use "./setting-description.scss";
@use "./setting-label.scss";
@use "./setting-header.scss";

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const Root = styled.div<any>`
const Root = styled.h1<any>`
display: flex;
justify-content: ${props => props.justifyContent ? props.justifyContent : 'center'};
font-family: ${props => props.theme.fontFamily};

View File

@@ -0,0 +1,199 @@
import * as React from 'react';
import { useState, useCallback, CSSProperties, useEffect, useRef, useId } from 'react';
import { _ } from '@joplin/lib/locale';
import { focus } from '@joplin/lib/utils/focusHandler';
import ItemList from './ItemList';
interface Props {
inputType?: string;
inputStyle: CSSProperties;
value: string;
onChange: (newValue: string)=> void;
suggestedValues: string[];
renderOption: (suggestedValue: string)=> React.ReactElement;
controls?: React.ReactNode;
inputId: string;
}
const suggestionMatchesFilter = (suggestion: string, filter: string) => {
return suggestion.toLowerCase().startsWith(filter.toLowerCase());
};
const InlineCombobox: React.FC<Props> = ({ inputType, controls, inputStyle, value, suggestedValues, renderOption, onChange, inputId }) => {
const [showList, setShowList] = useState(false);
const containerRef = useRef<HTMLDivElement|null>(null);
const inputRef = useRef<HTMLInputElement|null>(null);
const listboxRef = useRef<ItemList<string>|null>(null);
const [filteredSuggestions, setFilteredSuggestions] = useState(suggestedValues);
useEffect(() => {
setFilteredSuggestions(suggestedValues);
}, [suggestedValues]);
const selectedIndex = filteredSuggestions.indexOf(value);
useEffect(() => {
if (selectedIndex >= 0 && showList) {
listboxRef.current?.makeItemIndexVisible(selectedIndex);
}
}, [selectedIndex, showList]);
const focusInput = useCallback(() => {
focus('ComboBox/focus input', inputRef.current);
}, []);
const onTextChange: React.ChangeEventHandler<HTMLInputElement> = useCallback((event) => {
const newValue = event.target.value;
onChange(newValue);
setShowList(true);
const filteredSuggestions = suggestedValues.filter((suggestion: string) =>
suggestionMatchesFilter(suggestion, newValue),
);
// If no suggestions, show all fonts
setFilteredSuggestions(filteredSuggestions.length > 0 ? filteredSuggestions : suggestedValues);
}, [onChange, suggestedValues]);
const onFocus: React.FocusEventHandler<HTMLElement> = useCallback(() => {
setShowList(true);
}, []);
const onBlur = useCallback((event: React.FocusEvent) => {
const hasHoverOrFocus = !!containerRef.current.querySelector(':focus-within, :hover');
const movesToContainedItem = containerRef.current.contains(event.relatedTarget);
if (!hasHoverOrFocus && !movesToContainedItem) {
setShowList(false);
}
}, []);
const onItemClick: React.MouseEventHandler<HTMLDivElement> = useCallback((event) => {
const newValue = event.currentTarget.getAttribute('data-key');
if (!newValue) return;
focusInput();
onChange(newValue);
setFilteredSuggestions(suggestedValues);
setShowList(false);
}, [onChange, suggestedValues, focusInput]);
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = useCallback(event => {
if (event.nativeEvent.isComposing) return;
let closestIndex = selectedIndex;
if (selectedIndex === -1) {
closestIndex = filteredSuggestions.findIndex(suggestion => {
return suggestionMatchesFilter(suggestion, value);
});
}
const isGoToNext = event.code === 'ArrowDown';
if (isGoToNext || event.code === 'ArrowUp') {
event.preventDefault();
if (!event.altKey) {
let newSelectedIndex;
if (isGoToNext) {
newSelectedIndex = (selectedIndex + 1) % filteredSuggestions.length;
} else {
newSelectedIndex = selectedIndex - 1;
if (newSelectedIndex < 0) {
newSelectedIndex += filteredSuggestions.length;
}
}
const newKey = filteredSuggestions[newSelectedIndex];
onChange(newKey);
}
setShowList(true);
} else if (event.code === 'Enter') {
event.preventDefault();
onChange(filteredSuggestions[closestIndex]);
setShowList(false);
} else if (event.code === 'Escape') {
event.preventDefault();
setShowList(false);
}
}, [filteredSuggestions, value, selectedIndex, onChange]);
const valuesListId = useId();
const itemId = (index: number) => {
if (index < 0) {
return undefined;
} else {
return `combobox-${valuesListId}-option-${index}`;
}
};
const onRenderItem = (key: string, index: number) => {
const selected = key === value;
const id = itemId(index);
return (
<div
key={key}
data-key={key}
className={`combobox-suggestion-option ${selected ? '-selected' : ''}`}
role='option'
aria-posinset={1 + index}
aria-setsize={filteredSuggestions.length}
onClick={onItemClick}
aria-selected={selected}
id={id}
>{renderOption(key)}</div>
);
};
return (
<div
className={`combobox-wrapper ${showList ? '-expanded' : ''}`}
onFocus={onFocus}
onBlur={onBlur}
onKeyDown={onKeyDown}
ref={containerRef}
>
<input
type={inputType ?? 'text'}
style={inputStyle}
value={value}
onChange={onTextChange}
onKeyDown={onKeyDown}
spellCheck={false}
id={inputId}
ref={inputRef}
role='combobox'
aria-autocomplete='list'
aria-controls={valuesListId}
aria-expanded={showList}
aria-activedescendant={itemId(selectedIndex)}
/>
<div className='suggestions'>
{
// Custom controls
controls
}
<ItemList
role='listbox'
aria-label={_('Suggestions')}
style={{ height: 200 }}
itemHeight={26}
alwaysRenderSelection={true}
selectedIndex={selectedIndex >= 0 ? selectedIndex : undefined}
items={filteredSuggestions}
itemRenderer={onRenderItem}
id={valuesListId}
ref={listboxRef}
/>
</div>
</div>
);
};
export default InlineCombobox;

View File

@@ -43,6 +43,7 @@ import UpdateNotification from './UpdateNotification/UpdateNotification';
import NoteEditor from './NoteEditor/NoteEditor';
import PluginNotification from './PluginNotification/PluginNotification';
import { Toast } from '@joplin/lib/services/plugins/api/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const ipcRenderer = require('electron').ipcRenderer;
@@ -121,6 +122,18 @@ const defaultLayout: LayoutItem = {
],
};
const layoutKeyToLabel = (key: string, plugins: PluginStates) => {
if (key === 'sideBar') return _('Sidebar');
if (key === 'noteList') return _('Note list');
if (key === 'editor') return _('Editor');
const viewInfo = pluginUtils.viewInfoByViewId(plugins, key);
if (viewInfo) {
return PluginService.instance().safePluginNameById(viewInfo.plugin.id);
}
return key;
};
class MainScreenComponent extends React.Component<Props, State> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -567,7 +580,14 @@ class MainScreenComponent extends React.Component<Props, State> {
return (
<div style={styles.messageBox}>
<span style={theme.textStyle}>{msg}</span>
<span
style={theme.textStyle}
role='alert'
// role='alert' has an implicit aria-live='assertive', which tells screen readers that changes
// to the warning's content should be announced as soon as possible. However, since it's generally
// okay for announcements related to these notifications to be delayed, use aria-live='polite'.
aria-live='polite'
>{msg}</span>
</div>
);
}
@@ -728,6 +748,10 @@ class MainScreenComponent extends React.Component<Props, State> {
);
}
private layoutKeyToLabel = (key: string) => {
return layoutKeyToLabel(key, this.props.plugins);
};
public render() {
const theme = themeStyle(this.props.themeId);
const style = {
@@ -746,6 +770,7 @@ class MainScreenComponent extends React.Component<Props, State> {
onResize={this.resizableLayout_resize}
onMoveButtonClick={this.resizableLayout_moveButtonClick}
renderItem={this.resizableLayout_renderItem}
layoutKeyToLabel={this.layoutKeyToLabel}
moveMode={this.props.layoutMoveMode}
moveModeMessage={_('Use the arrows to move the layout items. Press "Escape" to exit.')}
/>

View File

@@ -165,6 +165,7 @@ interface Props {
showNoteCounts: boolean;
uncompletedTodosOnTop: boolean;
showCompletedTodos: boolean;
tabMovesFocus: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
pluginMenuItems: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -256,6 +257,7 @@ function useMenuStates(menu: any, props: Props) {
menuItemSetChecked('showNoteCounts', props.showNoteCounts);
menuItemSetChecked('uncompletedTodosOnTop', props.uncompletedTodosOnTop);
menuItemSetChecked('showCompletedTodos', props.showCompletedTodos);
menuItemSetChecked('toggleTabMovesFocus', props.tabMovesFocus);
}
timeoutId = setTimeout(scheduleUpdate, 150);
@@ -276,6 +278,7 @@ function useMenuStates(menu: any, props: Props) {
props['notes.sortOrder.reverse'],
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
props['folders.sortOrder.reverse'],
props.tabMovesFocus,
props.noteListRendererId,
props.showNoteCounts,
props.uncompletedTodosOnTop,
@@ -476,6 +479,7 @@ function useMenu(props: Props) {
menuItemDic.focusElementNoteList,
menuItemDic.focusElementNoteTitle,
menuItemDic.focusElementNoteBody,
menuItemDic.focusElementToolbar,
];
const importItems = [];
@@ -824,6 +828,12 @@ function useMenu(props: Props) {
},
},
separator(),
{
...menuItemDic['toggleTabMovesFocus'],
label: Setting.settingMetadata('editor.tabMovesFocus').label(),
type: 'checkbox',
},
separator(),
{
label: _('Actual Size'),
click: () => {
@@ -877,7 +887,9 @@ function useMenu(props: Props) {
note: {
label: _('&Note'),
submenu: [
menuItemDic.openNoteInNewWindow,
menuItemDic.toggleExternalEditing,
separator(),
menuItemDic.setTags,
menuItemDic.showShareNoteDialog,
separator(),
@@ -1143,6 +1155,7 @@ const mapStateToProps = (state: AppState): Partial<Props> => {
['folders.sortOrder.field']: state.settings['folders.sortOrder.field'],
['notes.sortOrder.reverse']: state.settings['notes.sortOrder.reverse'],
['folders.sortOrder.reverse']: state.settings['folders.sortOrder.reverse'],
tabMovesFocus: state.settings['editor.tabMovesFocus'],
pluginSettings: state.settings['plugins.states'],
showNoteCounts: state.settings.showNoteCounts,
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,

View File

@@ -32,6 +32,14 @@ function styles_(props: MultiNoteActionsProps) {
display: 'flex',
flexDirection: 'column',
},
divider: {
borderTopWidth: 1,
borderTopStyle: 'solid',
borderTopColor: theme.dividerColor,
width: '100%',
height: 1,
marginBottom: 10,
},
button: {
...theme.buttonStyle,
marginBottom: 10,
@@ -68,11 +76,17 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) {
const item = menuItems[i];
if (!item.enabled) continue;
itemComps.push(
<button key={item.label} style={styles.button} onClick={() => multiNotesButton_click(item)}>
{item.label}
</button>,
);
if (item.type === 'separator') {
itemComps.push(
<div key={`divider${i}`} style={styles.divider}/>,
);
} else {
itemComps.push(
<button key={item.label} style={styles.button} onClick={() => multiNotesButton_click(item)}>
{item.label}
</button>,
);
}
}
return (

View File

@@ -1,50 +1,66 @@
import * as React from 'react';
const { connect } = require('react-redux');
import { connect } from 'react-redux';
import Setting from '@joplin/lib/models/Setting';
import { AppState, AppStateRoute } from '../app.reducer';
import bridge from '../services/bridge';
import { useContext, useEffect, useRef } from 'react';
import { useContext, useEffect, useMemo, useRef } from 'react';
import { WindowIdContext } from './NewWindowOrIFrame';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactor of code from before rule was applied
type ScreenProps = any;
interface AppScreen {
screen: React.ComponentType<ScreenProps>;
title?: ()=> string;
}
interface Props {
route: AppStateRoute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
screens: Record<string, any>;
screens: Record<string, AppScreen>;
style: React.CSSProperties;
className?: string;
}
const NavigatorComponent: React.FC<Props> = props => {
const useWindowTitleManager = (screenInfo: AppScreen) => {
const windowTitle = useMemo(() => {
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
const windowTitle = [`Joplin${devMarker}`];
if (screenInfo?.title) {
windowTitle.push(screenInfo.title());
}
return windowTitle.join(' - ');
}, [screenInfo]);
const windowId = useContext(WindowIdContext);
useEffect(() => {
bridge().windowById(windowId)?.setTitle(windowTitle);
}, [windowTitle, windowId]);
};
const useWindowRefocusManager = (route: AppStateRoute) => {
const windowId = useContext(WindowIdContext);
const route = props.route;
const screenInfo = props.screens[route?.routeName];
const screensRef = useRef(props.screens);
screensRef.current = props.screens;
const prevRoute = useRef<AppStateRoute|null>(null);
const prevRouteName = useRef<string|null>(null);
const routeName = route?.routeName;
useEffect(() => {
const routeName = route?.routeName;
if (route) {
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
const windowTitle = [`Joplin${devMarker}`];
if (screenInfo.title) {
windowTitle.push(screenInfo.title());
}
bridge().windowById(windowId)?.setTitle(windowTitle.join(' - '));
}
// When a navigation happens in an unfocused window, show the window to the user.
// This might happen if, for example, a secondary window triggers a navigation in
// the main window.
if (routeName && routeName !== prevRoute.current?.routeName) {
if (routeName && routeName !== prevRouteName.current) {
bridge().switchToWindow(windowId);
}
prevRoute.current = route;
}, [route, screenInfo, windowId]);
prevRouteName.current = routeName;
}, [routeName, windowId]);
};
const NavigatorComponent: React.FC<Props> = props => {
const route = props.route;
const screenInfo = props.screens[route?.routeName];
useWindowTitleManager(screenInfo);
useWindowRefocusManager(route);
if (!route) throw new Error('Route must not be null');

View File

@@ -32,7 +32,9 @@ function Toolbar(props: ToolbarProps) {
const styles = styles_(props);
return (
<ToolbarBase
id="CodeMirrorToolbar"
style={styles.root}
scrollable={true}
items={props.toolbarButtonInfos}
disabled={!!props.disabled}
aria-label={_('Editor actions')}

View File

@@ -358,6 +358,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
return {
language: isHTMLNote ? EditorLanguageType.Html : EditorLanguageType.Markdown,
readOnly: props.disabled,
markdownMarkEnabled: Setting.value('markdown.plugin.mark'),
katexEnabled: Setting.value('markdown.plugin.katex'),
themeData: {
...styles.globalTheme,
@@ -372,10 +373,12 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
spellcheckEnabled: Setting.value('editor.spellcheckBeta'),
keymap: keyboardMode,
indentWithTabs: true,
tabMovesFocus: props.tabMovesFocus,
editorLabel: _('Markdown editor'),
};
}, [
props.contentMarkupLanguage, props.disabled, props.keyboardMode, styles.globalTheme,
props.tabMovesFocus,
]);
// Update the editor's value

View File

@@ -19,12 +19,11 @@ import { MarkupLanguage, MarkupToHtml } from '@joplin/renderer';
import BaseItem from '@joplin/lib/models/BaseItem';
import setupToolbarButtons from './utils/setupToolbarButtons';
import { plainTextToHtml } from '@joplin/lib/htmlUtils';
import openEditDialog from './utils/openEditDialog';
import { themeStyle } from '@joplin/lib/theme';
import { loadScript } from '../../../utils/loadScript';
import bridge from '../../../../services/bridge';
import { TinyMceEditorEvents } from './utils/types';
import type { Editor } from 'tinymce';
import type { Editor, EditorEvent } from 'tinymce';
import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands';
import shouldPasteResources from './utils/shouldPasteResources';
import lightTheme from '@joplin/lib/themes/light';
@@ -42,6 +41,8 @@ import { hasProtocol } from '@joplin/utils/url';
import useTabIndenter from './utils/useTabIndenter';
import useKeyboardRefocusHandler from './utils/useKeyboardRefocusHandler';
import useDocument from '../../../hooks/useDocument';
import useEditDialog from './utils/useEditDialog';
import useEditDialogEventListeners from './utils/useEditDialogEventListeners';
const logger = Logger.create('TinyMCE');
@@ -72,14 +73,6 @@ function awfulInitHack(html: string): string {
return html === '<div id="rendered-md"></div>' ? '<div id="rendered-md"><p></p></div>' : html;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function findEditableContainer(node: any): any {
while (node) {
if (node.classList && node.classList.contains('joplin-editable')) return node;
node = node.parentNode;
}
return null;
}
let markupToHtml_ = new MarkupToHtml();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -130,19 +123,23 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const { scrollToPercent } = useScroll({ editor, onScroll: props.onScroll });
usePluginServiceRegistration(ref);
useContextMenu(editor, props.plugins, props.dispatch, props.htmlToMarkdown, props.markupToHtml);
useTabIndenter(editor);
useKeyboardRefocusHandler(editor);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const dispatchDidUpdate = (editor: any) => {
const dispatchDidUpdate = useCallback((editor: Editor) => {
if (dispatchDidUpdateIID_) shim.clearTimeout(dispatchDidUpdateIID_);
dispatchDidUpdateIID_ = shim.setTimeout(() => {
dispatchDidUpdateIID_ = null;
if (editor && editor.getDoc()) editor.getDoc().dispatchEvent(new Event('joplin-noteDidUpdate'));
}, 10);
};
}, []);
const editDialog = useEditDialog({ editor, markupToHtml, dispatchDidUpdate });
const editDialogRef = useRef(editDialog);
editDialogRef.current = editDialog;
useEditDialogEventListeners(editor, editDialog);
usePluginServiceRegistration(ref);
useContextMenu(editor, props.plugins, props.dispatch, props.htmlToMarkdown, props.markupToHtml, editDialog);
useTabIndenter(editor, !props.tabMovesFocus);
useKeyboardRefocusHandler(editor);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const insertResourcesIntoContent = useCallback(async (filePaths: string[] = null, options: any = null) => {
@@ -179,7 +176,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
props.onMessage({ channel: href });
}
}
}, [editor, props.onMessage]);
}, [editor, props.onMessage, dispatchDidUpdate]);
useImperativeHandle(ref, () => {
return {
@@ -412,9 +409,11 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
element.setAttribute('id', 'tinyMceStyle');
editorContainerDom.head.appendChild(element);
element.appendChild(editorContainerDom.createTextNode(`
.joplin-tinymce .tox-editor-header {
padding-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px;
padding-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px;
.joplin-tinymce .tox-editor-header.tox-editor-header {
margin-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px;
margin-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px;
padding: 0;
box-shadow: none;
}
.tox .tox-toolbar,
@@ -434,7 +433,8 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
.tox .tox-dialog__body-content,
.tox .tox-collection__item {
.tox .tox-collection__item,
.tox .tox-insert-table-picker__label {
color: ${theme.color};
}
@@ -473,7 +473,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
*/
.tox .tox-dialog textarea {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace !important;
}
.tox .tox-dialog-wrap__backdrop {
@@ -498,6 +498,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-toolbar-label {
color: ${theme.color3} !important;
fill: ${theme.color3} !important;
background: transparent;
}
.tox .tox-statusbar a,
@@ -524,6 +525,11 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-split-button:focus {
background-color: ${theme.backgroundColor3}
}
.tox .tox-tbtn:focus-visible,
.tox .tox-split-button:focus-visible {
background-color: ${theme.backgroundColorHover3}
}
.tox .tox-tbtn:hover,
.tox .tox-menu button:hover > svg {
@@ -560,6 +566,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-split-button:hover {
box-shadow: none;
}
/* Decrease the spacing between groups */
.tox .tox-toolbar__group {
padding-left: 7px;
padding-right: 7px;
}
.tox-tinymce,
.tox .tox-toolbar__group,
@@ -628,7 +640,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
useEffect(() => {
if (!editor) return;
editor.setMode(props.disabled ? 'readonly' : 'design');
editor.mode.set(props.disabled ? 'readonly' : 'design');
}, [editor, props.disabled]);
// -----------------------------------------------------------------------------------------
@@ -675,14 +687,19 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const containerWindow = editorContainerDom.defaultView as any;
const editors = await containerWindow.tinymce.init({
selector: `#${editorContainer.id}`,
// Ensures that the "Premium plugins" toolbar option is disabled. See
// https://www.tiny.cloud/docs/tinymce/latest/editor-premium-upgrade-promotion/
promotion: false,
width: '100%',
body_class: 'jop-tinymce',
height: '100%',
resize: false,
highlight_on_focus: false,
icons: 'Joplin',
icons_url: 'gui/NoteEditor/NoteBody/TinyMCE/icons.js',
plugins: 'noneditable link joplinLists hr searchreplace codesample table',
noneditable_noneditable_class: 'joplin-editable', // Can be a regex too
plugins: 'link joplinLists searchreplace codesample table',
noneditable_class: 'joplin-editable', // Can be a regex too
iframe_aria_text: _('Rich Text editor. Press Escape then Tab to escape focus.'),
// #p: Pad empty paragraphs with &nbsp; to prevent them from being removed.
@@ -694,7 +711,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
relative_urls: false,
branding: false,
statusbar: false,
target_list: false,
link_target_list: false,
// Handle the first table row as table header.
// https://www.tiny.cloud/docs/plugins/table/#table_header_type
table_header_type: 'sectionCells',
@@ -704,13 +721,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
contextmenu: false,
browser_spellcheck: true,
// Work around an issue where images with a base64 SVG data URL would be broken.
//
// See https://github.com/tinymce/tinymce/issues/3864
//
// This was fixed in TinyMCE 6.1, so remove it when we upgrade.
images_dataimg_filter: (img: HTMLImageElement) => !img.src.startsWith('data:'),
formats: {
joplinHighlight: { inline: 'mark', remove: 'all' },
joplinStrikethrough: { inline: 's', remove: 'all' },
@@ -739,7 +749,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: _('Code Block'),
icon: 'code-sample',
onAction: async function() {
openEditDialog(editor, markupToHtml, dispatchDidUpdate, null);
editDialogRef.current.editNew();
},
});
@@ -747,14 +757,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: _('Inline Code'),
icon: 'sourcecode',
onAction: function() {
editor.execCommand('mceToggleFormat', false, 'code', { class: 'inline-code' });
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
editor.execCommand('mceToggleFormat', false, 'code', { class: 'inline-code' } as any);
},
onSetup: function(api) {
api.setActive(editor.formatter.match('code'));
const unbind = editor.formatter.formatChanged('code', api.setActive).unbind;
const handle = editor.formatter.formatChanged('code', active => api.setActive(active));
return function() {
if (unbind) unbind();
handle?.unbind();
};
},
});
@@ -805,11 +816,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
editor.addShortcut('Meta+Shift+9', '', () => editor.execCommand('InsertJoplinChecklist'));
// TODO: remove event on unmount?
editor.on('DblClick', (event) => {
const editable = findEditableContainer(event.target);
if (editable) openEditDialog(editor, markupToHtml, dispatchDidUpdate, editable);
});
editor.on('drop', (event) => {
// Prevent the message "Dropped file type is not supported" from showing up.
// It was added in TinyMCE 5.4 and doesn't apply since we do support
@@ -1206,9 +1212,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onSetAttrib = (event: any) => {
const onSetAttrib = (event: EditorEvent<any>) => {
// Dispatch onChange when a link is edited
if (event.attrElm[0].nodeName === 'A') {
const target = Array.isArray(event.attrElm) ? event.attrElm[0] : event.attrElm;
if (target.nodeName === 'A') {
if (event.attrName === 'title' || event.attrName === 'href' || event.attrName === 'rel') {
onChangeHandler();
}
@@ -1380,7 +1387,17 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
useEffect(() => {
return () => {
if (editorRef.current) editorRef.current.remove();
if (!editorRef.current) return;
const ownerDocument = editorRef.current.getContainer().ownerDocument;
const parentWindow = ownerDocument.defaultView;
// Calling .remove after the parent window is closed throws an Error
// related to DOM API access. Since closing the window also removes the editor,
// it shouldn't be necessary to call .remove in this case:
if (parentWindow) {
editorRef.current.remove();
}
};
}, []);

View File

@@ -886,7 +886,7 @@
var parentLi = editor.dom.getParent(elm, 'li,dd,dt', getClosestListRootElm(editor, elm));
return parentLi ? parentLi : elm;
});
return DomQuery.unique(listItemsElms);
return [...new Set(listItemsElms)];
};
var getSelectedListItems = function (editor) {
var selectedBlocks = editor.selection.getSelectedBlocks();
@@ -919,7 +919,7 @@
var listRoots = map(lists, function (list) {
return findLastParentListNode(editor, list).getOr(list);
});
return DomQuery.unique(listRoots);
return [...new Set(listRoots)];
};
var shouldIndentOnTab = function (editor) {
@@ -2119,8 +2119,7 @@
};
var register$1 = function (editor) {
var hasPlugin = function (editor, plugin) {
var plugins = editor.settings.plugins ? editor.settings.plugins : '';
return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1;
return editor.hasPlugin(plugin);
};
var _ = getLocalizationFunction(editor);
var exec = function (command) {

View File

@@ -0,0 +1,71 @@
import Setting from '@joplin/lib/models/Setting';
import { focus } from '@joplin/lib/utils/focusHandler';
const taboverride = require('taboverride');
export interface TextAreaTabHandler {
remove(): void;
}
const createTextAreaKeyListeners = () => {
let hasListeners = true;
// Selectively enable/disable taboverride based on settings -- remove taboverride
// when pressing tab if tab is expected to move focus.
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Tab') {
if (Setting.value('editor.tabMovesFocus')) {
taboverride.utils.removeListeners(event.currentTarget);
hasListeners = false;
} else {
// Prevent the default focus-changing behavior
event.preventDefault();
requestAnimationFrame(() => {
focus('openEditDialog::dialogTextArea_keyDown', event.target);
});
}
}
};
const onKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Tab' && !hasListeners) {
taboverride.utils.addListeners(event.currentTarget);
hasListeners = true;
}
};
return { onKeyDown, onKeyUp };
};
// Allows pressing tab in a textarea to input an actual tab (instead of changing focus)
// taboverride will take care of actually inserting the tab character, while the keydown
// event listener will override the default behaviour, which is to focus the next field.
const enableTextAreaTab = (textAreas: HTMLTextAreaElement[]): TextAreaTabHandler => {
type RemoveCallback = ()=> void;
const removeCallbacks: RemoveCallback[] = [];
for (const textArea of textAreas) {
const { onKeyDown, onKeyUp } = createTextAreaKeyListeners();
textArea.addEventListener('keydown', onKeyDown);
textArea.addEventListener('keyup', onKeyUp);
// Enable/disable taboverride **after** the listeners above.
// The custom keyup/keydown need to have higher precedence.
taboverride.set(textArea, true);
removeCallbacks.push(() => {
taboverride.set(textArea, false);
textArea.removeEventListener('keyup', onKeyUp);
textArea.removeEventListener('keydown', onKeyDown);
});
}
return {
remove: () => {
for (const callback of removeCallbacks) {
callback();
}
},
};
};
export default enableTextAreaTab;

View File

@@ -67,6 +67,7 @@ export default function(editor: any) {
editor.ui.registry.addGroupToolbarButton('formattingExtras', {
icon: 'image-options',
tooltip: _('Formatting'),
items: items.join(' '),
});
}

View File

@@ -1,3 +1,5 @@
import type { Editor } from 'tinymce';
// eslint-disable-next-line import/prefer-default-export
export enum TinyMceEditorEvents {
KeyUp = 'keyup',
@@ -14,3 +16,5 @@ export enum TinyMceEditorEvents {
ExecCommand = 'ExecCommand',
SetAttrib = 'SetAttrib',
}
export type DispatchDidUpdateCallback = (editor: Editor)=> void;

View File

@@ -14,6 +14,9 @@ import Resource from '@joplin/lib/models/Resource';
import { TinyMceEditorEvents } from './types';
import { HtmlToMarkdownHandler, MarkupToHtmlHandler } from '../../../utils/types';
import { Editor } from 'tinymce';
import { EditDialogControl } from './useEditDialog';
import { Dispatch } from 'redux';
import { _ } from '@joplin/lib/locale';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -52,23 +55,14 @@ interface ContextMenuActionOptions {
const contextMenuActionOptions: ContextMenuActionOptions = { current: null };
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
export default function(editor: Editor, plugins: PluginStates, dispatch: Function, htmlToMd: HtmlToMarkdownHandler, mdToHtml: MarkupToHtmlHandler) {
export default function(editor: Editor, plugins: PluginStates, dispatch: Dispatch, htmlToMd: HtmlToMarkdownHandler, mdToHtml: MarkupToHtmlHandler, editDialog: EditDialogControl) {
useEffect(() => {
if (!editor) return () => {};
const contextMenuItems = menuItems(dispatch, htmlToMd, mdToHtml);
const targetWindow = bridge().activeWindow();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function onContextMenu(event: ElectronEvent, params: any) {
const element = contextMenuElement(editor, params.x, params.y);
if (!element) return;
event.preventDefault();
const menu = new Menu();
const makeMainMenuItems = (element: Element) => {
let itemType: ContextMenuItemType = ContextMenuItemType.None;
let resourceId = '';
let linkToCopy = null;
@@ -103,29 +97,57 @@ export default function(editor: Editor, plugins: PluginStates, dispatch: Functio
mdToHtml,
};
const result = [];
for (const itemName in contextMenuItems) {
const item = contextMenuItems[itemName];
if (!item.isActive(itemType, contextMenuActionOptions.current)) continue;
menu.append(new MenuItem({
result.push(new MenuItem({
label: item.label,
click: () => {
item.onAction(contextMenuActionOptions.current);
},
}));
}
return result;
};
const makeEditableMenuItems = (element: Element) => {
if (editDialog.isEditable(element)) {
return [
new MenuItem({
type: 'normal',
label: _('Edit'),
click: () => {
editDialog.editExisting(element);
},
}),
new MenuItem({ type: 'separator' }),
];
}
return [];
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function onContextMenu(event: ElectronEvent, params: any) {
const element = contextMenuElement(editor, params.x, params.y);
if (!element) return;
event.preventDefault();
const menu = new Menu();
const menuItems = [];
menuItems.push(...makeEditableMenuItems(element));
menuItems.push(...makeMainMenuItems(element));
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
menuItems.push(...spellCheckerMenuItems);
menuItems.push(...menuUtils.pluginContextMenuItems(plugins, MenuItemLocation.EditorContextMenu));
for (const item of spellCheckerMenuItems) {
menu.append(new MenuItem(item));
for (const item of menuItems) {
menu.append(item);
}
for (const item of menuUtils.pluginContextMenuItems(plugins, MenuItemLocation.EditorContextMenu)) {
menu.append(new MenuItem(item));
}
menu.popup({ window: targetWindow });
}
@@ -136,5 +158,5 @@ export default function(editor: Editor, plugins: PluginStates, dispatch: Functio
targetWindow.webContents.off('context-menu', onContextMenu);
}
};
}, [editor, plugins, dispatch, htmlToMd, mdToHtml]);
}, [editor, plugins, dispatch, htmlToMd, mdToHtml, editDialog]);
}

View File

@@ -1,43 +1,32 @@
import { RefObject, useMemo } from 'react';
import type { Editor } from 'tinymce';
import { DispatchDidUpdateCallback, TinyMceEditorEvents } from './types';
import { MarkupToHtmlHandler } from '../../../utils/types';
import { _ } from '@joplin/lib/locale';
import enableTextAreaTab, { TextAreaTabHandler } from './enableTextAreaTab';
import { MarkupToHtml } from '@joplin/renderer';
import { TinyMceEditorEvents } from './types';
import { focus } from '@joplin/lib/utils/focusHandler';
const taboverride = require('taboverride');
interface Props {
editor: Editor;
markupToHtml: RefObject<MarkupToHtmlHandler>;
dispatchDidUpdate: DispatchDidUpdateCallback;
}
export interface EditDialogControl {
editNew: ()=> void;
editExisting: (elementInEditable: Node)=> void;
isEditable: (element: Node)=> boolean;
}
interface SourceInfo {
openCharacters: string;
closeCharacters: string;
content: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
node: any;
node: Element;
language: string;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function dialogTextArea_keyDown(event: any) {
if (event.key === 'Tab') {
window.requestAnimationFrame(() => focus('openEditDialog::dialogTextArea_keyDown', event.target));
}
}
// Allows pressing tab in a textarea to input an actual tab (instead of changing focus)
// taboverride will take care of actually inserting the tab character, while the keydown
// event listener will override the default behaviour, which is to focus the next field.
function enableTextAreaTab(enable: boolean) {
const textAreas = document.getElementsByClassName('tox-textarea');
for (const textArea of textAreas) {
taboverride.set(textArea, enable);
if (enable) {
textArea.addEventListener('keydown', dialogTextArea_keyDown);
} else {
textArea.removeEventListener('keydown', dialogTextArea_keyDown);
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function findBlockSource(node: any): SourceInfo {
function findBlockSource(node: Element): SourceInfo {
const sources = node.getElementsByClassName('joplin-source');
if (!sources.length) throw new Error('No source for node');
const source = sources[0];
@@ -81,9 +70,14 @@ function editableInnerHtml(html: string): string {
return editable[0].innerHTML;
}
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
export default function openEditDialog(editor: any, markupToHtml: any, dispatchDidUpdate: Function, editable: any) {
function openEditDialog(
editor: Editor,
markupToHtml: RefObject<MarkupToHtmlHandler>,
dispatchDidUpdate: DispatchDidUpdateCallback,
editable: Element,
) {
const source = editable ? findBlockSource(editable) : newBlockSource();
let tabHandler: TextAreaTabHandler|null = null;
editor.windowManager.open({
title: _('Edit'),
@@ -113,7 +107,7 @@ export default function openEditDialog(editor: any, markupToHtml: any, dispatchD
dispatchDidUpdate(editor);
},
onClose: () => {
enableTextAreaTab(false);
tabHandler?.remove();
},
body: {
type: 'panel',
@@ -124,12 +118,11 @@ export default function openEditDialog(editor: any, markupToHtml: any, dispatchD
label: 'Language',
// Katex is a special case with special opening/closing tags
// and we don't currently handle switching the language in this case.
disabled: source.language === 'katex',
enabled: source.language !== 'katex',
},
{
type: 'textarea',
name: 'codeTextArea',
value: source.content,
},
],
},
@@ -142,6 +135,40 @@ export default function openEditDialog(editor: any, markupToHtml: any, dispatchD
});
window.requestAnimationFrame(() => {
enableTextAreaTab(true);
const containerDocument = editor.getContainer().ownerDocument;
const textAreas = containerDocument.querySelectorAll<HTMLTextAreaElement>('.tox-textarea');
tabHandler = enableTextAreaTab([...textAreas]);
});
}
const findEditableContainer = (node: Node) => {
if (node.nodeName.startsWith('#')) { // Not an element, e.g. #text
node = node.parentElement;
}
return (node as Element)?.closest('.joplin-editable');
};
const useEditDialog = ({
editor, markupToHtml, dispatchDidUpdate,
}: Props): EditDialogControl => {
return useMemo(() => {
const edit = (editable: Element|null) => {
openEditDialog(editor, markupToHtml, dispatchDidUpdate, editable);
};
return {
isEditable: element => !!findEditableContainer(element),
editExisting: (element: Node) => {
const editable = findEditableContainer(element);
if (editable) {
edit(editable);
}
},
editNew: () => {
edit(null);
},
};
}, [editor, markupToHtml, dispatchDidUpdate]);
};
export default useEditDialog;

View File

@@ -0,0 +1,34 @@
import { Editor } from 'tinymce';
import { EditDialogControl } from './useEditDialog';
import { useEffect } from 'react';
import { TinyMceEditorEvents } from './types';
const useEditDialogEventListeners = (editor: Editor|null, editDialog: EditDialogControl) => {
useEffect(() => {
if (!editor) return () => {};
const dblClickHandler = (event: Event) => {
editDialog.editExisting(event.target as Node);
};
const keyDownHandler = (event: KeyboardEvent) => {
const hasModifiers = event.shiftKey || event.altKey || event.ctrlKey || event.metaKey;
if (event.code === 'Enter' && !event.isComposing && !hasModifiers) {
const selection = editor.selection.getNode();
if (editDialog.isEditable(selection)) {
editDialog.editExisting(selection);
event.preventDefault();
}
}
};
editor.on(TinyMceEditorEvents.KeyDown, keyDownHandler);
editor.on('DblClick', dblClickHandler);
return () => {
editor.off(TinyMceEditorEvents.KeyDown, keyDownHandler);
editor.off('DblClick', dblClickHandler);
};
}, [editor, editDialog]);
};
export default useEditDialogEventListeners;

View File

@@ -1,9 +1,9 @@
import { useEffect } from 'react';
import type { Editor, EditorEvent } from 'tinymce';
const useTabIndenter = (editor: Editor) => {
const useTabIndenter = (editor: Editor, enabled: boolean) => {
useEffect(() => {
if (!editor) return () => {};
if (!editor || !enabled) return () => {};
const canChangeIndentation = () => {
const selectionElement = editor.selection.getNode();
@@ -70,7 +70,7 @@ const useTabIndenter = (editor: Editor) => {
return () => {
editor.off('keydown', eventHandler);
};
}, [editor]);
}, [editor, enabled]);
};
export default useTabIndenter;

View File

@@ -16,13 +16,11 @@ import useFolder from './utils/useFolder';
import styles_ from './styles';
import { NoteEditorProps, FormNote, OnChangeEvent, NoteBodyEditorProps, AllAssetsOptions, NoteBodyEditorRef } from './utils/types';
import CommandService from '@joplin/lib/services/CommandService';
import ToolbarButton from '../ToolbarButton/ToolbarButton';
import Button, { ButtonLevel } from '../Button/Button';
import eventManager, { EventName } from '@joplin/lib/eventManager';
import { AppState } from '../../app.reducer';
import ToolbarButtonUtils, { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import { _, _n } from '@joplin/lib/locale';
import TagList from '../TagList';
import NoteTitleBar from './NoteTitle/NoteTitleBar';
import markupLanguageUtils from '@joplin/lib/utils/markupLanguageUtils';
import Setting from '@joplin/lib/models/Setting';
@@ -59,6 +57,7 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
import WebviewController from '@joplin/lib/services/plugins/WebviewController';
import AsyncActionQueue, { IntervalType } from '@joplin/lib/AsyncActionQueue';
import useResourceUnwatcher from './utils/useResourceUnwatcher';
import StatusBar from './StatusBar';
const debounce = require('debounce');
@@ -440,24 +439,6 @@ function NoteEditorContent(props: NoteEditorProps) {
return <div style={emptyDivStyle} ref={containerRef}></div>;
}
function renderTagButton() {
return <ToolbarButton
themeId={props.themeId}
toolbarButtonInfo={props.setTagsToolbarButtonInfo}
/>;
}
function renderTagBar() {
const theme = themeStyle(props.themeId);
const noteIds = [formNote.id];
const instructions = <span onClick={() => { void CommandService.instance().execute('setTags', noteIds); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>{_('Click to add tags...')}</span>;
const tagList = props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
return (
<div style={{ paddingLeft: 8, display: 'flex', flexDirection: 'row', alignItems: 'center' }}>{tagList}{instructions}</div>
);
}
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
const editorProps: NoteBodyEditorProps = {
@@ -488,6 +469,7 @@ function NoteEditorContent(props: NoteEditorProps) {
searchMarkers: searchMarkers,
visiblePanes: props.noteVisiblePanes || ['editor', 'viewer'],
keyboardMode: Setting.value('editor.keyboardMode'),
tabMovesFocus: props.tabMovesFocus,
locale: Setting.value('locale'),
onDrop: onDrop,
noteToolbarButtonInfos: props.toolbarButtonInfos,
@@ -539,6 +521,7 @@ function NoteEditorContent(props: NoteEditorProps) {
verticalAlign: 'top',
boxSizing: 'border-box',
flex: 1,
overflowX: 'scroll',
};
return (
@@ -689,10 +672,11 @@ function NoteEditorContent(props: NoteEditorProps) {
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
{renderSearchBar()}
</div>
<div className="tag-bar" style={{ paddingLeft: theme.editorPaddingLeft, display: 'flex', flexDirection: 'row', alignItems: 'center', height: 40 }}>
{renderTagButton()}
{renderTagBar()}
</div>
<StatusBar
noteId={formNote.id}
setTagsToolbarButtonInfo={props.setTagsToolbarButtonInfo}
selectedNoteTags={props.selectedNoteTags}
/>
<WarningBanner bodyEditor={props.bodyEditor}/>
</div>
</div>
@@ -749,6 +733,7 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
], whenClauseContext)[0] as ToolbarButtonInfo,
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
scrollbarSize: state.settings['style.scrollbarSize'],
tabMovesFocus: state.settings['editor.tabMovesFocus'],
isSafeMode: state.settings.isSafeMode,
useCustomPdfViewer: false,
syncUserId: state.settings['sync.userId'],

View File

@@ -0,0 +1,88 @@
import * as React from 'react';
import ToolbarButton from '../ToolbarButton/ToolbarButton';
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import CommandService from '@joplin/lib/services/CommandService';
import { themeStyle } from '@joplin/lib/theme';
import { AppState } from '../../app.reducer';
import { connect } from 'react-redux';
import { TagEntity } from '@joplin/lib/services/database/types';
import TagList from '../TagList';
import { _ } from '@joplin/lib/locale';
import { useCallback } from 'react';
import KeymapService from '@joplin/lib/services/KeymapService';
interface Props {
themeId: number;
tabMovesFocus: boolean;
noteId: string;
setTagsToolbarButtonInfo: ToolbarButtonInfo;
selectedNoteTags: TagEntity[];
}
interface StatusIndicatorProps {
commandName: string;
showWhenUnfocused: boolean;
// Even if not visible, [label] should reflect the current state
// of the indicator.
label: string;
}
const StatusIndicator: React.FC<StatusIndicatorProps> = props => {
const runCommand = useCallback(() => {
void CommandService.instance().execute(props.commandName);
}, [props.commandName]);
const keyshortcuts = KeymapService.instance().getAriaKeyShortcuts(props.commandName);
return <span
className={`status editor-status-indicator ${props.showWhenUnfocused ? '-show' : ''}`}
aria-live='polite'
>
<button
className='button'
aria-keyshortcuts={keyshortcuts}
onClick={runCommand}
>
{props.label}
</button>
</span>;
};
const StatusBar: React.FC<Props> = props => {
function renderTagButton() {
return <ToolbarButton
themeId={props.themeId}
toolbarButtonInfo={props.setTagsToolbarButtonInfo}
/>;
}
function renderTagBar() {
const theme = themeStyle(props.themeId);
const noteIds = [props.noteId];
const instructions = <span onClick={() => { void CommandService.instance().execute('setTags', noteIds); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>{_('Click to add tags...')}</span>;
const tagList = props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
return <div className='tag-bar'>
{renderTagButton()}
<div className='content'>{tagList}{instructions}</div>
</div>;
}
const keyboardStatus = <StatusIndicator
commandName='toggleTabMovesFocus'
label={props.tabMovesFocus ? _('Tab moves focus') : _('Tab indents')}
showWhenUnfocused={props.tabMovesFocus}
/>;
return <div className='editor-status-bar'>
{renderTagBar()}
<div className='spacer'/>
{keyboardStatus}
</div>;
};
export default connect((state: AppState) => {
return {
themeId: state.settings.theme,
tabMovesFocus: state.settings['editor.tabMovesFocus'],
};
})(StatusBar);

View File

@@ -0,0 +1,36 @@
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { focus } from '@joplin/lib/utils/focusHandler';
import { WindowCommandDependencies } from '../utils/types';
export const declaration: CommandDeclaration = {
name: 'focusElementToolbar',
label: () => _('Toolbar'),
parentLabel: () => _('Focus'),
};
export const runtime = (dependencies: WindowCommandDependencies): CommandRuntime => {
return {
execute: async () => {
if (!dependencies || !dependencies.containerRef || !dependencies.containerRef.current) return;
const firstButtonOnRTEToolbar = dependencies.containerRef.current.querySelector(
'.tox-toolbar__group button',
);
if (firstButtonOnRTEToolbar) {
focus('focusElementToolbar', firstButtonOnRTEToolbar);
return;
}
const firstButtonOnMarkdownToolbar = dependencies.containerRef.current.querySelector(
'#CodeMirrorToolbar .button:not(.disabled)',
);
if (firstButtonOnMarkdownToolbar) {
focus('focusElementToolbar', firstButtonOnMarkdownToolbar);
}
},
};
};

View File

@@ -1,6 +1,7 @@
// AUTO-GENERATED using `gulp buildScriptIndexes`
import * as focusElementNoteBody from './focusElementNoteBody';
import * as focusElementNoteTitle from './focusElementNoteTitle';
import * as focusElementToolbar from './focusElementToolbar';
import * as pasteAsText from './pasteAsText';
import * as showLocalSearch from './showLocalSearch';
import * as showRevisions from './showRevisions';
@@ -8,6 +9,7 @@ import * as showRevisions from './showRevisions';
const index: any[] = [
focusElementNoteBody,
focusElementNoteTitle,
focusElementToolbar,
pasteAsText,
showLocalSearch,
showRevisions,

View File

@@ -7,3 +7,6 @@
@use "./styles/note-editor-viewer-row.scss";
@use "./styles/revision-viewer-root.scss";
@use "./styles/revision-viewer-title.scss";
@use "./styles/tag-bar.scss";
@use "./styles/editor-status-bar.scss";
@use "./styles/editor-status-indicator.scss";

View File

@@ -0,0 +1,13 @@
.editor-status-bar {
display: flex;
flex-direction: row;
> .spacer {
flex-grow: 1;
}
> .status {
align-self: end;
}
}

View File

@@ -0,0 +1,18 @@
.editor-status-indicator {
width: 0;
overflow: hidden;
&:has(> :focus-visible), &.-show {
width: unset;
overflow: visible;
}
> .button {
font-size: var(--joplin-font-size-small);
background-color: var(--joplin-background-color-active3);
color: var(--joplin-color);
border: none;
padding: 4px;
}
}

View File

@@ -0,0 +1,14 @@
.tag-bar {
padding-left: var(--joplin-editor-padding-left);
display: flex;
flex-direction: row;
align-items: center;
height: 40px;
> .content {
padding-left: 8px;
display: flex;
flex-direction: row;
align-items: center;
}
}

View File

@@ -51,6 +51,7 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
createFileURL: options.createFileURL,
resizeLargeImages: Setting.value('imageResizing'),
markupLanguage: options.markupLanguage,
resourceSuffix: i > 0 ? ' ' : '',
});
if (!newBody) {

View File

@@ -11,6 +11,8 @@ import { ParseOptions } from '@joplin/lib/HtmlToMd';
import { ScrollStrategy } from '@joplin/editor/CodeMirror/CodeMirrorControl';
import { MarkupToHtmlOptions } from '../../hooks/useMarkupToHtml';
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
import { RefObject, SetStateAction } from 'react';
import * as React from 'react';
export interface AllAssetsOptions {
contentMaxWidthTarget?: string;
@@ -49,6 +51,7 @@ export interface NoteEditorProps {
watchedResources: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
highlightedWords: any[];
tabMovesFocus: boolean;
plugins: PluginStates;
toolbarButtonInfos: ToolbarItem[];
setTagsToolbarButtonInfo: ToolbarButtonInfo;
@@ -109,8 +112,7 @@ export interface NoteBodyEditorProps {
htmlToMarkdown: HtmlToMarkdownHandler;
allAssets: (markupLanguage: MarkupLanguage, options: AllAssetsOptions)=> Promise<RenderResultPluginAsset[]>;
disabled: boolean;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
dispatch: Function;
dispatch: Dispatch;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
noteToolbar: any;
setLocalSearchResultCount(count: number): void;
@@ -121,6 +123,7 @@ export interface NoteBodyEditorProps {
searchMarkers: SearchMarkers;
visiblePanes: string[];
keyboardMode: string;
tabMovesFocus: boolean;
resourceInfos: ResourceInfos;
resourceDirectory: string;
locale: string;
@@ -271,3 +274,11 @@ export interface ScrollToTextValue {
element: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'strong' | 'ul';
scrollStrategy?: ScrollStrategy;
}
export interface WindowCommandDependencies {
setShowLocalSearch: React.Dispatch<SetStateAction<boolean>>;
noteSearchBarRef: RefObject<HTMLInputElement>;
editorRef: RefObject<NoteBodyEditorRef>;
titleInputRef: RefObject<HTMLInputElement>;
containerRef: RefObject<HTMLDivElement|null>;
}

View File

@@ -1,15 +1,11 @@
import { useMemo } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import getActivePluginEditorView from '@joplin/lib/services/plugins/utils/getActivePluginEditorView';
import getShownPluginEditorView from '@joplin/lib/services/plugins/utils/getShownPluginEditorView';
// If a plugin editor should be shown for the current note, this function will return the plugin and
// associated view.
export default (plugins: PluginStates, shownEditorViewIds: string[]) => {
return useMemo(() => {
const { editorPlugin, editorView } = getActivePluginEditorView(plugins);
if (editorView) {
if (!shownEditorViewIds.includes(editorView.id)) return { editorPlugin: null, editorView: null };
}
return { editorPlugin, editorView };
return getShownPluginEditorView(plugins, shownEditorViewIds);
}, [plugins, shownEditorViewIds]);
};

View File

@@ -1,5 +1,5 @@
import { RefObject, useEffect } from 'react';
import { NoteBodyEditorRef, OnChangeEvent, ScrollOptionTypes } from './types';
import { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
import { WindowCommandDependencies, NoteBodyEditorRef, OnChangeEvent, ScrollOptionTypes } from './types';
import editorCommandDeclarations, { enabledCondition } from '../editorCommandDeclarations';
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext, RegisteredRuntime } from '@joplin/lib/services/CommandService';
import time from '@joplin/lib/time';
@@ -10,14 +10,14 @@ const commandsWithDependencies = [
require('../commands/showLocalSearch'),
require('../commands/focusElementNoteTitle'),
require('../commands/focusElementNoteBody'),
require('../commands/focusElementToolbar'),
require('../commands/pasteAsText'),
];
type OnBodyChange = (event: OnChangeEvent)=> void;
interface HookDependencies {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
setShowLocalSearch: Function;
setShowLocalSearch: Dispatch<SetStateAction<boolean>>;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
dispatch: Function;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -93,11 +93,12 @@ export default function useWindowCommandHandler(dependencies: HookDependencies)
));
}
const dependencies = {
const dependencies: WindowCommandDependencies = {
editorRef,
setShowLocalSearch,
noteSearchBarRef,
titleInputRef,
containerRef,
};
for (const command of commandsWithDependencies) {

View File

@@ -29,6 +29,7 @@ import getNoteElementIdFromJoplinId from '../NoteListItem/utils/getNoteElementId
import useFocusVisible from './utils/useFocusVisible';
import { stateUtils } from '@joplin/lib/reducer';
import { connect } from 'react-redux';
import useOnNoteDoubleClick from './utils/useOnNoteDoubleClick';
const commands = {
focusElementNoteList,
@@ -103,6 +104,8 @@ const NoteList = (props: Props) => {
const onNoteClick = useOnNoteClick(props.dispatch, focusNote);
const onNoteDoubleClick = useOnNoteDoubleClick();
const onKeyDown = useOnKeyDown(
activeNoteId,
props.selectedNoteIds,
@@ -198,7 +201,9 @@ const NoteList = (props: Props) => {
const renderEmptyList = () => {
if (props.notes.length) return null;
return <div className="emptylist">{getEmptyFolderMessage(props.folders, props.selectedFolderId)}</div>;
// Role status is necessary for the screenreader to announce that the list is empty, since when there are
// zero items there is not list to render
return <div className="emptylist" role="status">{getEmptyFolderMessage(props.folders, props.selectedFolderId)}</div>;
};
const renderFiller = (key: string, style: React.CSSProperties) => {
@@ -226,6 +231,7 @@ const NoteList = (props: Props) => {
itemSize={itemSize}
onChange={listRenderer.onChange}
onClick={onNoteClick}
onDoubleClick={onNoteDoubleClick}
onContextMenu={onItemContextMenu}
onDragStart={onDragStart}
onDragOver={onDragOver}
@@ -302,6 +308,7 @@ const NoteList = (props: Props) => {
onKeyUp={onKeyUp}
onDrop={onDrop}
onContextMenu={onContainerContextMenu}
id='notes-list'
>
{renderEmptyList()}
{renderFiller('top', topFillerStyle)}

View File

@@ -0,0 +1,10 @@
import * as React from 'react';
import { useCallback } from 'react';
import CommandService from '@joplin/lib/services/CommandService';
export default () => {
return useCallback((event: React.MouseEvent<HTMLDivElement>) => {
const noteId = event.currentTarget.getAttribute('data-id');
void CommandService.instance().execute('openNoteInNewWindow', noteId);
}, []);
};

View File

@@ -22,6 +22,7 @@ interface NoteItemProps {
noteCount: number;
onChange: OnChangeHandler;
onClick: MouseEventHandler<HTMLDivElement>;
onDoubleClick: MouseEventHandler<HTMLDivElement>;
onContextMenu: MouseEventHandler;
onDragOver: DragEventHandler;
onDragStart: DragEventHandler;
@@ -79,6 +80,7 @@ const NoteListItem = (props: NoteItemProps, ref: LegacyRef<HTMLDivElement>) => {
props.style,
props.itemSize,
props.onClick,
props.onDoubleClick,
props.flow,
);

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { ItemFlow } from '@joplin/lib/services/plugins/api/noteListType';
const useItemElement = (
rootElement: HTMLDivElement, noteId: string, noteHtml: string, focusVisible: boolean, style: React.CSSProperties, itemSize: Size, onClick: React.MouseEventHandler<HTMLDivElement>, flow: ItemFlow,
rootElement: HTMLDivElement, noteId: string, noteHtml: string, focusVisible: boolean, style: React.CSSProperties, itemSize: Size, onClick: React.MouseEventHandler<HTMLDivElement>, onDoubleClick: React.MouseEventHandler<HTMLDivElement>, flow: ItemFlow,
) => {
const [itemElement, setItemElement] = useState<HTMLDivElement>(null);
@@ -21,8 +21,10 @@ const useItemElement = (
if (flow === ItemFlow.LeftToRight) element.style.width = `${itemSize.width}px`;
element.style.height = `${itemSize.height}px`;
element.innerHTML = noteHtml;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're mixing React synthetic events with DOM events which ideally should not be done but it is fine in this particular case
element.addEventListener('click', onClick as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- we're mixing React synthetic events with DOM events which ideally should not be done but it is fine in this particular case
element.addEventListener('dblclick', onDoubleClick as any);
rootElement.appendChild(element);
@@ -31,7 +33,7 @@ const useItemElement = (
return () => {
element.remove();
};
}, [rootElement, itemSize, noteHtml, noteId, style, onClick, flow]);
}, [rootElement, itemSize, noteHtml, noteId, style, onClick, onDoubleClick, flow]);
useEffect(() => {
if (!itemElement) return;

View File

@@ -1,7 +1,6 @@
import * as React from 'react';
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
import time from '@joplin/lib/time';
import DialogButtonRow from './DialogButtonRow';
import Note from '@joplin/lib/models/Note';
import bridge from '../services/bridge';
@@ -9,7 +8,7 @@ import shim from '@joplin/lib/shim';
import { NoteEntity } from '@joplin/lib/services/database/types';
import { focus } from '@joplin/lib/utils/focusHandler';
import Dialog from './Dialog';
const Datetime = require('react-datetime').default;
import { formatDateTimeLocalToMs, formatMsToDateTimeLocal, formatMsToLocal } from '@joplin/utils/time';
const { clipboard } = require('electron');
const formatcoords = require('formatcoords');
@@ -23,14 +22,14 @@ interface Props {
interface FormNote {
id: string;
deleted_time: string;
deleted_time: number;
location: string;
markup_language: string;
revisionsLink: string;
source_url: string;
todo_completed?: string;
user_created_time: string;
user_updated_time: string;
todo_completed?: number;
user_created_time: number;
user_updated_time: number;
}
interface State {
@@ -40,6 +39,12 @@ interface State {
editedValue: any;
}
const uniqueId = (key: string) => `note-properties-dialog-${key}`;
const isPropertyDatetimeRelated = (key: string) => {
return key === 'user_created_time' || key === 'user_updated_time' || key === 'deleted_time';
};
class NotePropertiesDialog extends React.Component<Props, State> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -48,6 +53,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
private styleKey_: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
private styles_: any;
private inputRef: React.RefObject<HTMLInputElement>;
public constructor(props: Props) {
super(props);
@@ -55,6 +61,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
this.revisionsLink_click = this.revisionsLink_click.bind(this);
this.buttonRow_click = this.buttonRow_click.bind(this);
this.okButton = React.createRef();
this.inputRef = React.createRef();
this.state = {
formNote: null,
@@ -116,17 +123,17 @@ class NotePropertiesDialog extends React.Component<Props, State> {
public noteToFormNote(note: NoteEntity) {
const formNote: FormNote = {
id: note.id,
user_updated_time: time.formatMsToLocal(note.user_updated_time),
user_created_time: time.formatMsToLocal(note.user_created_time),
user_updated_time: note.user_updated_time,
user_created_time: note.user_created_time,
source_url: note.source_url,
location: '',
revisionsLink: note.id,
markup_language: Note.markupLanguageToLabel(note.markup_language),
deleted_time: note.deleted_time ? time.formatMsToLocal(note.deleted_time) : '',
deleted_time: note.deleted_time,
};
if (note.todo_completed) {
formNote.todo_completed = time.formatMsToLocal(note.todo_completed);
formNote.todo_completed = note.todo_completed;
}
if (Number(note.latitude) || Number(note.longitude)) {
@@ -138,11 +145,11 @@ class NotePropertiesDialog extends React.Component<Props, State> {
public formNoteToNote(formNote: FormNote) {
const note: NoteEntity = { id: formNote.id, ...this.latLongFromLocation(formNote.location) };
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
note.user_created_time = formNote.user_created_time;
note.user_updated_time = formNote.user_updated_time;
if (formNote.todo_completed) {
note.todo_completed = time.formatLocalToMs(formNote.todo_completed);
note.todo_completed = formNote.todo_completed;
}
note.source_url = formNote.source_url;
@@ -224,13 +231,11 @@ class NotePropertiesDialog extends React.Component<Props, State> {
});
shim.setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
if ((this.refs.editField as any).openCalendar) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(this.refs.editField as any).openCalendar();
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
focus('NotePropertiesDialog::editPropertyButtonClick', (this.refs.editField as any));
// Opens datetime-local fields with calendar
if (this.inputRef.current.showPicker) {
this.inputRef.current.showPicker();
} else if (this.inputRef.current) {
focus('NotePropertiesDialog::editPropertyButtonClick', (this.inputRef.current));
}
}, 100);
}
@@ -242,14 +247,8 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return new Promise((resolve: Function) => {
const newFormNote = { ...this.state.formNote };
if (this.state.editedKey.indexOf('_time') >= 0) {
const dt = time.anythingToDateTime(this.state.editedValue, new Date());
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(newFormNote as any)[this.state.editedKey] = time.formatMsToLocal(dt.getTime());
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(newFormNote as any)[this.state.editedKey] = this.state.editedValue;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(newFormNote as any)[this.state.editedKey] = this.state.editedValue;
this.setState(
{
@@ -282,7 +281,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
const styles = this.styles(this.props.themeId);
const theme = themeStyle(this.props.themeId);
const labelText = this.formatLabel(key);
const labelComp = <label role='rowheader' style={{ ...theme.textStyle, ...theme.controlBoxLabel }}>{labelText}</label>;
const labelComp = <label htmlFor={uniqueId(key)} role='rowheader' style={{ ...theme.textStyle, ...theme.controlBoxLabel }}>{labelText}</label>;
let controlComp = null;
let editComp = null;
let editCompHandler = null;
@@ -299,24 +298,17 @@ class NotePropertiesDialog extends React.Component<Props, State> {
};
if (this.state.editedKey === key) {
if (key.indexOf('_time') >= 0) {
controlComp = (
<Datetime
ref="editField"
initialValue={value}
dateFormat={time.dateFormat()}
timeFormat={time.timeFormat()}
inputProps={{
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onKeyDown: (event: any) => onKeyDown(event),
style: styles.input,
}}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onChange={(momentObject: any) => {
this.setState({ editedValue: momentObject });
}}
/>
);
if (isPropertyDatetimeRelated(key)) {
controlComp = <input
type="datetime-local"
defaultValue={formatMsToDateTimeLocal(value)}
ref={this.inputRef}
onChange={event => this.setState({ editedValue: formatDateTimeLocalToMs(event.target.value) })}
onKeyDown={event => onKeyDown(event)}
style={styles.input}
id={uniqueId(key)}
name={uniqueId(key)}
/>;
editCompHandler = () => {
void this.saveProperty();
@@ -328,12 +320,14 @@ class NotePropertiesDialog extends React.Component<Props, State> {
<input
defaultValue={value}
type="text"
ref="editField"
ref={this.inputRef}
onChange={event => {
this.setState({ editedValue: event.target.value });
}}
onKeyDown={event => onKeyDown(event)}
style={styles.input}
id={uniqueId(key)}
name={uniqueId(key)}
/>
);
}
@@ -347,6 +341,8 @@ class NotePropertiesDialog extends React.Component<Props, State> {
} catch (error) {
displayedValue = '';
}
} else if (isPropertyDatetimeRelated(key)) {
displayedValue = formatMsToLocal(value);
}
if (['source_url', 'location'].indexOf(key) >= 0) {
@@ -415,22 +411,6 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return key;
}
public formatValue(key: string, note: NoteEntity) {
if (key === 'location') {
if (!Number(note.latitude) && !Number(note.longitude)) return null;
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
return dms.format('DDMMss', { decimalPlaces: 0 });
}
if (['user_updated_time', 'user_created_time', 'todo_completed'].indexOf(key) >= 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return time.formatMsToLocal((note as any)[key]);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
return (note as any)[key];
}
public render() {
const theme = themeStyle(this.props.themeId);
const formNote = this.state.formNote;

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import { useEffect } from 'react';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
import NoteTextViewer, { NoteViewerControl } from './NoteTextViewer';
@@ -21,6 +22,7 @@ import useQueuedAsyncEffect from '@joplin/lib/hooks/useQueuedAsyncEffect';
import useMarkupToHtml from './hooks/useMarkupToHtml';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
import { focus } from '@joplin/lib/utils/focusHandler';
interface Props {
themeId: number;
@@ -61,7 +63,7 @@ const useNoteContent = (
useQueuedAsyncEffect(async () => {
const noteBody = note?.body ?? _('This note has no history');
const markupLanguage = note.markup_language ?? MarkupLanguage.Markdown;
const markupLanguage = note?.markup_language ?? MarkupLanguage.Markdown;
const result = await markupToHtml(markupLanguage, noteBody, {
resources: await shared.attachedResources(noteBody),
whiteBackgroundNoteRendering: markupLanguage === MarkupLanguage.Html,
@@ -78,6 +80,7 @@ const useNoteContent = (
const NoteRevisionViewerComponent: React.FC<Props> = ({ themeId, noteId, onBack, customCss, scrollbarSize }) => {
const helpButton_onClick = useCallback(() => {}, []);
const viewerRef = useRef<NoteViewerControl|null>(null);
const revisionListRef = useRef<HTMLSelectElement|null>(null);
const [revisions, setRevisions] = useState<RevisionEntity[]>([]);
const [currentRevId, setCurrentRevId] = useState('');
@@ -168,7 +171,7 @@ const NoteRevisionViewerComponent: React.FC<Props> = ({ themeId, noteId, onBack,
<i style={theme.buttonIconStyle} className={'fa fa-chevron-left'}></i>{_('Back')}
</button>
<input readOnly type="text" className='title' style={theme.inputStyle} value={note?.title ?? ''} />
<select disabled={!revisions.length} value={currentRevId} className='revisions' style={theme.dropdownList} onChange={revisionList_onChange}>
<select disabled={!revisions.length} value={currentRevId} className='revisions' style={theme.dropdownList} onChange={revisionList_onChange} ref={revisionListRef}>
{revisionListItems}
</select>
<button disabled={!revisions.length || restoring} onClick={importButton_onClick} className='restore'style={{ ...theme.buttonStyle, marginLeft: 10, height: theme.inputStyle.height }}>
@@ -180,6 +183,12 @@ const NoteRevisionViewerComponent: React.FC<Props> = ({ themeId, noteId, onBack,
const viewer = <NoteTextViewer themeId={themeId} viewerStyle={{ display: 'flex', flex: 1, borderLeft: 'none' }} ref={viewerRef} onDomReady={viewer_domReady} onIpcMessage={webview_ipcMessage} />;
useEffect(() => {
// We need to force focus here because otherwise the focus is lost and goes back
// to the start of the document. See https://github.com/laurent22/joplin/pull/11769
focus('NoteRevisionViewer', revisionListRef.current);
}, [revisionListRef, revisions]);
return (
<div className='revision-viewer-root'>
{titleInput}

View File

@@ -225,7 +225,7 @@ const NoteTextViewer = forwardRef((props: Props, ref: ForwardedRef<NoteViewerCon
style={viewerStyle}
allow='clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=(self) encrypted-media=(self)'
allowFullScreen={true}
aria-label={_('Note editor')}
aria-label={_('Note viewer')}
src={`joplin-content://note-viewer/${__dirname}/note-viewer/index.html`}
></iframe>
);

View File

@@ -35,6 +35,7 @@ function NoteToolbar(props: NoteToolbarProps) {
return (
<ToolbarBase
style={styles.root}
scrollable={false}
items={props.toolbarButtonInfos}
disabled={props.disabled}
aria-label={_('Note')}

View File

@@ -35,7 +35,10 @@ export default (props: Props) => {
};
notyfContext.open(options);
}, [toast.message, toast.duration, toast.type, notyfContext]);
// toast.timestamp needs to be included in the dependency list to allow
// showing multiple toasts with the same message, one after another.
// See https://github.com/laurent22/joplin/issues/11783
}, [toast.message, toast.duration, toast.type, toast.timestamp, notyfContext]);
return <div style={{ display: 'none' }}/>;
};

View File

@@ -1,13 +1,13 @@
import * as React from 'react';
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme';
import time from '@joplin/lib/time';
const Datetime = require('react-datetime').default;
import CreatableSelect from 'react-select/creatable';
import Select from 'react-select';
import makeAnimated from 'react-select/animated';
import { focus } from '@joplin/lib/utils/focusHandler';
import Dialog from './Dialog';
import { ChangeEvent } from 'react';
import { formatDateTimeLocalToMs, isValidDate } from '@joplin/utils/time';
interface Props {
themeId: number;
@@ -204,16 +204,14 @@ export default class PromptDialog extends React.Component<Props, any> {
if (this.props.onClose) {
let outputAnswer = this.state.answer;
if (this.props.inputType === 'datetime') {
// outputAnswer = anythingToDate(outputAnswer);
outputAnswer = time.anythingToDateTime(outputAnswer);
outputAnswer = isValidDate(outputAnswer) ? formatDateTimeLocalToMs(outputAnswer) : null;
}
this.props.onClose(accept ? outputAnswer : null, buttonType);
}
this.setState({ visible: false, answer: '' });
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onChange = (event: any) => {
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ answer: event.target.value });
};
@@ -226,11 +224,6 @@ export default class PromptDialog extends React.Component<Props, any> {
// return m.isValid() ? m.toDate() : null;
// }
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onDateTimeChange = (momentObject: any) => {
this.setState({ answer: momentObject });
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onSelectChange = (newValue: any) => {
this.setState({ answer: newValue });
@@ -258,8 +251,13 @@ export default class PromptDialog extends React.Component<Props, any> {
let inputComp = null;
if (this.props.inputType === 'datetime') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
inputComp = <Datetime className="datetime-picker" value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={(momentObject: any) => onDateTimeChange(momentObject)} />;
inputComp = <input
defaultValue={this.state.answer}
onChange={onChange}
type="datetime-local"
className='datetime-picker'
style={styles.input}
/>;
} else if (this.props.inputType === 'tags') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
inputComp = <CreatableSelect className="tag-selector" onMenuOpen={this.select_menuOpen} onMenuClose={this.select_menuClose} styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;

View File

@@ -0,0 +1,69 @@
import * as React from 'react';
import { Resizable, ResizeCallback, ResizeStartCallback, Size } from 're-resizable';
import { LayoutItem } from './utils/types';
import { itemMinHeight, itemMinWidth, itemSize, LayoutItemSizes } from './utils/useLayoutItemSizes';
interface Props {
item: LayoutItem;
parent: LayoutItem|null;
sizes: LayoutItemSizes;
resizedItemMaxSize: Size|null;
onResizeStart: ResizeStartCallback;
onResize: ResizeCallback;
onResizeStop: ResizeCallback;
children: React.ReactNode;
isLastChild: boolean;
visible: boolean;
}
const LayoutItemContainer: React.FC<Props> = ({
item, visible, parent, sizes, resizedItemMaxSize, onResize, onResizeStart, onResizeStop, children, isLastChild,
}) => {
const style: React.CSSProperties = {
display: visible ? 'flex' : 'none',
flexDirection: item.direction,
};
const size: Size = itemSize(item, parent, sizes, true);
const className = `resizableLayoutItem rli-${item.key}`;
if (item.resizableRight || item.resizableBottom) {
const enable = {
top: false,
right: !!item.resizableRight && !isLastChild,
bottom: !!item.resizableBottom && !isLastChild,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
};
return (
<Resizable
key={item.key}
className={className}
style={style}
size={size}
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
enable={enable}
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
maxWidth={resizedItemMaxSize?.width}
maxHeight={resizedItemMaxSize?.height}
>
{children}
</Resizable>
);
} else {
return (
<div key={item.key} className={className} style={{ ...style, ...size }}>
{children}
</div>
);
}
};
export default LayoutItemContainer;

View File

@@ -1,8 +1,9 @@
import * as React from 'react';
import { useCallback } from 'react';
import { useCallback, useId } from 'react';
import Button, { ButtonLevel } from '../Button/Button';
import { MoveDirection } from './utils/movements';
import styled from 'styled-components';
import { _ } from '@joplin/lib/locale';
const StyledRoot = styled.div`
display: flex;
@@ -10,6 +11,11 @@ const StyledRoot = styled.div`
padding: 5px;
background-color: ${props => props.theme.backgroundColor};
border-radius: 5px;
> .label {
// Used only for accessibility tools
display: none;
}
`;
const ButtonRow = styled.div`
@@ -26,23 +32,32 @@ const ArrowButton = styled(Button)`
opacity: ${props => props.disabled ? 0.2 : 1};
`;
type ButtonKey = string;
export interface MoveButtonClickEvent {
direction: MoveDirection;
itemKey: string;
buttonKey: ButtonKey;
}
interface Props {
onClick(event: MoveButtonClickEvent): void;
itemKey: string;
itemLabel: string;
canMoveLeft: boolean;
canMoveRight: boolean;
canMoveUp: boolean;
canMoveDown: boolean;
// Specifies which button to auto-focus (if any). Clicking a "Move ..." button changes the app's layout. By default, this
// causes focus to jump to the start of the move dialog. Providing the key of the last-clicked button allows focus
// to be restored after changing the app layout:
autoFocusKey: ButtonKey|null;
}
export default function MoveButtons(props: Props) {
const onButtonClick = useCallback((direction: MoveDirection) => {
props.onClick({ direction, itemKey: props.itemKey });
props.onClick({ direction, itemKey: props.itemKey, buttonKey: `${props.itemKey}-${direction}` });
}, [props.onClick, props.itemKey]);
function canMove(dir: MoveDirection) {
@@ -53,28 +68,64 @@ export default function MoveButtons(props: Props) {
throw new Error('Unreachable');
}
const iconLabel = (dir: MoveDirection) => {
if (dir === MoveDirection.Up) return _('Move up');
if (dir === MoveDirection.Down) return _('Move down');
if (dir === MoveDirection.Left) return _('Move left');
if (dir === MoveDirection.Right) return _('Move right');
const unreachable: never = dir;
throw new Error(`Invalid direction: ${unreachable}`);
};
const descriptionId = useId();
const buttonKey = (dir: MoveDirection) => `${props.itemKey}-${dir}`;
const autoFocusDirection = (() => {
if (!props.autoFocusKey) return undefined;
const buttonDirections = [MoveDirection.Up, MoveDirection.Down, MoveDirection.Left, MoveDirection.Right];
const autoFocusDirection = buttonDirections.find(
direction => buttonKey(direction) === props.autoFocusKey,
);
if (!autoFocusDirection) {
return null;
}
const autoFocusDirectionEnabled = autoFocusDirection && canMove(autoFocusDirection);
if (autoFocusDirectionEnabled) {
return autoFocusDirection;
} else {
// Select an enabled direction instead
return buttonDirections.find(dir => canMove(dir));
}
})();
function renderButton(dir: MoveDirection) {
return <ArrowButton
disabled={!canMove(dir)}
level={ButtonLevel.Primary}
iconName={`fas fa-arrow-${dir}`}
iconLabel={iconLabel(dir)}
autoFocus={autoFocusDirection === dir}
onClick={() => onButtonClick(dir)}
/>;
}
return (
<StyledRoot>
<StyledRoot role='group' aria-labelledby={descriptionId}>
<ButtonRow>
{renderButton(MoveDirection.Up)}
</ButtonRow>
<ButtonRow>
{renderButton(MoveDirection.Left)}
<EmptyButton iconName="fas fa-arrow-down" disabled={true}/>
<EmptyButton iconName="fas fa-arrow-down" aria-hidden={true} disabled={true}/>
{renderButton(MoveDirection.Right)}
</ButtonRow>
<ButtonRow>
{renderButton(MoveDirection.Down)}
</ButtonRow>
<div className='label' id={descriptionId}>{props.itemLabel}</div>
</StyledRoot>
);
}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useRef, useState, useEffect } from 'react';
import { useRef, useState, useEffect, useCallback } from 'react';
import useWindowResizeEvent from './utils/useWindowResizeEvent';
import setLayoutItemProps from './utils/setLayoutItemProps';
import useLayoutItemSizes, { LayoutItemSizes, itemSize, calculateMaxSizeAvailableForItem, itemMinWidth, itemMinHeight } from './utils/useLayoutItemSizes';
@@ -7,16 +7,26 @@ import validateLayout from './utils/validateLayout';
import { Size, LayoutItem } from './utils/types';
import { canMove, MoveDirection } from './utils/movements';
import MoveButtons, { MoveButtonClickEvent } from './MoveButtons';
import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRootMessage } from './utils/style';
import { Resizable } from 're-resizable';
const EventEmitter = require('events');
import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootMessage } from './utils/style';
import type { ResizeCallback, ResizeStartCallback } from 're-resizable';
import Dialog from '../Dialog';
import * as EventEmitter from 'events';
import LayoutItemContainer from './LayoutItemContainer';
interface OnResizeEvent {
layout: LayoutItem;
}
interface ResizedItem {
key: string;
initialWidth: number;
initialHeight: number;
maxSize: Size;
}
interface Props {
layout: LayoutItem;
layoutKeyToLabel: (key: string)=> string;
onResize(event: OnResizeEvent): void;
width?: number;
height?: number;
@@ -33,101 +43,57 @@ function itemVisible(item: LayoutItem, moveMode: boolean) {
return item.visible !== false;
}
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, resizedItemMaxSize: Size | null, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const style: any = {
display: itemVisible(item, moveMode) ? 'flex' : 'none',
flexDirection: item.direction,
};
const size: Size = itemSize(item, parent, sizes, true);
const className = `resizableLayoutItem rli-${item.key}`;
if (item.resizableRight || item.resizableBottom) {
const enable = {
top: false,
right: !!item.resizableRight && !isLastChild,
bottom: !!item.resizableBottom && !isLastChild,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
};
return (
<Resizable
key={item.key}
className={className}
style={style}
size={size}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onResizeStart={onResizeStart as any}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onResize={onResize as any}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onResizeStop={onResizeStop as any}
enable={enable}
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
maxWidth={resizedItemMaxSize?.width}
maxHeight={resizedItemMaxSize?.height}
>
{children}
</Resizable>
);
} else {
return (
<div key={item.key} className={className} style={{ ...style, ...size }}>
{children}
</div>
);
}
}
function ResizableLayout(props: Props) {
const eventEmitter = useRef(new EventEmitter());
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const [resizedItem, setResizedItem] = useState<any>(null);
const [resizedItem, setResizedItem] = useState<ResizedItem|null>(null);
const lastUsedMoveButtonKey = useRef<string|null>(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function renderItemWrapper(comp: any, item: LayoutItem, parent: LayoutItem | null, size: Size, moveMode: boolean) {
const moveOverlay = moveMode ? (
<StyledMoveOverlay>
<MoveButtons
itemKey={item.key}
onClick={props.onMoveButtonClick}
canMoveLeft={canMove(MoveDirection.Left, item, parent)}
canMoveRight={canMove(MoveDirection.Right, item, parent)}
canMoveUp={canMove(MoveDirection.Up, item, parent)}
canMoveDown={canMove(MoveDirection.Down, item, parent)}
/>
</StyledMoveOverlay>
) : null;
const onMoveButtonClick = useCallback((event: MoveButtonClickEvent) => {
lastUsedMoveButtonKey.current = event.buttonKey;
props.onMoveButtonClick(event);
}, [props.onMoveButtonClick]);
const renderMoveControls = (item: LayoutItem, parent: LayoutItem | null, size: Size) => {
return (
<StyledWrapperRoot key={item.key} size={size}>
<StyledMoveOverlay>
<MoveButtons
autoFocusKey={lastUsedMoveButtonKey.current}
itemKey={item.key}
itemLabel={props.layoutKeyToLabel(item.key)}
onClick={onMoveButtonClick}
canMoveLeft={canMove(MoveDirection.Left, item, parent)}
canMoveRight={canMove(MoveDirection.Right, item, parent)}
canMoveUp={canMove(MoveDirection.Up, item, parent)}
canMoveDown={canMove(MoveDirection.Down, item, parent)}
/>
</StyledMoveOverlay>
</StyledWrapperRoot>
);
};
function renderItemWrapper(comp: React.ReactNode, item: LayoutItem, size: Size) {
return (
<StyledWrapperRoot key={item.key} size={size}>
{moveOverlay}
{comp}
</StyledWrapperRoot>
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function renderLayoutItem(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, isVisible: boolean, isLastChild: boolean): any {
function onResizeStart() {
function renderLayoutItem(
item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, isVisible: boolean, isLastChild: boolean, onlyMoveControls: boolean,
): React.ReactNode {
const onResizeStart: ResizeStartCallback = () => {
setResizedItem({
key: item.key,
initialWidth: sizes[item.key].width,
initialHeight: sizes[item.key].height,
maxSize: calculateMaxSizeAvailableForItem(item, parent, sizes),
});
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function onResize(_event: any, direction: string, _refToElement: any, delta: any) {
const onResize: ResizeCallback = (_event, direction, _refToElement, delta) => {
const newWidth = Math.max(itemMinWidth, resizedItem.initialWidth + delta.width);
const newHeight = Math.max(itemMinHeight, resizedItem.initialHeight + delta.height);
@@ -147,15 +113,18 @@ function ResizableLayout(props: Props) {
props.onResize({ layout: newLayout });
eventEmitter.current.emit('resize');
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function onResizeStop(_event: any, _direction: any, _refToElement: any, delta: any) {
const onResizeStop: ResizeCallback = (_event, _direction, _refToElement, delta) => {
onResize(_event, _direction, _refToElement, delta);
setResizedItem(null);
}
};
const resizedItemMaxSize = resizedItem && item.key === resizedItem.key ? resizedItem.maxSize : null;
const visible = itemVisible(item, props.moveMode);
const itemContainerProps = {
key: item.key, item, parent, sizes, resizedItemMaxSize, onResizeStart, onResizeStop, onResize, isLastChild, visible,
};
if (!item.children) {
const size = itemSize(item, parent, sizes, false);
@@ -166,17 +135,22 @@ function ResizableLayout(props: Props) {
visible: isVisible,
});
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
const wrapper = onlyMoveControls ? renderMoveControls(item, parent, size) : renderItemWrapper(comp, item, size);
return <LayoutItemContainer {...itemContainerProps}>
{wrapper}
</LayoutItemContainer>;
} else {
const childrenComponents = [];
for (let i = 0; i < item.children.length; i++) {
const child = item.children[i];
childrenComponents.push(renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1));
childrenComponents.push(
renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1, onlyMoveControls),
);
}
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
return <LayoutItemContainer {...itemContainerProps}>
{childrenComponents}
</LayoutItemContainer>;
}
}
@@ -187,22 +161,24 @@ function ResizableLayout(props: Props) {
useWindowResizeEvent(eventEmitter);
const sizes = useLayoutItemSizes(props.layout, props.moveMode);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
function renderMoveModeBox(rootComp: any) {
return (
<MoveModeRootWrapper>
const renderRoot = (moveControlsOnly: boolean) => {
return renderLayoutItem(props.layout, null, sizes, itemVisible(props.layout, props.moveMode), true, moveControlsOnly);
};
function renderMoveModeBox() {
return <div>
<Dialog contentFillsScreen={true} className='change-app-layout-dialog'>
<MoveModeRootMessage>{props.moveModeMessage}</MoveModeRootMessage>
{rootComp}
</MoveModeRootWrapper>
);
{renderRoot(true)}
</Dialog>
{renderRoot(false)}
</div>;
}
const rootComp = renderLayoutItem(props.layout, null, sizes, itemVisible(props.layout, props.moveMode), true);
if (props.moveMode) {
return renderMoveModeBox(rootComp);
return renderMoveModeBox();
} else {
return rootComp;
return renderRoot(false);
}
}

View File

@@ -28,18 +28,12 @@ export const StyledMoveOverlay = styled.div`
height: 100%;
`;
export const MoveModeRootWrapper = styled.div`
position:relative;
display: flex;
align-items: center;
justify-content: center;
`;
export const MoveModeRootMessage = styled.div`
position:absolute;
export const MoveModeRootMessage = styled.h1`
position: absolute;
bottom: 10px;
font-size: 1em;
z-index:200;
background-color: ${props => props.theme.backgroundColor};
padding: 10px;
border-radius: 5;
`;

View File

@@ -116,7 +116,7 @@ const ResourceTableComp = (props: ResourceTable) => {
<tbody>
{filteredResources.map((resource: InnerResource, index: number) =>
<tr key={index}>
<td style={titleCellStyle} className="titleCell">
<td id={`title-${resource.id}`} style={titleCellStyle} className="titleCell">
<a
style={{ color: theme.urlColor }}
href="#"
@@ -126,7 +126,14 @@ const ResourceTableComp = (props: ResourceTable) => {
<td style={cellStyle} className="dataCell">{prettyBytes(resource.size)}</td>
<td style={cellStyle} className="dataCell">{resource.id}</td>
<td style={cellStyle} className="dataCell">
<button style={theme.buttonStyle} onClick={() => props.onResourceDelete(resource)}>{_('Delete')}</button>
<button
id={`delete-${resource.id}`}
aria-labelledby={`delete-${resource.id} title-${resource.id}`}
style={theme.buttonStyle}
onClick={() => props.onResourceDelete(resource)}
>
{_('Delete')}
</button>
</td>
</tr>,
)}

View File

@@ -174,6 +174,7 @@ function SearchBar(props: Props) {
onKeyDown={onKeyDown}
onSearchButtonClick={onSearchButtonClick}
searchStarted={searchStarted}
aria-controls='notes-list'
/>
</Root>
);

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyledRoot, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton, StyledRoot } from './styles';
import { ButtonLevel } from '../Button/Button';
import CommandService from '@joplin/lib/services/CommandService';
import Synchronizer from '@joplin/lib/Synchronizer';
@@ -74,7 +74,7 @@ const SidebarComponent = (props: Props) => {
);
return (
<StyledRoot className="sidebar" role='navigation' aria-label={_('Sidebar')}>
<StyledRoot className='sidebar _scrollbar2' role='navigation' aria-label={_('Sidebar')}>
<div style={{ flex: 1 }}><FolderAndTagList/></div>
<div style={{ flex: 0, padding: theme.mainPadding }}>
{syncReportComp}

View File

@@ -54,10 +54,6 @@ const useOnSidebarKeyDownHandler = (props: Props) => {
indexChange = -1;
} else if (event.code === 'ArrowDown') {
indexChange = 1;
} else if (event.code === 'Tab' && event.shiftKey) {
event.preventDefault();
void CommandService.instance().execute('focusElement', 'noteBody');
} else if (event.code === 'Enter' && !event.shiftKey) {
event.preventDefault();
void CommandService.instance().execute('focusElement', 'noteList');

View File

@@ -61,7 +61,12 @@ export const StyledListItemAnchor = styled.a`
text-decoration: none;
color: ${(props: StyleProps) => listItemTextColor(props)};
cursor: default;
opacity: ${(props: StyleProps) => props.selected || props.shareId ? 1 : 0.8};
opacity: ${(props: StyleProps) => {
// So that the conflicts folder and shared folders have sufficient contrast,
// use an opacity of 1 even when unselected.
const needsHigherContrast = props.isConflictFolder || props.isSpecialItem;
return (props.selected || props.shareId || needsHigherContrast) ? 1 : 0.8;
}};
white-space: nowrap;
display: flex;
flex: 1;

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useRef, useCallback } from 'react';
import { useRef, useCallback, useId } from 'react';
import { _ } from '@joplin/lib/locale';
import DialogButtonRow from '../DialogButtonRow';
import Dialog from '../Dialog';
@@ -49,7 +49,7 @@ const SyncTargetBoxes = styled.div`
justify-content: center;
`;
const SyncTargetTitle = styled.p`
const SyncTargetTitle = styled.h2`
display: flex;
flex-direction: row;
font-weight: bold;
@@ -78,8 +78,11 @@ const SyncTargetBox = styled.div`
opacity: 1;
`;
const FeatureList = styled.div`
const FeatureList = styled.ul`
margin-bottom: 1em;
list-style-type: none;
padding: 0;
`;
const FeatureIcon = styled.i`
@@ -90,7 +93,7 @@ const FeatureIcon = styled.i`
position: absolute;
`;
const FeatureLine = styled.div<{ enabled: boolean }>`
const FeatureLine = styled.li<{ enabled: boolean }>`
margin-bottom: .5em;
opacity: ${props => props.enabled ? 1 : 0.5};
position: relative;
@@ -156,7 +159,10 @@ export default function(props: Props) {
function renderFeature(enabled: boolean, label: string) {
const className = enabled ? 'fas fa-check' : 'fas fa-times';
return (
<FeatureLine enabled={enabled} key={label}><FeatureIcon className={className}></FeatureIcon> <FeatureLabel>{label}</FeatureLabel></FeatureLine>
<FeatureLine enabled={enabled} key={label}>
<FeatureIcon className={className} role='img' aria-label={enabled ? _('Check') : _('Not checked')}/>
<FeatureLabel>{label}</FeatureLabel>
</FeatureLine>
);
}
@@ -190,13 +196,16 @@ export default function(props: Props) {
});
}, [props.dispatch, closeDialog]);
function renderSelectArea(info: SyncTargetInfo) {
const baseId = useId();
function renderSelectArea(info: SyncTargetInfo, describedById: string) {
return (
<SelectButton
level={ButtonLevel.Primary}
title={_('Select')}
onClick={() => onSelectButtonClick(info.name as SyncTargetInfoName)}
disabled={false}
aria-describedby={describedById}
/>
);
}
@@ -207,8 +216,14 @@ export default function(props: Props) {
const logoImageName = logosImageNames[info.name];
const logoImageSrc = logoImageName ? `${bridge().buildDir()}/images/${logoImageName}` : '';
const logo = logoImageSrc ? <SyncTargetLogo src={logoImageSrc}/> : null;
const descriptionComp = <SyncTargetDescription height={height} ref={info.name === 'joplinCloud' ? joplinCloudDescriptionRef : null}>{info.description}</SyncTargetDescription>;
const logo = logoImageSrc ? <SyncTargetLogo src={logoImageSrc} aria-hidden={true}/> : null;
const descriptionComp = (
<SyncTargetDescription
height={height}
ref={info.name === 'joplinCloud' ? joplinCloudDescriptionRef : null}
>{info.description}</SyncTargetDescription>
);
const featuresComp = renderFeatures(info.name);
const renderSlowSyncWarning = () => {
@@ -216,12 +231,13 @@ export default function(props: Props) {
return <SlowSyncWarning>{`⚠️ ${_('%s is not optimised for synchronising many small files so your initial synchronisation will be slow.', info.label)}`}</SlowSyncWarning>;
};
const headerId = `${baseId}-${info.id}`;
return (
<SyncTargetBox id={key} key={key}>
<SyncTargetTitle>{logo}{info.label}</SyncTargetTitle>
<SyncTargetTitle id={headerId}>{logo}{info.label}</SyncTargetTitle>
{descriptionComp}
{featuresComp}
{renderSelectArea(info)}
{renderSelectArea(info, headerId)}
{renderSlowSyncWarning()}
</SyncTargetBox>
);
@@ -249,7 +265,26 @@ export default function(props: Props) {
boxes.push(renderSyncTarget(info));
}
const selfHostingMessage = <SelfHostingMessage>Self-hosting? Joplin also supports various self-hosting options such as Nextcloud, WebDAV, AWS S3 and Joplin Server. <a href="#" onClick={onSelfHostingClick}>Click here to select one</a>.</SelfHostingMessage>;
const selfHostingLabelId = `${baseId}-selfHosting`;
const selfHostingLinkId = `${baseId}-selfHostingLink`;
const selfHostingMessage = <SelfHostingMessage>
<span id={selfHostingLabelId}>
Self-hosting? Joplin also supports various self-hosting options such as Nextcloud, WebDAV, AWS S3 and Joplin Server.
</span>
{' '}
<a
href="#"
onClick={onSelfHostingClick}
// Include the link ID in aria-labelledby to include the link text in the
// description. See
// https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA7
id={selfHostingLinkId}
aria-labelledby={`${selfHostingLabelId} ${selfHostingLinkId}`}
>
Click here to select one
</a>.
</SelfHostingMessage>;
return (
<ContentRoot>

View File

@@ -10,10 +10,12 @@ import { focus } from '@joplin/lib/utils/focusHandler';
interface Props {
themeId: number;
scrollable: boolean;
style: React.CSSProperties;
items: ToolbarItem[];
disabled: boolean;
'aria-label': string;
id?: string;
}
const getItemType = (item: ToolbarItem) => {
@@ -177,9 +179,10 @@ const ToolbarBaseComponent: React.FC<Props> = props => {
return (
<div
ref={containerRef}
className='editor-toolbar'
className={`editor-toolbar ${props.scrollable ? '-scrollable' : ''}`}
style={props.style}
id={props.id ?? undefined}
role='toolbar'
aria-label={props['aria-label']}
@@ -191,7 +194,8 @@ const ToolbarBaseComponent: React.FC<Props> = props => {
<div className='group'>
{centerItemComps}
</div>
<div className='group -right'>
<div className='spacer' />
<div className='group'>
{rightItemComps}
</div>
</div>

View File

@@ -4,6 +4,7 @@ import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer';
import Note from '@joplin/lib/models/Note';
import time from '@joplin/lib/time';
import { formatMsToDateTimeLocal } from '@joplin/utils/time';
import { NoteEntity } from '@joplin/lib/services/database/types';
export const declaration: CommandDeclaration = {
@@ -29,7 +30,7 @@ export const runtime = (comp: any): CommandRuntime => {
label: _('Set alarm:'),
inputType: 'datetime',
buttons: ['ok', 'cancel', 'clear'],
value: note.todo_due ? new Date(note.todo_due) : defaultDate,
value: note.todo_due ? formatMsToDateTimeLocal(note.todo_due) : formatMsToDateTimeLocal(defaultDate.getTime()),
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onClose: async (answer: any, buttonType: string) => {
let newNote: NoteEntity = null;
@@ -42,7 +43,7 @@ export const runtime = (comp: any): CommandRuntime => {
} else if (answer !== null) {
newNote = {
id: note.id,
todo_due: answer.getTime(),
todo_due: answer,
};
}

View File

@@ -28,7 +28,6 @@ import * as restoreNote from './restoreNote';
import * as revealResourceFile from './revealResourceFile';
import * as search from './search';
import * as setTags from './setTags';
import * as showEditorPlugin from './showEditorPlugin';
import * as showModalMessage from './showModalMessage';
import * as showNoteContentProperties from './showNoteContentProperties';
import * as showNoteProperties from './showNoteProperties';
@@ -36,7 +35,6 @@ import * as showPrompt from './showPrompt';
import * as showShareFolderDialog from './showShareFolderDialog';
import * as showShareNoteDialog from './showShareNoteDialog';
import * as showSpellCheckerMenu from './showSpellCheckerMenu';
import * as toggleEditorPlugin from './toggleEditorPlugin';
import * as toggleEditors from './toggleEditors';
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
import * as toggleMenuBar from './toggleMenuBar';
@@ -78,7 +76,6 @@ const index: any[] = [
revealResourceFile,
search,
setTags,
showEditorPlugin,
showModalMessage,
showNoteContentProperties,
showNoteProperties,
@@ -86,7 +83,6 @@ const index: any[] = [
showShareFolderDialog,
showShareNoteDialog,
showSpellCheckerMenu,
toggleEditorPlugin,
toggleEditors,
toggleLayoutMoveMode,
toggleMenuBar,

View File

@@ -1,6 +1,6 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import dialogs from '../../dialogs';
import shim from '@joplin/lib/shim';
export const declaration: CommandDeclaration = {
name: 'resetLayout',
@@ -12,7 +12,7 @@ export const runtime = (): CommandRuntime => {
execute: async (context: CommandContext) => {
const message = _('Are you sure you want to return to the default layout? The current layout configuration will be lost.');
const isConfirmed = await dialogs.confirm(message);
const isConfirmed = await shim.showConfirmationDialog(message);
if (!isConfirmed) return;

View File

@@ -51,6 +51,7 @@ interface Props {
searchStarted: boolean;
placeholder?: string;
disabled?: boolean;
'aria-controls'?: string;
}
export interface OnChangeEvent {
@@ -71,7 +72,7 @@ export default function(props: Props) {
<SearchInput
ref={props.inputRef}
value={props.value}
type="text"
type="search"
placeholder={props.placeholder || _('Search...')}
onChange={onChange}
onFocus={props.onFocus}
@@ -79,6 +80,7 @@ export default function(props: Props) {
onKeyDown={props.onKeyDown}
spellCheck={false}
disabled={props.disabled}
aria-controls={props['aria-controls']}
/>
<SearchButton
aria-label={iconLabel}

View File

@@ -7,6 +7,7 @@ export default function() {
'focusElementNoteList',
'focusElementNoteTitle',
'focusElementSideBar',
'focusElementToolbar',
'focusSearch',
'historyBackward',
'historyForward',
@@ -32,6 +33,7 @@ export default function() {
'textSelectAll',
'textBulletedList',
'toggleExternalEditing',
'openNoteInNewWindow',
'toggleLayoutMoveMode',
'resetLayout',
'toggleMenuBar',
@@ -41,6 +43,7 @@ export default function() {
'togglePerFolderSortOrder',
'toggleSideBar',
'toggleVisiblePanes',
'toggleTabMovesFocus',
'editor.deleteLine',
'editor.duplicateLine',
// We cannot put the undo/redo commands in the menu because they are

View File

@@ -0,0 +1,14 @@
.change-app-layout-dialog {
padding: 0;
> .content {
position:relative;
display: flex;
align-items: center;
justify-content: center;
}
&::backdrop {
background-color: transparent !important;
}
}

View File

@@ -0,0 +1,12 @@
.combobox-suggestion-option {
height: 26px;
box-sizing: border-box;
padding: 5px;
border-bottom: 1px solid var(--joplin-border-color4);
cursor: pointer;
&:hover, &.-selected {
color: var(--joplin-background-color);
background-color: var(--joplin-color);
}
}

View File

@@ -0,0 +1,16 @@
.combobox-wrapper {
> .suggestions {
background-color: var(--joplin-background-color);
width: 50%;
min-width: 20em;
border: 1px solid var(--joplin-border-color4);
border-radius: 5px;
margin-top: 10px;
display: none;
}
&.-expanded > .suggestions {
display: block;
}
}

View File

@@ -32,13 +32,16 @@
}
&.-fullscreen {
max-width: 100vw;
max-height: 100vh;
&::backdrop {
background-color: var(--joplin-background-color);
}
> .content {
width: calc(100% - 20px);
padding: 10px;
margin: 0;
padding: 0;
border-radius: 0;
box-shadow: none;
background-color: transparent;

View File

@@ -7,15 +7,21 @@
padding: var(--joplin-toolbar-padding);
padding-right: var(--joplin-main-padding);
&.-scrollable {
overflow-x: auto;
}
> .group {
display: flex;
flex-direction: row;
box-sizing: border-box;
min-width: 0;
&.-right {
flex: 1;
justify-content: flex-end;
}
min-width: 0;
flex-shrink: 0;
}
> .spacer {
flex-grow: 1;
min-width: 7px;
}
}

View File

@@ -11,3 +11,6 @@
@use './dialog-anchor-node.scss';
@use './note-editor-wrapper.scss';
@use './text-input.scss';
@use './combobox-wrapper.scss';
@use './combobox-suggestion-option.scss';
@use './change-app-layout-dialog.scss';

View File

@@ -0,0 +1,158 @@
import NoteListUtils from './NoteListUtils';
import KeymapService from '@joplin/lib/services/KeymapService';
import menuCommandNames from '../menuCommandNames';
import { MenuItem as MenuItemType } from '@joplin/lib/services/commands/MenuUtils';
import initializeCommandService from '../../utils/initializeCommandService';
import { createAppDefaultWindowState } from '../../app.reducer';
type MenuItemWrapper = {
value: MenuItemType;
};
jest.mock('../../services/bridge', () => ({
__esModule: true,
default: () => ({
MenuItem: class MenuItem {
public value: MenuItemType;
public constructor(value: MenuItemType) {
this.value = value;
}
},
Menu: class MockMenu {
public items: string[] = [];
public append(item: MenuItemWrapper) {
const identifier = item.value.id ? item.value.id : (
item.value.label ? item.value.label : item.value.type
);
this.items.push(identifier);
}
},
}),
}));
const mockDispatch = jest.fn();
describe('NoteListUtils', () => {
beforeEach(() => {
const mockStore = {
getState: () => {
return {
...createAppDefaultWindowState(),
settings: {},
};
},
};
initializeCommandService(mockStore, false);
const keymapService = KeymapService.instance();
keymapService.initialize(menuCommandNames());
});
it('should show only trash menu options on deleted note', () => {
const noteIds = ['noteId1'];
const deletedNote = {
id: 'noteId1',
deleted_time: new Date().getTime(),
};
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
deletedNote,
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([
'restoreNote',
'permanentlyDeleteNote',
]);
});
it('should show menu options for normal notes', () => {
const noteIds = ['noteId1'];
const normalNote = {
id: 'noteId1',
};
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
normalNote,
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([
'openNoteInNewWindow',
'startExternalEditing',
'separator',
'setTags',
'separator',
'toggleNoteType',
'moveToFolder',
'duplicateNote',
'deleteNote',
'separator',
'Copy Markdown link',
'Copy external link',
'separator',
'Export',
]);
});
it('should show options when more than one note is selected', () => {
const noteIds = ['noteId1', 'noteId2'];
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
{ id: 'noteId1' },
{ id: 'noteId2' },
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([
'setTags',
'separator',
'Switch to note type',
'Switch to to-do type',
'moveToFolder',
'duplicateNote',
'deleteNote',
'separator',
'Copy Markdown link',
'separator',
'Export',
]);
});
it('should hide all options for encrypted', () => {
const noteIds = ['noteId1'];
const encrypted = {
id: 'noteId1',
encryption_applied: 1,
};
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
encrypted,
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([]);
});
});

View File

@@ -42,34 +42,24 @@ export default class NoteListUtils {
const menu = new Menu();
if (!includeEncryptedNotes && !includeDeletedNotes) {
if (singleNoteId) {
menu.append(
new MenuItem(menuUtils.commandToStatefulMenuItem('openNoteInNewWindow', singleNoteId)),
);
const cmd = props.watchedNoteFiles.includes(singleNoteId) ? 'stopExternalEditing' : 'startExternalEditing';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem(cmd, singleNoteId) as any));
menu.append(new MenuItem({ type: 'separator' }));
}
menu.append(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
new MenuItem(menuUtils.commandToStatefulMenuItem('setTags', noteIds) as any),
);
menu.append(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
new MenuItem(menuUtils.commandToStatefulMenuItem('moveToFolder', noteIds) as any),
);
menu.append(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
new MenuItem(menuUtils.commandToStatefulMenuItem('duplicateNote', noteIds) as any),
);
if (singleNoteId) {
const editInMenu = new Menu();
const cmd = props.watchedNoteFiles.includes(singleNoteId) ? 'stopExternalEditing' : 'startExternalEditing';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
editInMenu.append(new MenuItem(menuUtils.commandToStatefulMenuItem(cmd, singleNoteId) as any));
editInMenu.append(
new MenuItem(menuUtils.commandToStatefulMenuItem('openNoteInNewWindow', singleNoteId)),
);
menu.append(new MenuItem({ label: _('Edit in...'), submenu: editInMenu }));
}
menu.append(new MenuItem({ type: 'separator' }));
if (noteIds.length <= 1) {
menu.append(
@@ -107,6 +97,25 @@ export default class NoteListUtils {
);
}
menu.append(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
new MenuItem(menuUtils.commandToStatefulMenuItem('moveToFolder', noteIds) as any),
);
menu.append(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
new MenuItem(menuUtils.commandToStatefulMenuItem('duplicateNote', noteIds) as any),
);
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menuUtils.commandToStatefulMenuItem('deleteNote', noteIds) as any,
),
);
menu.append(new MenuItem({ type: 'separator' }));
menu.append(
new MenuItem({
label: _('Copy Markdown link'),
@@ -132,6 +141,8 @@ export default class NoteListUtils {
);
}
menu.append(new MenuItem({ type: 'separator' }));
if ([9, 10].includes(Setting.value('sync.target'))) {
menu.append(
new MenuItem(
@@ -191,15 +202,9 @@ export default class NoteListUtils {
menuUtils.commandToStatefulMenuItem('permanentlyDeleteNote', noteIds) as any,
),
);
} else {
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menuUtils.commandToStatefulMenuItem('deleteNote', noteIds) as any,
),
);
}
const pluginViewInfos = pluginUtils.viewInfosByType(props.plugins, 'menuItem');
for (const info of pluginViewInfos) {

View File

@@ -0,0 +1,23 @@
import { ElectronApplication, Locator, Page } from '@playwright/test';
import MainScreen from './MainScreen';
import activateMainMenuItem from '../util/activateMainMenuItem';
export default class ChangeAppLayoutScreen {
public readonly containerLocator: Locator;
public constructor(page: Page, private readonly mainScreen: MainScreen) {
this.containerLocator = page.locator('.change-app-layout-dialog[open]');
}
public async open(electronApp: ElectronApplication) {
await this.mainScreen.waitFor();
await activateMainMenuItem(electronApp, 'Change application layout');
return this.waitFor();
}
public async waitFor() {
await this.containerLocator.waitFor();
}
}

View File

@@ -0,0 +1,22 @@
import { Locator, Page } from '@playwright/test';
export default class EditorCodeDialog {
private readonly dialog: Locator;
public readonly textArea: Locator;
public readonly okButton: Locator;
public constructor(page: Page) {
this.dialog = page.getByRole('dialog', { name: 'Edit' });
this.textArea = this.dialog.locator('textarea');
this.okButton = this.dialog.getByRole('button', { name: 'OK' });
}
public async waitFor() {
await this.dialog.waitFor();
await this.textArea.waitFor();
}
public async submit() {
await this.okButton.click();
}
}

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