You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-30 20:39:46 +02:00
Compare commits
117 Commits
cli-v3.2.3
...
mobile_plu
Author | SHA1 | Date | |
---|---|---|---|
|
812666957c | ||
|
dabb7e08b4 | ||
|
91ac4f8526 | ||
|
3603350287 | ||
|
bcde346ebe | ||
|
9803d7985d | ||
|
30f6b3ecb2 | ||
|
0b287d1113 | ||
|
be18655ceb | ||
|
be43ff42c9 | ||
|
1230e1b30c | ||
|
f5d168b16a | ||
|
7055d3db18 | ||
|
18a9c3f841 | ||
|
a4ab197c42 | ||
|
2b5881a103 | ||
|
e9dee4cd99 | ||
|
dd948f5c95 | ||
|
798e1b8f4f | ||
|
b3f69794b1 | ||
|
f25e1a5e80 | ||
|
786e55c972 | ||
|
cd9155514c | ||
|
3e9e669642 | ||
|
e36a30eb1a | ||
|
1975ebd438 | ||
|
94bff77313 | ||
|
6e3258a5d8 | ||
|
c55c8d62ec | ||
|
c7031568a8 | ||
|
c2c72215b9 | ||
|
cc09f92d3b | ||
|
8312196faa | ||
|
a16a66c37b | ||
|
a8210225a0 | ||
|
cd50454664 | ||
|
986163721d | ||
|
e41dcb9bc9 | ||
|
f90e642f43 | ||
|
67d1dd36be | ||
|
2cba693905 | ||
|
a226ede5d7 | ||
|
7994c0bc79 | ||
|
d589891a86 | ||
|
fe6c949cc1 | ||
|
4e677d2baf | ||
|
25aab57af5 | ||
|
db81064c98 | ||
|
9b82578253 | ||
|
bb513c83ac | ||
|
662185816d | ||
|
cc1582d535 | ||
|
aa6348a127 | ||
|
68f4b8ed0c | ||
|
98540493e0 | ||
|
762daa5a68 | ||
|
827233605e | ||
|
31b13defb6 | ||
|
8611391d01 | ||
|
5a3d57e39a | ||
|
e22ccd6edf | ||
|
8aec0ae445 | ||
|
24a2f5452c | ||
|
d6f1ca4ba4 | ||
|
2a058ed809 | ||
|
877123bda7 | ||
|
d621e631f7 | ||
|
64d1da9773 | ||
|
2643bb9b32 | ||
|
5c737b3ccd | ||
|
23f75f8784 | ||
|
60b2f69620 | ||
|
1d00b7a68e | ||
|
d0b783c595 | ||
|
9c446b03da | ||
|
0603c56446 | ||
|
4223864302 | ||
|
0ddf5732a8 | ||
|
a5ffc11831 | ||
|
157ad2c0cd | ||
|
0ac710ecf9 | ||
|
d3046582e1 | ||
|
9642640cda | ||
|
dab2438df0 | ||
|
dc7871b655 | ||
|
ff465767ab | ||
|
c58aac9387 | ||
|
29e55b8231 | ||
|
dc10ff6215 | ||
|
e8e3ef36ed | ||
|
e1b41cff5f | ||
|
5782ee6ba1 | ||
|
cbf81d1257 | ||
|
c357b77a48 | ||
|
64c14fe76f | ||
|
b2c1d7a2ba | ||
|
dbd4cffef3 | ||
|
d190463325 | ||
|
2c1aa5d620 | ||
|
52d255352a | ||
|
76274033db | ||
|
92abfac3af | ||
|
c6956df1c9 | ||
|
0bd1e202a2 | ||
|
f602ad8a63 | ||
|
6a1b498e96 | ||
|
519f3f5898 | ||
|
907b1e969e | ||
|
b59774a763 | ||
|
0494719e4f | ||
|
0e1b81685a | ||
|
c157cd0cb3 | ||
|
5c711df2e4 | ||
|
e520a695a6 | ||
|
5ee8a9a454 | ||
|
6b73879512 | ||
|
f08235f05c |
@@ -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
30
.gitignore
vendored
@@ -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
|
||||
|
@@ -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();
|
@@ -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 => {
|
||||
|
@@ -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);
|
||||
|
BIN
Assets/WebsiteAssets/images/news/20250114-mobile-toolbar.png
Normal file
BIN
Assets/WebsiteAssets/images/news/20250114-mobile-toolbar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
BIN
Assets/WebsiteAssets/images/news/20250114-multi-window.png
Normal file
BIN
Assets/WebsiteAssets/images/news/20250114-multi-window.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/EssayPro.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/EssayPro.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/RealGambling.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/RealGambling.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/Slotozilla.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/Slotozilla.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
@@ -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> => <strong>Import</strong> => <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> => <strong>Advanced options</strong> => <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'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>
|
@@ -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>
|
||||
|
@@ -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>
|
@@ -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&mtm_kwd=joplinapp&mtm_source=joplinapp-webseite&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&mtm_kwd=joplinapp&mtm_source=joplinapp-webseite&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
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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",
|
||||
|
@@ -1 +1 @@
|
||||
Should keep this comment: <!-- keep this & that -->
|
||||
Should keep this comment: <!-- keep this & that -->
|
@@ -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"/>
|
||||
|
@@ -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">
|
||||
|
1
packages/app-cli/tests/md_to_html/sanitize_23.html
Normal file
1
packages/app-cli/tests/md_to_html/sanitize_23.html
Normal file
@@ -0,0 +1 @@
|
||||
<math class="jop-noMdConv"><p class="jop-noMdConv"><style class="jop-noMdConv"><!--</style><img src onerror=alert(1)>--></style>
|
1
packages/app-cli/tests/md_to_html/sanitize_23.md
Normal file
1
packages/app-cli/tests/md_to_html/sanitize_23.md
Normal file
@@ -0,0 +1 @@
|
||||
<math><p><style><!--</style><img src onerror=alert(1)>--></style>
|
@@ -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": {
|
||||
|
596
packages/app-clipper/popup/package-lock.json
generated
596
packages/app-clipper/popup/package-lock.json
generated
@@ -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": {
|
||||
|
@@ -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": [
|
||||
|
@@ -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
|
||||
|
@@ -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}`);
|
||||
},
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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',
|
||||
};
|
||||
|
||||
|
@@ -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',
|
||||
};
|
||||
|
||||
|
20
packages/app-desktop/commands/toggleTabMovesFocus.ts
Normal file
20
packages/app-desktop/commands/toggleTabMovesFocus.ts
Normal 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',
|
||||
};
|
||||
};
|
@@ -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()}
|
||||
|
@@ -17,7 +17,7 @@ interface Props {
|
||||
onApplyClick?: Function;
|
||||
}
|
||||
|
||||
export const StyledRoot = styled.div`
|
||||
const StyledRoot = styled.nav`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
|
@@ -164,7 +164,7 @@ export default function Sidebar(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot role='tablist'>
|
||||
<StyledRoot className='settings-sidebar _scrollbar2' role='tablist'>
|
||||
{buttons}
|
||||
</StyledRoot>
|
||||
);
|
||||
|
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
@use "./setting-description.scss";
|
||||
@use "./setting-label.scss";
|
||||
@use "./setting-header.scss";
|
||||
|
@@ -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};
|
||||
|
199
packages/app-desktop/gui/InlineCombobox.tsx
Normal file
199
packages/app-desktop/gui/InlineCombobox.tsx
Normal 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;
|
@@ -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.')}
|
||||
/>
|
||||
|
@@ -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,
|
||||
|
@@ -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 (
|
||||
|
@@ -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');
|
||||
|
||||
|
@@ -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')}
|
||||
|
@@ -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
|
||||
|
@@ -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 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();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
@@ -67,6 +67,7 @@ export default function(editor: any) {
|
||||
|
||||
editor.ui.registry.addGroupToolbarButton('formattingExtras', {
|
||||
icon: 'image-options',
|
||||
tooltip: _('Formatting'),
|
||||
items: items.join(' '),
|
||||
});
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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]);
|
||||
}
|
||||
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
||||
|
@@ -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'],
|
||||
|
88
packages/app-desktop/gui/NoteEditor/StatusBar.tsx
Normal file
88
packages/app-desktop/gui/NoteEditor/StatusBar.tsx
Normal 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);
|
@@ -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);
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
};
|
@@ -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,
|
||||
|
@@ -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";
|
||||
|
@@ -0,0 +1,13 @@
|
||||
|
||||
.editor-status-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> .spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> .status {
|
||||
align-self: end;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
14
packages/app-desktop/gui/NoteEditor/styles/tag-bar.scss
Normal file
14
packages/app-desktop/gui/NoteEditor/styles/tag-bar.scss
Normal 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;
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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>;
|
||||
}
|
||||
|
@@ -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]);
|
||||
};
|
||||
|
@@ -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) {
|
||||
|
@@ -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)}
|
||||
|
@@ -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);
|
||||
}, []);
|
||||
};
|
@@ -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,
|
||||
);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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}
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -35,6 +35,7 @@ function NoteToolbar(props: NoteToolbarProps) {
|
||||
return (
|
||||
<ToolbarBase
|
||||
style={styles.root}
|
||||
scrollable={false}
|
||||
items={props.toolbarButtonInfos}
|
||||
disabled={props.disabled}
|
||||
aria-label={_('Note')}
|
||||
|
@@ -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' }}/>;
|
||||
};
|
||||
|
@@ -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)} />;
|
||||
|
@@ -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;
|
@@ -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>
|
||||
);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
`;
|
||||
|
@@ -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>,
|
||||
)}
|
||||
|
@@ -174,6 +174,7 @@ function SearchBar(props: Props) {
|
||||
onKeyDown={onKeyDown}
|
||||
onSearchButtonClick={onSearchButtonClick}
|
||||
searchStarted={searchStarted}
|
||||
aria-controls='notes-list'
|
||||
/>
|
||||
</Root>
|
||||
);
|
||||
|
@@ -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}
|
||||
|
@@ -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');
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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}
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
16
packages/app-desktop/gui/styles/combobox-wrapper.scss
Normal file
16
packages/app-desktop/gui/styles/combobox-wrapper.scss
Normal 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
|
158
packages/app-desktop/gui/utils/NoteListUtils.test.ts
Normal file
158
packages/app-desktop/gui/utils/NoteListUtils.test.ts
Normal 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([]);
|
||||
});
|
||||
});
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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
Reference in New Issue
Block a user