1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-08 23:07:32 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Laurent Cozic
2f3a4f1258 update 2025-06-15 09:40:19 +01:00
669 changed files with 48845 additions and 109753 deletions

View File

@@ -15,23 +15,6 @@
# POSTGRES_PORT=5432
# POSTGRES_HOST=localhost
# =============================================================================
# TRANSCRIBE CONFIG EXAMPLE
# -----------------------------------------------------------------------------
# This service is not required, and it will be ignored by using --profile server
# when running docker-compose. If you want to use it, you need to set the
# following environment variables.
# =============================================================================
# TRANSCRIBE_API_KEY=secret_string_shared_between_server_and_transcribe
# TRANSCRIBE_ENABLED=true
# QUEUE_DATABASE_NAME=transcribe
# QUEUE_DATABASE_USER=transcribe
# QUEUE_DATABASE_PASSWORD=transcribe
# QUEUE_DATABASE_PORT=5431
# HTR_CLI_IMAGES_FOLDER=/home/user/images_storage
# =============================================================================
# DEV CONFIG EXAMPLE
# -----------------------------------------------------------------------------

View File

@@ -10,16 +10,13 @@ QUEUE_TTL=900000
QUEUE_RETRY_COUNT=2
QUEUE_MAINTENANCE_INTERVAL=30000
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:latest
# Fullpath to images folder e.g.:
#HTR_CLI_IMAGES_FOLDER=/home/user/joplin/packages/transcribe/images
HTR_CLI_IMAGES_FOLDER=
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:0.0.2
# Fullpath to images folder
HTR_CLI_IMAGES_FOLDER=/home/user/joplin/packages/transcribe/images
QUEUE_DRIVER=pg
# QUEUE_DRIVER=sqlite
FILE_STORAGE_MAINTENANCE_INTERVAL=3600000
FILE_STORAGE_TTL=604800000 # one week
# =============================================================================
# Queue driver
@@ -30,5 +27,4 @@ FILE_STORAGE_TTL=604800000 # one week
QUEUE_DATABASE_NAME=transcribe
QUEUE_DATABASE_USER=transcribe
QUEUE_DATABASE_PASSWORD=transcribe
QUEUE_DATABASE_PORT=5432
QUEUE_DATABASE_HOST=localhost
QUEUE_DATABASE_PORT=5432

View File

@@ -55,7 +55,6 @@ packages/app-desktop/vendor/lib/
packages/app-mobile/packageInfo.js
packages/app-mobile/android
packages/app-mobile/**/*.bundle.js
packages/app-mobile/**/*.bundle.css
packages/app-mobile/web/public/pluginAssets/**/*
packages/app-mobile/ios
packages/app-mobile/lib/rnInjectedJs/
@@ -75,7 +74,6 @@ packages/lib/services/database/types.ts
packages/lib/vendor/
packages/lib/vendor/fountain.min.js
packages/lib/welcomeAssets.js
packages/editor/*/vendor/
packages/plugins/**/api
packages/plugins/**/dist
packages/server/dist/
@@ -98,7 +96,6 @@ packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
packages/app-cli/app/command-cat.js
packages/app-cli/app/command-config.js
packages/app-cli/app/command-cp.js
@@ -117,8 +114,6 @@ packages/app-cli/app/command-ls.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mv.js
packages/app-cli/app/command-publish.test.js
packages/app-cli/app/command-publish.js
packages/app-cli/app/command-ren.js
packages/app-cli/app/command-restore.js
packages/app-cli/app/command-rmbook.test.js
@@ -127,21 +122,14 @@ packages/app-cli/app/command-rmnote.test.js
packages/app-cli/app/command-rmnote.js
packages/app-cli/app/command-set.js
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-share.test.js
packages/app-cli/app/command-share.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-unpublish.test.js
packages/app-cli/app/command-unpublish.js
packages/app-cli/app/command-use.js
packages/app-cli/app/command-version.js
packages/app-cli/app/gui/FolderListWidget.js
packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/iterateStdin.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
packages/app-cli/tests/MarkupToHtml.js
@@ -163,8 +151,6 @@ packages/app-desktop/app.reducer.js
packages/app-desktop/app.js
packages/app-desktop/bridge.js
packages/app-desktop/checkForUpdates.js
packages/app-desktop/commands/convertNoteToMarkdown.test.js
packages/app-desktop/commands/convertNoteToMarkdown.js
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyToClipboard.js
packages/app-desktop/commands/editProfileConfig.js
@@ -205,7 +191,6 @@ packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
packages/app-desktop/gui/ConversionNotification/ConversionNotification.js
packages/app-desktop/gui/Dialog.js
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
@@ -307,7 +292,6 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js
packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/getResourceBaseUrl.js
packages/app-desktop/gui/NoteEditor/utils/getWindowCommandPriority.js
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/markupRenderOptions.js
@@ -465,6 +449,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/index.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/linkToNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newFolder.js
@@ -565,8 +550,6 @@ packages/app-desktop/integration-tests/util/setSettingValue.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/integration-tests/wcag.spec.js
packages/app-desktop/main-html.js
packages/app-desktop/main.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js
@@ -618,7 +601,6 @@ packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
packages/app-desktop/utils/restartInSafeModeFromMain.js
packages/app-desktop/utils/sourceMapSetup.js
packages/app-desktop/utils/window/types.js
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/commands/dismissPluginPanels.js
@@ -635,25 +617,16 @@ packages/app-mobile/components/BottomDrawer.js
packages/app-mobile/components/CameraView/ActionButtons.js
packages/app-mobile/components/CameraView/Camera/index.jest.js
packages/app-mobile/components/CameraView/Camera/index.js
packages/app-mobile/components/CameraView/Camera/index.web.js
packages/app-mobile/components/CameraView/Camera/types.js
packages/app-mobile/components/CameraView/CameraView.test.js
packages/app-mobile/components/CameraView/CameraView.js
packages/app-mobile/components/CameraView/CameraView.web.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.test.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.js
packages/app-mobile/components/CameraView/PhotoPreview.js
packages/app-mobile/components/CameraView/ScannedBarcodes.js
packages/app-mobile/components/CameraView/types.js
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
packages/app-mobile/components/CameraView/utils/testing.js
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
packages/app-mobile/components/Checkbox.js
packages/app-mobile/components/ComboBox.test.js
packages/app-mobile/components/ComboBox.js
packages/app-mobile/components/DialogManager/PromptButton.js
packages/app-mobile/components/DialogManager/PromptDialog.js
packages/app-mobile/components/DialogManager/TextInputDialog.js
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
packages/app-mobile/components/DialogManager/index.js
packages/app-mobile/components/DialogManager/types.js
@@ -675,55 +648,64 @@ packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
packages/app-mobile/components/ExtendedWebView/types.js
packages/app-mobile/components/ExtendedWebView/utils/useCss.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/types.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/addPluginAssets.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/makeResourceModel.js
packages/app-mobile/components/NoteBodyViewer/hooks/useContentScripts.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.js
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
packages/app-mobile/components/NoteBodyViewer/types.js
packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js
packages/app-mobile/components/NoteEditor/EditLinkDialog.js
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/RichTextEditor.test.js
packages/app-mobile/components/NoteEditor/RichTextEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
packages/app-mobile/components/NoteEditor/WarningBanner.js
packages/app-mobile/components/NoteEditor/commandDeclarations.js
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteItem.js
packages/app-mobile/components/NoteList.js
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/SafeAreaView.js
packages/app-mobile/components/ScreenHeader/Menu.js
packages/app-mobile/components/ScreenHeader/WarningBanner.test.js
packages/app-mobile/components/ScreenHeader/WarningBanner.js
packages/app-mobile/components/ScreenHeader/WarningBox.js
packages/app-mobile/components/ScreenHeader/WebBetaButton.js
packages/app-mobile/components/ScreenHeader/index.js
packages/app-mobile/components/SearchInput.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/SideMenuContentNote.js
packages/app-mobile/components/TagEditor.test.js
packages/app-mobile/components/TagEditor.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/accessibility/AccessibleView.test.js
packages/app-mobile/components/accessibility/AccessibleView.js
@@ -748,7 +730,6 @@ 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.test.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
@@ -820,8 +801,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useRepoApi.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useUpdateState.js
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/DocumentScanner/DocumentScanner.js
packages/app-mobile/components/screens/DocumentScanner/NotePreview.js
packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note/Note.test.js
@@ -860,38 +839,6 @@ packages/app-mobile/components/voiceTyping/AudioRecordingBanner.js
packages/app-mobile/components/voiceTyping/RecordingControls.js
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.js
packages/app-mobile/components/voiceTyping/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/applyTemplateToEditor.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.test.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/startAutosaveLoop.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/watchEditorForTemplateChanges.js
packages/app-mobile/contentScripts/imageEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
packages/app-mobile/contentScripts/markdownEditorBundle/types.js
packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/markdownEditorBundle/utils/useCodeMirrorPlugins.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/types.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/addPluginAssets.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/afterFullPageRender.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/makeResourceModel.js
packages/app-mobile/contentScripts/rendererBundle/types.js
packages/app-mobile/contentScripts/rendererBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/rendererBundle/utils/useContentScripts.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.test.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/convertHtmlToMarkdown.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/richTextEditorBundle/types.js
packages/app-mobile/contentScripts/richTextEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/types.js
packages/app-mobile/contentScripts/utils/polyfills.js
packages/app-mobile/contentScripts/utils/readFileToBase64.js
packages/app-mobile/contentScripts/utils/setUpLogger.js
packages/app-mobile/gulpfile.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
@@ -914,7 +861,7 @@ packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
packages/app-mobile/tools/buildInjectedJs/copyAssets.js
packages/app-mobile/tools/buildInjectedJs/copyJs.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
@@ -923,7 +870,6 @@ packages/app-mobile/utils/ShareUtils.js
packages/app-mobile/utils/TlsUtils.js
packages/app-mobile/utils/appDefaultState.js
packages/app-mobile/utils/autodetectTheme.js
packages/app-mobile/utils/buildStartupTasks.js
packages/app-mobile/utils/checkPermissions.js
packages/app-mobile/utils/createRootStyle.js
packages/app-mobile/utils/database-driver-react-native.js
@@ -942,8 +888,6 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
@@ -952,6 +896,7 @@ packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/lockToSingleInstance.js
@@ -983,6 +928,7 @@ packages/default-plugins/commands/editPatch.js
packages/default-plugins/utils/getCurrentCommitHash.js
packages/default-plugins/utils/getPathToPatchFileFor.js
packages/default-plugins/utils/readRepositoryJson.js
packages/default-plugins/utils/waitForCliInput.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5BuiltInOptions.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
@@ -997,70 +943,47 @@ packages/editor/CodeMirror/editorCommands/duplicateLine.js
packages/editor/CodeMirror/editorCommands/editorCommands.js
packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js
packages/editor/CodeMirror/editorCommands/insertLineAfter.js
packages/editor/CodeMirror/editorCommands/insertNewlineContinueMarkup.test.js
packages/editor/CodeMirror/editorCommands/insertNewlineContinueMarkup.js
packages/editor/CodeMirror/editorCommands/jumpToHash.test.js
packages/editor/CodeMirror/editorCommands/jumpToHash.js
packages/editor/CodeMirror/editorCommands/markdownCommands.bulletedVsChecklist.test.js
packages/editor/CodeMirror/editorCommands/markdownCommands.test.js
packages/editor/CodeMirror/editorCommands/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/editorCommands/markdownCommands.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
packages/editor/CodeMirror/extensions/highlightActiveLineExtension.js
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.js
packages/editor/CodeMirror/extensions/links/referenceLinksStateField.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.test.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.js
packages/editor/CodeMirror/extensions/links/utils/getUrlAtPosition.js
packages/editor/CodeMirror/extensions/links/utils/openLink.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.test.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.test.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.js
packages/editor/CodeMirror/extensions/markdownMathExtension.test.js
packages/editor/CodeMirror/extensions/markdownMathExtension.js
packages/editor/CodeMirror/extensions/modifierKeyCssExtension.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.test.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.js
packages/editor/CodeMirror/extensions/rendering/addFormattingClasses.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.test.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.js
packages/editor/CodeMirror/extensions/rendering/renderingExtension.js
packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
packages/editor/CodeMirror/extensions/rendering/replaceFormatCharacters.js
packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/index.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
packages/editor/CodeMirror/markdown/computeSelectionFormatting.test.js
packages/editor/CodeMirror/markdown/computeSelectionFormatting.js
packages/editor/CodeMirror/markdown/decoratorExtension.test.js
packages/editor/CodeMirror/markdown/decoratorExtension.js
packages/editor/CodeMirror/markdown/insertNewlineContinueMarkup.test.js
packages/editor/CodeMirror/markdown/insertNewlineContinueMarkup.js
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/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testing/createEditorControl.js
packages/editor/CodeMirror/testing/createTestEditor.js
packages/editor/CodeMirror/testing/findNodesWithName.js
packages/editor/CodeMirror/testing/forceFullParse.js
packages/editor/CodeMirror/testing/loadLanguages.js
packages/editor/CodeMirror/testing/pressReleaseKey.js
packages/editor/CodeMirror/testing/typeText.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
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/utils/biDirectionalTextExtension.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/computeSelectionFormatting.test.js
packages/editor/CodeMirror/utils/formatting/computeSelectionFormatting.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
@@ -1079,65 +1002,15 @@ packages/editor/CodeMirror/utils/handleLinkEditRequests.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
packages/editor/CodeMirror/utils/keyUpHandlerExtension.js
packages/editor/CodeMirror/utils/overwriteModeExtension.test.js
packages/editor/CodeMirror/utils/overwriteModeExtension.js
packages/editor/CodeMirror/utils/searchExtension.js
packages/editor/CodeMirror/utils/selectedNoteIdExtension.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/ProseMirror/commands.test.js
packages/editor/ProseMirror/commands.js
packages/editor/ProseMirror/createEditor.js
packages/editor/ProseMirror/index.js
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
packages/editor/ProseMirror/plugins/detailsPlugin.js
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/postProcessRenderedHtml.js
packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
packages/editor/ProseMirror/plugins/keymapPlugin.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
packages/editor/ProseMirror/plugins/listPlugin.js
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
packages/editor/ProseMirror/plugins/searchPlugin.js
packages/editor/ProseMirror/schema.js
packages/editor/ProseMirror/styles.js
packages/editor/ProseMirror/testing/createTestEditor.js
packages/editor/ProseMirror/types.js
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
packages/editor/ProseMirror/utils/dom/createButton.js
packages/editor/ProseMirror/utils/dom/createTextArea.js
packages/editor/ProseMirror/utils/dom/createTextNode.js
packages/editor/ProseMirror/utils/dom/createUniqueId.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
packages/editor/ProseMirror/utils/forEachHeading.js
packages/editor/ProseMirror/utils/jumpToHash.js
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.js
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
packages/editor/ProseMirror/utils/preprocessEditorInput.js
packages/editor/ProseMirror/utils/sanitizeHtml.js
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
packages/editor/ProseMirror/vendor/changedDescendants.js
packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/polyfills.js
packages/editor/testing/createEditorSettings.js
packages/editor/testing/setUpLogger.js
packages/editor/types.js
packages/editor/utils/getFileFromPasteEvent.js
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/FeedHandler.spec.js
packages/fork-htmlparser2/src/FeedHandler.js
@@ -1182,8 +1055,6 @@ packages/lib/JoplinDatabase.js
packages/lib/JoplinError.js
packages/lib/JoplinServerApi.js
packages/lib/ObjectUtils.js
packages/lib/PerformanceLogger.test.js
packages/lib/PerformanceLogger.js
packages/lib/PoorManIntervals.js
packages/lib/RotatingLogs.test.js
packages/lib/RotatingLogs.js
@@ -1201,13 +1072,10 @@ packages/lib/array.js
packages/lib/callbackUrlUtils.test.js
packages/lib/callbackUrlUtils.js
packages/lib/clipperUtils.js
packages/lib/commands/convertHtmlToMarkdown.test.js
packages/lib/commands/convertHtmlToMarkdown.js
packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js
packages/lib/commands/index.js
packages/lib/commands/leaveSharedFolder.js
packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/renderMarkup.test.js
@@ -1219,8 +1087,6 @@ packages/lib/commands/toggleAllFolders.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
@@ -1397,7 +1263,6 @@ packages/lib/services/database/migrations/44.js
packages/lib/services/database/migrations/45.js
packages/lib/services/database/migrations/46.js
packages/lib/services/database/migrations/47.js
packages/lib/services/database/migrations/48.js
packages/lib/services/database/migrations/index.js
packages/lib/services/database/sqlStringToLines.js
packages/lib/services/database/types.js
@@ -1466,8 +1331,6 @@ packages/lib/services/ocr/OcrDriverBase.js
packages/lib/services/ocr/OcrService.test.js
packages/lib/services/ocr/OcrService.js
packages/lib/services/ocr/drivers/OcrDriverTesseract.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.test.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.js
packages/lib/services/ocr/utils/filterOcrText.test.js
packages/lib/services/ocr/utils/filterOcrText.js
packages/lib/services/ocr/utils/types.js
@@ -1637,7 +1500,6 @@ packages/lib/shim-init-node.js
packages/lib/shim.js
packages/lib/string-utils.test.js
packages/lib/string-utils.js
packages/lib/testing/plugins/createTestPlugin.js
packages/lib/testing/share/makeMockShareInvitation.js
packages/lib/testing/share/mockShareService.js
packages/lib/testing/syncTargetUtils.js
@@ -1777,20 +1639,6 @@ packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionTracker.js
packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/model/FolderRecord.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.test.js
@@ -1821,7 +1669,6 @@ packages/tools/release-electron.js
packages/tools/release-ios.js
packages/tools/release-plugin-repo-cli.js
packages/tools/release-server.js
packages/tools/release-transcribe.js
packages/tools/saveClaConsentRecords.js
packages/tools/setupNewRelease.js
packages/tools/spellcheck.js

View File

@@ -23,8 +23,6 @@ module.exports = {
'FileSystemCreateWritableOptions': 'readonly',
'FileSystemHandle': 'readonly',
'IDBTransactionMode': 'readonly',
'FlatArray': 'readonly',
'BigInt': 'readonly',
'globalThis': 'readonly',
// ServiceWorker

View File

@@ -7,13 +7,9 @@
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
TRANSCRIBE_TAG_PREFIX=transcribe
TRANSCRIBE_REPOSITORY=joplin/transcribe
IS_PULL_REQUEST=0
IS_DESKTOP_RELEASE=0
IS_SERVER_RELEASE=0
IS_TRANSCRIBE_RELEASE=0
IS_LINUX=0
IS_MACOS=0
@@ -27,10 +23,6 @@ if [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
IS_SERVER_RELEASE=1
fi
if [[ $GIT_TAG_NAME = $TRANSCRIBE_TAG_PREFIX-* ]]; then
IS_TRANSCRIBE_RELEASE=1
fi
if [[ $GIT_TAG_NAME = v* ]]; then
IS_DESKTOP_RELEASE=1
fi
@@ -49,17 +41,15 @@ DOCKER_IMAGE_PLATFORM="linux/amd64"
# a release
RUN_TESTS=0
if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ] && [ "$IS_TRANSCRIBE_RELEASE" = 0 ]; then
if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ]; then
RUN_TESTS=1
fi
if [ "$RUNNER_ARCH" == "ARM64" ]; then
if [ "$IS_SERVER_RELEASE" == "0" ] && [ "$IS_TRANSCRIBE_RELEASE" == "0" ]; then
# We exit now because nothing works properly with the ARM64 architecture.
# We only proceed if building the server image.
echo "Running on ARM64 and not trying to build server image - early exit"
exit 0
fi
if [ "$RUNNER_ARCH" == "ARM64" ] && [ "$IS_SERVER_RELEASE" == "0" ]; then
# We exit now because nothing works properly with the ARM64 architecture.
# We only proceed if building the server image.
echo "Running on ARM64 and not trying to build server image - early exit"
exit 0
fi
if [ "$RUNNER_ARCH" == "ARM64" ]; then
@@ -90,14 +80,12 @@ echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "BUILD_SEQUENCIAL=$BUILD_SEQUENCIAL"
echo "SERVER_REPOSITORY=$SERVER_REPOSITORY"
echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
echo "TRANSCRIBE_TAG_PREFIX=$TRANSCRIBE_TAG_PREFIX"
echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
echo "IS_DESKTOP_RELEASE=$IS_DESKTOP_RELEASE"
echo "IS_SERVER_RELEASE=$IS_SERVER_RELEASE"
echo "IS_TRANSCRIBE_RELEASE=$IS_TRANSCRIBE_RELEASE"
echo "RUN_TESTS=$RUN_TESTS"
echo "IS_LINUX=$IS_LINUX"
echo "IS_MACOS=$IS_MACOS"
@@ -145,7 +133,7 @@ if [ "$RUN_TESTS" == "1" ]; then
# Allocation failed - JavaScript heap out of memory
#
# https://stackoverflow.com/questions/38558989
export NODE_OPTIONS="--max-old-space-size=32768"
export NODE_OPTIONS="--max-old-space-size=32768 --security-revert=CVE-2023-46809"
yarn test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
@@ -313,13 +301,9 @@ if [ "$IS_DESKTOP_RELEASE" == "1" ]; then
USE_HARD_LINKS=false yarn dist
fi
elif [[ $IS_LINUX = 1 ]] && [ "$IS_SERVER_RELEASE" == "1" ]; then
echo "Step: Building Joplin Server Docker Image..."
echo "Step: Building Docker Image..."
cd "$ROOT_DIR"
yarn buildServerDocker --docker-file Dockerfile.server --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
elif [[ $IS_LINUX = 1 ]] && [ "$IS_TRANSCRIBE_RELEASE" == "1" ]; then
echo "Step: Building Joplin Transcribe Docker Image..."
cd "$ROOT_DIR"
yarn buildServerDocker --docker-file Dockerfile.transcribe --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $TRANSCRIBE_REPOSITORY
yarn buildServerDocker --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
else
echo "Step: Building but *not* publishing desktop application..."

View File

@@ -13,7 +13,7 @@ jobs:
with:
# We need to pin the version to 18.15, because 18.16+ fails with this error:
# https://github.com/facebook/react-native/issues/36440
node-version: '18.20.8'
node-version: '18.15.0'
cache: 'yarn'
- name: Install Yarn
@@ -30,7 +30,7 @@ jobs:
# See github-action-main.yml for explanation
- uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.11'
- name: Set Publish Flag
run: |

View File

@@ -17,6 +17,7 @@ jobs:
uses: ./.github/workflows/shared/setup-build-environment
- name: Install Docker Engine
# if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
if: runner.os == 'Linux'
run: |
sudo apt-get install -y apt-transport-https
@@ -35,7 +36,7 @@ jobs:
# a pull request it will fail because the PR doesn't have access to
# secrets
- uses: docker/login-action@v3
if: runner.os == 'Linux' && (startsWith(github.ref, 'refs/tags/server-v') || startsWith(github.ref, 'refs/tags/transcribe-v'))
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v')
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -121,6 +122,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'yarn'
- name: Install Yarn
run: |
@@ -140,7 +142,7 @@ jobs:
echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM"
yarn install
yarn buildServerDocker --docker-file Dockerfile.server --platform $DOCKER_IMAGE_PLATFORM --tag-name server-v0.0.0 --repository joplin/server
yarn buildServerDocker --platform $DOCKER_IMAGE_PLATFORM --tag-name server-v0.0.0 --repository joplin/server
# Basic test to ensure that the created build is valid. It should exit with
# code 0 if it works.
@@ -152,7 +154,7 @@ jobs:
docker run -p 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
# Wait for server to start
sleep 120
sleep 30
# Check if status code is correct
# if the actual_status DOES NOT include the expected_status

View File

@@ -50,14 +50,13 @@ runs:
- uses: olegtarasov/get-tag@v2.1.4
- uses: dtolnay/rust-toolchain@stable
if: ${{ runner.os != 'Windows' }}
- uses: actions/setup-node@v4
with:
node-version: '18.20.8'
node-version: '18.18.0'
# Disable the cache on ARM runners. For now, we don't run "yarn install" on these
# environments and this breaks actions/setup-node.
# See https://github.com/laurent22/joplin/commit/47d0d3eb9e89153a609fb5441344da10904c6308#commitcomment-159577783.
# cache: ${{ (!contains(runner.os, 'arm') && 'yarn') || '' }}
cache: ${{ (!contains(runner.os, 'arm') && 'yarn') || '' }}
- name: Install Yarn
shell: bash
@@ -72,4 +71,4 @@ runs:
# Ref: https://github.com/nodejs/node-gyp/issues/2869
- uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.11'

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, windows-2025]
os: [macos-13, ubuntu-22.04, windows-2025]
steps:
- uses: actions/checkout@v4
- name: Setup build environment

267
.gitignore vendored
View File

@@ -71,7 +71,6 @@ packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
packages/app-cli/app/command-cat.js
packages/app-cli/app/command-config.js
packages/app-cli/app/command-cp.js
@@ -90,8 +89,6 @@ packages/app-cli/app/command-ls.js
packages/app-cli/app/command-mkbook.test.js
packages/app-cli/app/command-mkbook.js
packages/app-cli/app/command-mv.js
packages/app-cli/app/command-publish.test.js
packages/app-cli/app/command-publish.js
packages/app-cli/app/command-ren.js
packages/app-cli/app/command-restore.js
packages/app-cli/app/command-rmbook.test.js
@@ -100,21 +97,14 @@ packages/app-cli/app/command-rmnote.test.js
packages/app-cli/app/command-rmnote.js
packages/app-cli/app/command-set.js
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-share.test.js
packages/app-cli/app/command-share.js
packages/app-cli/app/command-sync.js
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-unpublish.test.js
packages/app-cli/app/command-unpublish.js
packages/app-cli/app/command-use.js
packages/app-cli/app/command-version.js
packages/app-cli/app/gui/FolderListWidget.js
packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/iterateStdin.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
packages/app-cli/tests/MarkupToHtml.js
@@ -136,8 +126,6 @@ packages/app-desktop/app.reducer.js
packages/app-desktop/app.js
packages/app-desktop/bridge.js
packages/app-desktop/checkForUpdates.js
packages/app-desktop/commands/convertNoteToMarkdown.test.js
packages/app-desktop/commands/convertNoteToMarkdown.js
packages/app-desktop/commands/copyDevCommand.js
packages/app-desktop/commands/copyToClipboard.js
packages/app-desktop/commands/editProfileConfig.js
@@ -178,7 +166,6 @@ packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
packages/app-desktop/gui/ConversionNotification/ConversionNotification.js
packages/app-desktop/gui/Dialog.js
packages/app-desktop/gui/DialogButtonRow.js
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
@@ -280,7 +267,6 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js
packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/getResourceBaseUrl.js
packages/app-desktop/gui/NoteEditor/utils/getWindowCommandPriority.js
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/markupRenderOptions.js
@@ -438,6 +424,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/index.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/linkToNote.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.js
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newFolder.js
@@ -538,8 +525,6 @@ packages/app-desktop/integration-tests/util/setSettingValue.js
packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/integration-tests/wcag.spec.js
packages/app-desktop/main-html.js
packages/app-desktop/main.js
packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js
@@ -591,7 +576,6 @@ packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
packages/app-desktop/utils/restartInSafeModeFromMain.js
packages/app-desktop/utils/sourceMapSetup.js
packages/app-desktop/utils/window/types.js
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/commands/dismissPluginPanels.js
@@ -608,25 +592,16 @@ packages/app-mobile/components/BottomDrawer.js
packages/app-mobile/components/CameraView/ActionButtons.js
packages/app-mobile/components/CameraView/Camera/index.jest.js
packages/app-mobile/components/CameraView/Camera/index.js
packages/app-mobile/components/CameraView/Camera/index.web.js
packages/app-mobile/components/CameraView/Camera/types.js
packages/app-mobile/components/CameraView/CameraView.test.js
packages/app-mobile/components/CameraView/CameraView.js
packages/app-mobile/components/CameraView/CameraView.web.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.test.js
packages/app-mobile/components/CameraView/CameraViewMultiPage.js
packages/app-mobile/components/CameraView/PhotoPreview.js
packages/app-mobile/components/CameraView/ScannedBarcodes.js
packages/app-mobile/components/CameraView/types.js
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
packages/app-mobile/components/CameraView/utils/testing.js
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
packages/app-mobile/components/Checkbox.js
packages/app-mobile/components/ComboBox.test.js
packages/app-mobile/components/ComboBox.js
packages/app-mobile/components/DialogManager/PromptButton.js
packages/app-mobile/components/DialogManager/PromptDialog.js
packages/app-mobile/components/DialogManager/TextInputDialog.js
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
packages/app-mobile/components/DialogManager/index.js
packages/app-mobile/components/DialogManager/types.js
@@ -648,55 +623,64 @@ packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
packages/app-mobile/components/ExtendedWebView/types.js
packages/app-mobile/components/ExtendedWebView/utils/useCss.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
packages/app-mobile/components/Modal.js
packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/types.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/addPluginAssets.js
packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/makeResourceModel.js
packages/app-mobile/components/NoteBodyViewer/hooks/useContentScripts.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.js
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
packages/app-mobile/components/NoteBodyViewer/types.js
packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js
packages/app-mobile/components/NoteEditor/EditLinkDialog.js
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/RichTextEditor.test.js
packages/app-mobile/components/NoteEditor/RichTextEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
packages/app-mobile/components/NoteEditor/WarningBanner.js
packages/app-mobile/components/NoteEditor/commandDeclarations.js
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteItem.js
packages/app-mobile/components/NoteList.js
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/SafeAreaView.js
packages/app-mobile/components/ScreenHeader/Menu.js
packages/app-mobile/components/ScreenHeader/WarningBanner.test.js
packages/app-mobile/components/ScreenHeader/WarningBanner.js
packages/app-mobile/components/ScreenHeader/WarningBox.js
packages/app-mobile/components/ScreenHeader/WebBetaButton.js
packages/app-mobile/components/ScreenHeader/index.js
packages/app-mobile/components/SearchInput.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/SideMenuContentNote.js
packages/app-mobile/components/TagEditor.test.js
packages/app-mobile/components/TagEditor.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/accessibility/AccessibleView.test.js
packages/app-mobile/components/accessibility/AccessibleView.js
@@ -721,7 +705,6 @@ 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.test.js
packages/app-mobile/components/plugins/PluginRunnerWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializeDialogWebView.js
packages/app-mobile/components/plugins/backgroundPage/initializePluginBackgroundIframe.js
@@ -793,8 +776,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useRepoApi.js
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/useUpdateState.js
packages/app-mobile/components/screens/ConfigScreen/types.js
packages/app-mobile/components/screens/DocumentScanner/DocumentScanner.js
packages/app-mobile/components/screens/DocumentScanner/NotePreview.js
packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note/Note.test.js
@@ -833,38 +814,6 @@ packages/app-mobile/components/voiceTyping/AudioRecordingBanner.js
packages/app-mobile/components/voiceTyping/RecordingControls.js
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.js
packages/app-mobile/components/voiceTyping/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/applyTemplateToEditor.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.test.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/startAutosaveLoop.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/types.js
packages/app-mobile/contentScripts/imageEditorBundle/contentScript/watchEditorForTemplateChanges.js
packages/app-mobile/contentScripts/imageEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
packages/app-mobile/contentScripts/markdownEditorBundle/types.js
packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/markdownEditorBundle/utils/useCodeMirrorPlugins.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/types.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/addPluginAssets.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/afterFullPageRender.js
packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/makeResourceModel.js
packages/app-mobile/contentScripts/rendererBundle/types.js
packages/app-mobile/contentScripts/rendererBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/rendererBundle/utils/useContentScripts.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.test.js
packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/convertHtmlToMarkdown.js
packages/app-mobile/contentScripts/richTextEditorBundle/contentScript/index.js
packages/app-mobile/contentScripts/richTextEditorBundle/types.js
packages/app-mobile/contentScripts/richTextEditorBundle/useWebViewSetup.js
packages/app-mobile/contentScripts/types.js
packages/app-mobile/contentScripts/utils/polyfills.js
packages/app-mobile/contentScripts/utils/readFileToBase64.js
packages/app-mobile/contentScripts/utils/setUpLogger.js
packages/app-mobile/gulpfile.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
@@ -887,7 +836,7 @@ packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
packages/app-mobile/tools/buildInjectedJs/copyAssets.js
packages/app-mobile/tools/buildInjectedJs/copyJs.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
@@ -896,7 +845,6 @@ packages/app-mobile/utils/ShareUtils.js
packages/app-mobile/utils/TlsUtils.js
packages/app-mobile/utils/appDefaultState.js
packages/app-mobile/utils/autodetectTheme.js
packages/app-mobile/utils/buildStartupTasks.js
packages/app-mobile/utils/checkPermissions.js
packages/app-mobile/utils/createRootStyle.js
packages/app-mobile/utils/database-driver-react-native.js
@@ -915,8 +863,6 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js
packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js
packages/app-mobile/utils/hooks/useReduceMotionEnabled.js
@@ -925,6 +871,7 @@ packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/lockToSingleInstance.js
@@ -956,6 +903,7 @@ packages/default-plugins/commands/editPatch.js
packages/default-plugins/utils/getCurrentCommitHash.js
packages/default-plugins/utils/getPathToPatchFileFor.js
packages/default-plugins/utils/readRepositoryJson.js
packages/default-plugins/utils/waitForCliInput.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5BuiltInOptions.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.test.js
packages/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation.js
@@ -970,70 +918,47 @@ packages/editor/CodeMirror/editorCommands/duplicateLine.js
packages/editor/CodeMirror/editorCommands/editorCommands.js
packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js
packages/editor/CodeMirror/editorCommands/insertLineAfter.js
packages/editor/CodeMirror/editorCommands/insertNewlineContinueMarkup.test.js
packages/editor/CodeMirror/editorCommands/insertNewlineContinueMarkup.js
packages/editor/CodeMirror/editorCommands/jumpToHash.test.js
packages/editor/CodeMirror/editorCommands/jumpToHash.js
packages/editor/CodeMirror/editorCommands/markdownCommands.bulletedVsChecklist.test.js
packages/editor/CodeMirror/editorCommands/markdownCommands.test.js
packages/editor/CodeMirror/editorCommands/markdownCommands.toggleList.test.js
packages/editor/CodeMirror/editorCommands/markdownCommands.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
packages/editor/CodeMirror/editorCommands/supportsCommand.js
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
packages/editor/CodeMirror/extensions/highlightActiveLineExtension.js
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.js
packages/editor/CodeMirror/extensions/links/referenceLinksStateField.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.test.js
packages/editor/CodeMirror/extensions/links/utils/findLineMatchingLink.js
packages/editor/CodeMirror/extensions/links/utils/getUrlAtPosition.js
packages/editor/CodeMirror/extensions/links/utils/openLink.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.test.js
packages/editor/CodeMirror/extensions/markdownDecorationExtension.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.test.js
packages/editor/CodeMirror/extensions/markdownHighlightExtension.js
packages/editor/CodeMirror/extensions/markdownMathExtension.test.js
packages/editor/CodeMirror/extensions/markdownMathExtension.js
packages/editor/CodeMirror/extensions/modifierKeyCssExtension.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.test.js
packages/editor/CodeMirror/extensions/overwriteModeExtension.js
packages/editor/CodeMirror/extensions/rendering/addFormattingClasses.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.test.js
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.js
packages/editor/CodeMirror/extensions/rendering/renderingExtension.js
packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
packages/editor/CodeMirror/extensions/rendering/replaceFormatCharacters.js
packages/editor/CodeMirror/extensions/rendering/types.js
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
packages/editor/CodeMirror/index.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
packages/editor/CodeMirror/markdown/computeSelectionFormatting.test.js
packages/editor/CodeMirror/markdown/computeSelectionFormatting.js
packages/editor/CodeMirror/markdown/decoratorExtension.test.js
packages/editor/CodeMirror/markdown/decoratorExtension.js
packages/editor/CodeMirror/markdown/insertNewlineContinueMarkup.test.js
packages/editor/CodeMirror/markdown/insertNewlineContinueMarkup.js
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/utils/renumberSelectedLists.test.js
packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.js
packages/editor/CodeMirror/markdown/utils/stripBlockquote.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testing/createEditorControl.js
packages/editor/CodeMirror/testing/createTestEditor.js
packages/editor/CodeMirror/testing/findNodesWithName.js
packages/editor/CodeMirror/testing/forceFullParse.js
packages/editor/CodeMirror/testing/loadLanguages.js
packages/editor/CodeMirror/testing/pressReleaseKey.js
packages/editor/CodeMirror/testing/typeText.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
packages/editor/CodeMirror/testUtil/typeText.js
packages/editor/CodeMirror/theme.js
packages/editor/CodeMirror/utils/biDirectionalTextExtension.js
packages/editor/CodeMirror/utils/formatting/RegionSpec.js
packages/editor/CodeMirror/utils/formatting/computeSelectionFormatting.test.js
packages/editor/CodeMirror/utils/formatting/computeSelectionFormatting.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
@@ -1052,65 +977,15 @@ packages/editor/CodeMirror/utils/handleLinkEditRequests.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
packages/editor/CodeMirror/utils/keyUpHandlerExtension.js
packages/editor/CodeMirror/utils/overwriteModeExtension.test.js
packages/editor/CodeMirror/utils/overwriteModeExtension.js
packages/editor/CodeMirror/utils/searchExtension.js
packages/editor/CodeMirror/utils/selectedNoteIdExtension.js
packages/editor/CodeMirror/utils/setupVim.js
packages/editor/ProseMirror/commands.test.js
packages/editor/ProseMirror/commands.js
packages/editor/ProseMirror/createEditor.js
packages/editor/ProseMirror/index.js
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
packages/editor/ProseMirror/plugins/detailsPlugin.js
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.js
packages/editor/ProseMirror/plugins/joplinEditablePlugin/postProcessRenderedHtml.js
packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
packages/editor/ProseMirror/plugins/keymapPlugin.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
packages/editor/ProseMirror/plugins/listPlugin.js
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
packages/editor/ProseMirror/plugins/searchPlugin.js
packages/editor/ProseMirror/schema.js
packages/editor/ProseMirror/styles.js
packages/editor/ProseMirror/testing/createTestEditor.js
packages/editor/ProseMirror/types.js
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
packages/editor/ProseMirror/utils/dom/createButton.js
packages/editor/ProseMirror/utils/dom/createTextArea.js
packages/editor/ProseMirror/utils/dom/createTextNode.js
packages/editor/ProseMirror/utils/dom/createUniqueId.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
packages/editor/ProseMirror/utils/forEachHeading.js
packages/editor/ProseMirror/utils/jumpToHash.js
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
packages/editor/ProseMirror/utils/postprocessEditorOutput.js
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
packages/editor/ProseMirror/utils/preprocessEditorInput.js
packages/editor/ProseMirror/utils/sanitizeHtml.js
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
packages/editor/ProseMirror/vendor/changedDescendants.js
packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
packages/editor/polyfills.js
packages/editor/testing/createEditorSettings.js
packages/editor/testing/setUpLogger.js
packages/editor/types.js
packages/editor/utils/getFileFromPasteEvent.js
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/FeedHandler.spec.js
packages/fork-htmlparser2/src/FeedHandler.js
@@ -1155,8 +1030,6 @@ packages/lib/JoplinDatabase.js
packages/lib/JoplinError.js
packages/lib/JoplinServerApi.js
packages/lib/ObjectUtils.js
packages/lib/PerformanceLogger.test.js
packages/lib/PerformanceLogger.js
packages/lib/PoorManIntervals.js
packages/lib/RotatingLogs.test.js
packages/lib/RotatingLogs.js
@@ -1174,13 +1047,10 @@ packages/lib/array.js
packages/lib/callbackUrlUtils.test.js
packages/lib/callbackUrlUtils.js
packages/lib/clipperUtils.js
packages/lib/commands/convertHtmlToMarkdown.test.js
packages/lib/commands/convertHtmlToMarkdown.js
packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js
packages/lib/commands/index.js
packages/lib/commands/leaveSharedFolder.js
packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/renderMarkup.test.js
@@ -1192,8 +1062,6 @@ packages/lib/commands/toggleAllFolders.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
@@ -1370,7 +1238,6 @@ packages/lib/services/database/migrations/44.js
packages/lib/services/database/migrations/45.js
packages/lib/services/database/migrations/46.js
packages/lib/services/database/migrations/47.js
packages/lib/services/database/migrations/48.js
packages/lib/services/database/migrations/index.js
packages/lib/services/database/sqlStringToLines.js
packages/lib/services/database/types.js
@@ -1439,8 +1306,6 @@ packages/lib/services/ocr/OcrDriverBase.js
packages/lib/services/ocr/OcrService.test.js
packages/lib/services/ocr/OcrService.js
packages/lib/services/ocr/drivers/OcrDriverTesseract.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.test.js
packages/lib/services/ocr/drivers/OcrDriverTranscribe.js
packages/lib/services/ocr/utils/filterOcrText.test.js
packages/lib/services/ocr/utils/filterOcrText.js
packages/lib/services/ocr/utils/types.js
@@ -1610,7 +1475,6 @@ packages/lib/shim-init-node.js
packages/lib/shim.js
packages/lib/string-utils.test.js
packages/lib/string-utils.js
packages/lib/testing/plugins/createTestPlugin.js
packages/lib/testing/share/makeMockShareInvitation.js
packages/lib/testing/share/mockShareService.js
packages/lib/testing/syncTargetUtils.js
@@ -1750,20 +1614,6 @@ packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionTracker.js
packages/tools/fuzzer/Client.js
packages/tools/fuzzer/ClientPool.js
packages/tools/fuzzer/Server.js
packages/tools/fuzzer/constants.js
packages/tools/fuzzer/model/FolderRecord.js
packages/tools/fuzzer/sync-fuzzer.js
packages/tools/fuzzer/types.js
packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.test.js
@@ -1794,7 +1644,6 @@ packages/tools/release-electron.js
packages/tools/release-ios.js
packages/tools/release-plugin-repo-cli.js
packages/tools/release-server.js
packages/tools/release-transcribe.js
packages/tools/saveClaConsentRecords.js
packages/tools/setupNewRelease.js
packages/tools/spellcheck.js

View File

@@ -1,4 +1,3 @@
{
"cSpell.enabled": true,
"editor.insertSpaces": false
"cSpell.enabled": true
}

View File

@@ -1300,9 +1300,4 @@ footer .bottom-links-row p {
:lang(zh-cn) #plans-section .faq {
display: none;
}
.cfa-button {
margin-top: 10px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,215 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Milo Ivir <mail@mivirtype.de>\n"
"Language-Team: \n"
"Language: hr_HR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.6\n"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:13
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:9
msgid "/month"
msgstr "/mjesec"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/partials/plan.mustache:19
msgid "/year"
msgstr "/godina"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:8
msgid ""
"<a href=\"https://joplincloud.com\">Joplin Cloud</a> allows you to "
"synchronise your notes across devices. It also lets you publish notes, and "
"collaborate on notebooks with your friends, family or colleagues."
msgstr ""
"<a href=\"https://joplincloud.com\">Joplin Cloud</a> omogućuje "
"sinkronizaciju bilješki na različitim uređajima. Omogućuje i objavljivanje "
"bilješki i suradnju na bilježnicama s prijateljima, obitelji ili kolegama."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:205
msgid "<span class=\"frame-bg frame-bg-yellow-lg\">Customise</span> it"
msgstr "<span class=\"frame-bg frame-bg-yellow-lg\">Prilagodi</span> uslugu"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:104
msgid "<span class=\"frame-bg frame-bg-yellow\">Multimedia</span> notes"
msgstr "<span class=\"frame-bg frame-bg-yellow\">Multimedijske</span> bilješke"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:256
msgid "100% <span class=\"frame-bg frame-bg-yellow-lg\">your data</span>"
msgstr "100 % <span class=\"frame-bg frame-bg-yellow-lg\">tvoji podaci</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:298
msgid "A <span class=\"frame-bg frame-bg-yellow-lg\">French</span> Alternative"
msgstr ""
"<span class=\"frame-bg frame-bg-yellow-lg\">Francuska</span> alternativa"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:236
msgid ""
"Access your notes from your computer, phone or tablet by synchronising with "
"various services, including Joplin Cloud, Dropbox and OneDrive. The app is "
"available on Windows, macOS, Linux, Android and iOS. A terminal app is also "
"available!"
msgstr ""
"Pristupi svojim bilješkama s računala, mobitela ili tableta sinkronizacijom "
"s raznim uslugama, uključujući Joplin Cloud, Dropbox i OneDrive. Program je "
"dostupan za Windows, macOS, Linux, Android i iOS sustave. Dostupan je i "
"program za terminal!"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:49
msgid ""
"Already have a Joplin Cloud account? <a href=\"https://"
"joplincloud.com\">Login now</a>"
msgstr ""
"Već imaš Joplin Cloud račun? <a href=\"https://joplincloud.com\">Prijavi se "
"sada</a>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:208
msgid ""
"Customise the app with plugins, custom themes and multiple text editors "
"(Rich Text or Markdown). Or create your own scripts and plugins using the "
"Extension API."
msgstr ""
"Prilagodi program pomoću dodataka, prilagođenih tema i uređivača teksta "
"(formatirani tekst ili Markdown). Ili izradi vlastita skripta i dodatke "
"pomoću API-ja za proširenja."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:242
msgid "Download it now"
msgstr "Preuzmi sada"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:112
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:63
msgid "Download the app"
msgstr "Preuzmi program"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:213
msgid "Find out more"
msgstr "Saznaj više"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:54
msgid "Free your <span class=\"frame-bg frame-bg-blue\">notes</span>"
msgstr "Oslobodi svoje <span class=\"frame-bg frame-bg-blue\">bilješke</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:175
msgid "Get the clipper"
msgstr "Nabavi Clipper"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:107
msgid ""
"Images, videos, PDFs and audio files are supported. Create math expressions "
"and diagrams directly from the app. Take photos with the mobile app and save "
"them to a note."
msgstr ""
"Podržane su slike, videozapisi, PDF-ovi i audio datoteke. Stvori matematičke "
"izraze i dijagrame izravno iz programa. Snimaj fotografije s programom za "
"mobitel i spremi ih u bilješku."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:327
msgid "In the <span class=\"frame-bg frame-bg-yellow\">Press</span>"
msgstr "<span class=\"frame-bg frame-bg-yellow\">Recenzije</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:5
msgid "Joplin Cloud <span class=\"frame-bg frame-bg-yellow\">plans</span>"
msgstr "Joplin Cloud <span class=\"frame-bg frame-bg-yellow\">tarife</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:301
msgid ""
"Joplin Cloud is based in France. This means your data is protected by strict "
"European Union privacy laws. In addition, Joplin Cloud implements strong end-"
"to-end encryption so that not even us can have access to your data."
msgstr ""
"Joplin Cloud ima sjedište u Francuskoj. To znači da su tvoji podaci "
"zaštićeni strogim zakonima o privatnosti Europske unije. Osim toga, Joplin "
"Cloud implementira snažno sveobuhvatno šifriranje (end-to-end encryption) "
"tako da čak ni mi ne možemo pristupiti tvojim podacima."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:57
msgid ""
"Joplin is an open source note-taking app. Capture your thoughts and securely "
"access them from any device."
msgstr ""
"Joplin je program za bilješke otvorenog koda. Zabilježi svoje misli i "
"sigurno im pristupi s bilo kojeg uređaja."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:262
msgid "More about E2EE"
msgstr "Više o E2EE"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:391
msgid "Our <span class=\"frame-bg frame-bg-blue-lg\">sponsors</span>"
msgstr "Naši <span class=\"frame-bg frame-bg-blue-lg\">sponzori</span>"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:23
msgid "Pay Monthly"
msgstr "Plaćaj mjesečno"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/plans.mustache:30
msgid "Pay Yearly"
msgstr "Plaćaj godišnje"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:167
msgid ""
"Save <span class=\"frame-bg frame-bg-blue\">web pages</span> <br>as notes"
msgstr ""
"Spremaj <span class=\"frame-bg frame-bg-blue\">web stranice</span> <br>kao "
"bilješke"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:65
msgid "Sign up with Joplin Cloud"
msgstr "Registriraj se na Joplin Cloud"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:394
msgid "Thank you for your support!"
msgstr "Hvala ti na podršci!"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:257
msgid ""
"The app is open source and your notes are saved to an open format, so you'll "
"always have access to them. Uses End-To-End Encryption (E2EE) to secure your "
"notes and ensure no-one but yourself can access them."
msgstr ""
"Program je otvorenog koda i tvoje se bilješke spremaju u otvorenom formatu, "
"tako da ćeš im uvijek moći pristupiti. Program koristi sveobuhvatno "
"šifriranje – engl. End-To-End Encryption (E2EE) – kako bi zaštitila tvoje "
"bilješke i osigurala da im nitko osim tebe ne može pristupiti."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:144
msgid "Try it now"
msgstr "Isprobaj sada"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:170
msgid ""
"Use the web clipper extension, available on Chrome and Firefox, to save web "
"pages or take screenshots as notes."
msgstr ""
"Koristi proširenje Web Clipper, dostupno za Chrome i Firefox, za spremanje "
"web stranica ili snimanje ekrana kao bilješku."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:138
msgid ""
"With Joplin Cloud, share your notes with your friends, family or colleagues "
"and collaborate on them."
msgstr ""
"Joplin Cloud ti omogućuje da dijeliš bilješke s prijateljima, obitelji ili "
"kolegama te da na njima surađujete."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:137
msgid "Work <span class=\"frame-bg frame-bg-yellow\">together</span>"
msgstr "<span class=\"frame-bg frame-bg-yellow\">Surađuj</span> s drugima"
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:141
msgid ""
"You can also publish a note to the internet and share the URL with others."
msgstr "Bilješke možeš objaviti i na internetu te dijeliti URL s drugima."
#: /Users/laurent/src/joplin/Assets/WebsiteAssets/templates/front.mustache:233
msgid ""
"Your notes, <span class=\"frame-bg frame-bg-blue-lg\">everywhere</span> you "
"are"
msgstr ""
"Tvoje bilješke, <span class=\"frame-bg frame-bg-blue-lg\">gdje god</span> se "
"nalaziš"

View File

@@ -1,28 +1,24 @@
<div class="col-12 col-lg-4 account-type-{{priceMonthly.accountType}} hosting-type-{{hostingType}}">
<div class="col-12 col-lg-4 account-type-{{priceMonthly.accountType}}">
<div class="price-container {{#featured}}price-container-blue{{/featured}}">
<div class="price-row">
<div class="plan-type">
<img src="{{imageBaseUrl}}/{{iconName}}.png"/>&nbsp;{{title}}
</div>
{{#priceMonthly.formattedMonthlyAmount}}
<div class="plan-price plan-price-monthly">
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
<div class="plan-price plan-price-yearly">
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
{{/priceMonthly.formattedMonthlyAmount}}
<div class="price-row">
<div class="plan-type">
<img src="{{imageBaseUrl}}/{{iconName}}.png"/>&nbsp;{{title}}
</div>
{{#priceYearly.formattedMonthlyAmount}}
<div class="plan-price-yearly-per-year">
<div>
({{priceYearly.formattedAmount}}<sub class="per-year">&nbsp;<span translate>/year</span></sub>)
</div>
<div class="plan-price plan-price-monthly">
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
{{/priceYearly.formattedMonthlyAmount}}
<div class="plan-price plan-price-yearly">
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;<span translate>/month</span>{{#footnote}} (*){{/footnote}}</sub>
</div>
</div>
<div class="plan-price-yearly-per-year">
<div>
({{priceYearly.formattedAmount}}<sub class="per-year">&nbsp;<span translate>/year</span></sub>)
</div>
</div>
{{#featureLabelsOn}}
<p><i class="fas fa-check feature feature-on"></i>{{.}}</p>
@@ -33,11 +29,7 @@
{{/featureLabelsOff}}
<p class="text-center subscribe-wrapper">
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton cfa-button">{{cfaLabel}}</a>
{{#learnMoreUrl}}
<a id="learnMore-{{name}}" href="{{learnMoreUrl}}" class="button-link btn-white learnMoreButton cfa-button">Learn more</a>
{{/learnMoreUrl}}
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton">{{cfaLabel}}</a>
</p>
{{#footnote}}<sub>(*) {{.}}</sub>{{/footnote}}

View File

@@ -1,91 +1,23 @@
<div id="plans-section" class="env-{{env}}">
<style>
.toggle-container {
display: flex;
border: 2px solid black;
border-radius: 100px;
overflow: hidden;
cursor: pointer;
margin-top: 20px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.toggle-option {
flex: 1;
padding: 10px 20px;
text-align: center;
transition: background 0.3s, color 0.3s;
user-select: none;
white-space: nowrap;
}
.active {
background: black;
color: white;
}
.inactive {
background: white;
color: black;
}
@media (max-width: 480px) {
.toggle-container {
flex-direction: column;
width: 100%;
border-radius: 10px;
}
}
</style>
<div class="container">
<div class="row">
<div class="col-12 title-box">
<h1 translate class="text-center">
Our synchronisation and sharing <span class="frame-bg frame-bg-yellow">solutions</span>
Joplin Cloud <span class="frame-bg frame-bg-yellow">plans</span>
</h1>
<p translate class="text-center sub-title">
Synchronise and share your notes with our range of plans.
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
</p>
</div>
</div>
<div class="toggle-container" id="toggle">
<div class="toggle-option active toggle-button-managed">Managed hosting</div>
<div class="toggle-option inactive toggle-button-self">Self-hosting</div>
</div>
<noscript>
<div class="alert alert-danger alert-env-dev" role="alert" style='text-align: center; margin-top: 10px;'>
To use this page please enable JavaScript!
</div>
</noscript>
<div class="row hosting-type-managed">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Cloud
</h1>
<p translate class="text-center sub-title">
<a href="https://joplincloud.com">Joplin Cloud</a> allows you to synchronise your notes across devices. It also lets you publish notes, and collaborate on notebooks with your friends, family or colleagues.
</p>
</div>
</div>
<div class="row hosting-type-self">
<div class="col-12 title-box">
<h1 translate class="text-center">
Joplin Server Business
</h1>
<p translate class="text-center sub-title">
Joplin Server Business is a synchronisation server that you can install on your own infrastructure, so that your data remains private and secure within your business.
</p>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.2em" class="hosting-type-managed">
<div style="display: flex; justify-content: center; margin-top: 1.2em">
<div class="form-check form-check-inline">
<input id="pay-monthly-radio" class="form-check-input" type="radio" name="pay-radio" checked value="monthly">
<label translate style="font-weight: bold" class="form-check-label" for="pay-monthly-radio">
@@ -114,11 +46,7 @@
{{> plan}}
{{/plans.teams}}
{{#plans.joplinServerBusiness}}
{{> plan}}
{{/plans.joplinServerBusiness}}
<p translate class="joplin-cloud-login-info hosting-type-managed">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
<p translate class="joplin-cloud-login-info">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
</div>
<div class="row">
@@ -219,29 +147,5 @@
$('.feature-description-' + featureId).toggle(200);
});
});
const setHostingType = (type) => {
const other = type === 'managed' ? 'self' : 'managed';
$('.toggle-button-' + type).addClass('active');
$('.toggle-button-' + type).removeClass('inactive');
$('.toggle-button-' + other).addClass('inactive');
$('.toggle-button-' + other).removeClass('active');
$('.hosting-type-' + type).show();
$('.hosting-type-' + other).hide();
}
$('.toggle-button-managed').click((event) => {
event.preventDefault();
setHostingType('managed');
});
$('.toggle-button-self').click((event) => {
event.preventDefault();
setHostingType('self');
});
const initialHostingType = urlQuery.get('hosting') ? urlQuery.get('hosting') : 'managed';
setHostingType(initialHostingType);
</script>
</div>

View File

@@ -23,6 +23,7 @@ RUN corepack enable
WORKDIR /app
COPY .yarn/plugins ./.yarn/plugins
COPY .yarn/releases ./.yarn/releases
COPY .yarn/patches ./.yarn/patches
COPY package.json .

View File

@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://damangameplay.in"><img title="Daman Game" width="256" src="https://joplinapp.org/images/sponsors/DamanGame.png" alt="Daman Game"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://casinoreviews.net"><img title="Casino Reviews" width="256" src="https://joplinapp.org/images/sponsors/CasinoReviews.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://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> <a href="https://www.reddit.com/r/tiktokRise/"><img title="Tiktok Rise" width="256" src="https://joplinapp.org/images/sponsors/TiktokRise.jpg" alt="Tiktok Rise"/></a> <a href="https://essaywriter.pro"><img title="write my essay services by EssayWriter" width="256" src="https://joplinapp.org/images/sponsors/EssayWriterPro.png" alt="write my essay services by EssayWriter"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a>
<!-- SPONSORS-ORG -->
* * *
@@ -40,8 +40,9 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
| | | | |
| :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars2.githubusercontent.com/u/97193607?s=96&v=4"/></br>[Akhil-CM](https://github.com/Akhil-CM) | <img width="50" src="https://avatars2.githubusercontent.com/u/552452?s=96&v=4"/></br>[andypiper](https://github.com/andypiper) | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1177810?s=96&v=4"/></br>[felixstorm](https://github.com/felixstorm) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/668977?s=96&v=4"/></br>[ugoertz](https://github.com/ugoertz) |
| | | | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1177810?s=96&v=4"/></br>[felixstorm](https://github.com/felixstorm) | <img width="50" src="https://avatars2.githubusercontent.com/u/8030470?s=96&v=4"/></br>[Galliver7](https://github.com/Galliver7) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/668977?s=96&v=4"/></br>[ugoertz](https://github.com/ugoertz) | | | |
<!-- SPONSORS-GITHUB -->
# Community

View File

@@ -5,24 +5,24 @@
"version": "latest",
"platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"yarn": "1.22.19",
"yarn": "latest",
"vips.dev": {
"platforms": ["aarch64-darwin"],
},
"nodejs": "23.10.0",
"nodejs": "latest",
"pkg-config": "latest",
"darwin.apple_sdk.frameworks.Foundation": { // satisfies missing CoreText/CoreText.h
// https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/darwin/apple-sdk/default.nix
"version": "",
"platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"python": "3.13.3",
"python": "latest",
"bat": "latest",
"electron": {
"version": "latest",
"excluded_platforms": ["aarch64-darwin", "x86_64-darwin"],
},
"git": "2.48.1",
"git": "2.47.2",
},
"shell": {
"init_hook": [

View File

@@ -21,7 +21,7 @@ version: '2'
services:
postgresql-master:
image: 'bitnami/postgresql:17.3.0'
image: 'bitnami/postgresql:16.6.0'
ports:
- '5432:5432'
environment:
@@ -38,7 +38,7 @@ services:
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
postgresql-slave:
image: 'bitnami/postgresql:17.3.0'
image: 'bitnami/postgresql:16.6.0'
ports:
- '5433:5432'
depends_on:

View File

@@ -17,21 +17,11 @@
version: '3'
networks:
app-network:
transcribe-network:
shared-network:
services:
db:
image: postgres:16
profiles:
- full
- server
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- app-network
ports:
- "5432:5432"
restart: unless-stopped
@@ -41,17 +31,10 @@ services:
- POSTGRES_DB=${POSTGRES_DATABASE}
app:
image: joplin/server:latest
profiles:
- full
- server
depends_on:
- db
- transcribe
ports:
- "22300:22300"
networks:
- app-network
- shared-network
restart: unless-stopped
environment:
- APP_PORT=22300
@@ -62,48 +45,3 @@ services:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PORT=${POSTGRES_PORT}
- POSTGRES_HOST=db
- TRANSCRIBE_API_KEY=${TRANSCRIBE_API_KEY}
- TRANSCRIBE_BASE_URL=http://transcribe:4567
- TRANSCRIBE_ENABLED=${TRANSCRIBE_ENABLED}
transcribe-db:
image: postgres:16
profiles:
- full
volumes:
- ./data/transcribe-postgres:/var/lib/postgresql/data
networks:
- transcribe-network
ports:
- "${QUEUE_DATABASE_PORT}:5432"
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=${QUEUE_DATABASE_PASSWORD}
- POSTGRES_USER=${QUEUE_DATABASE_USER}
- POSTGRES_DB=${QUEUE_DATABASE_NAME}
command: -p ${QUEUE_DATABASE_PORT}
transcribe:
image: joplin/transcribe:latest
profiles:
- full
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${HTR_CLI_IMAGES_FOLDER}:/app/packages/transcribe/images
depends_on:
- transcribe-db
ports:
- "4567:4567"
networks:
- transcribe-network
- shared-network
restart: unless-stopped
environment:
- APP_PORT=4567
- DB_CLIENT=pg
- QUEUE_DATABASE_NAME=${QUEUE_DATABASE_NAME}
- QUEUE_DATABASE_USER=${QUEUE_DATABASE_USER}
- QUEUE_DATABASE_PASSWORD=${QUEUE_DATABASE_PASSWORD}
- QUEUE_DATABASE_PORT=${QUEUE_DATABASE_PORT}
- QUEUE_DATABASE_HOST=transcribe-db
- API_KEY=${TRANSCRIBE_API_KEY}
- HTR_CLI_IMAGES_FOLDER=${HTR_CLI_IMAGES_FOLDER}

View File

@@ -1,13 +0,0 @@
<strong>Joplin</strong> je besplatan program otvorenog koda za bilješke i popis zadataka koji može obraditi veliki broj bilješki organizirane u bilježnice. Bilješke se mogu pretraživati, kopirati, označavati i mijenjati izravno iz programa ili iz vlastitog uređivača teksta.
Bilješke su u <a href="https://joplinapp.org/help/apps/markdown">Markdown formatu</a>.
Iz Evernotea izvezene bilješke <a href="https://joplinapp.org/help/apps/import_export">mogu se uvesti</a> u Joplin, uključujući formatirani sadržaj (koji se pretvara u Markdown), resurse (slike, privitke itd.) i potpune metapodatke (geografski podaci mjesta, vrijeme aktualiziranja, vrijeme stvaranja itd.). Mogu se uvesti i obične Markdown datoteke.
Joplin radi ponajprije s lokalnim podacima (offline first), što znači da uvijek imaš sve svoje podatke na mobitelu ili računalu. To osigurava da su tvoje bilješke uvijek dostupne, bez obzira je li imaš internetsku vezu ili ne.</p>
Bilješke se mogu sigurno <a href="https://joplinapp.org/help/apps/sync">sinkronizirati</a> pomoću <a href="https://joplinapp.org/help/apps/sync/e2ee">sveobuhvatnog šifriranja</a> s raznim uslugama u oblaku, uključujući Nextcloud, Dropbox, OneDrive i <a href="https://joplinapp.org/plans/">Joplin Cloud</a>.
Pretraživanje cijelog teksta dostupno je na svim platformama za brzo pronalaženje potrebnih informacija. Program se može prilagoditi pomoću dodataka i tema, a možeš stvoriti i vlastite.
Program je dostupan za Windows, Linux, macOS, Android i iOS sustave. <a href="https://joplinapp.org/help/apps/clipper">Web Clipper</a>, za spremanje web stranica i snimaka ekrana iz tvog preglednika, je također dostupan za <a href="https://addons.mozilla.org/firefox/addon/joplin-web-clipper/">Firefox</a> i <a href="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek">Chrome</a>.

View File

@@ -1 +0,0 @@
Program za bilješke i popis zadataka sa sinkronizacijom između Linuxa, macOS-a, Windowsa i mobitela

View File

@@ -38,7 +38,6 @@
"linter-precommit": "eslint --resolve-plugins-relative-to . --fix --ext .js --ext .jsx --ext .ts --ext .tsx",
"linter": "eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"packageJsonLint": "node ./packages/tools/packageJsonLint.js",
"syncFuzzer": "node ./packages/tools/fuzzer/sync-fuzzer.js",
"postinstall": "husky && gulp build",
"postPreReleasesToForum": "node ./packages/tools/postPreReleasesToForum",
"publishAll": "git pull && yarn buildParallel && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
@@ -51,8 +50,6 @@
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
"releasePluginRepoCli": "node packages/tools/release-plugin-repo-cli.js",
"releaseServer": "node packages/tools/release-server.js",
"releaseTranscribe": "node packages/tools/release-transcribe.js",
"saveClaConsentRecords": "node packages/tools/saveClaConsentRecords.js",
"setupNewRelease": "node ./packages/tools/setupNewRelease",
"spellcheck": "node packages/tools/spellcheck.js",
"tagServerLatest": "node packages/tools/tagServerLatest.js",
@@ -68,41 +65,41 @@
"watchWebsite": "nodemon --delay 1 --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --watch packages/doc-builder/build --ext md,ts,js,mustache,css,tsx,gif,png,svg --exec \"node packages/tools/website/build.js && http-server --port 8077 ../joplin-website/docs -a localhost\""
},
"devDependencies": {
"@crowdin/cli": "4",
"@crowdin/cli": "3",
"@joplin/utils": "~2.12",
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"cspell": "5.21.2",
"eslint": "8.57.1",
"eslint": "8.57.0",
"eslint-interactive": "10.8.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jest": "27.9.0",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-react": "7.37.4",
"eslint-plugin-promise": "6.2.0",
"eslint-plugin-react": "7.34.3",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "11.0.2",
"glob": "10.4.5",
"gulp": "4.0.2",
"husky": "9.1.7",
"lerna": "3.22.1",
"lint-staged": "15.5.2",
"madge": "8.0.0",
"npm-package-json-lint": "8.0.0",
"typescript": "5.8.2"
"lint-staged": "15.4.3",
"madge": "7.0.0",
"npm-package-json-lint": "7.1.0",
"typescript": "5.4.5"
},
"dependencies": {
"@types/fs-extra": "11.0.4",
"eslint-plugin-github": "4.10.2",
"http-server": "14.1.1",
"node-gyp": "11.2.0",
"nodemon": "3.1.10"
"node-gyp": "9.4.1",
"nodemon": "3.1.9"
},
"packageManager": "yarn@4.9.2",
"resolutions": {
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
"eslint": "patch:eslint@8.57.1#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"eslint": "patch:eslint@8.57.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
@@ -117,7 +114,6 @@
"pdfjs-dist@2.16.105": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"pdfjs-dist@*": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"pdfjs-dist@3.11.174": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"canvas@npm:^2.11.2": "link:./.yarn/joplin-empty-package/",
"node-gyp@npm:^9.0.0": "11.2.0"
"canvas@npm:^2.11.2": "link:./.yarn/joplin-empty-package/"
}
}

View File

@@ -380,13 +380,6 @@ class AppGui {
this.widget('noteList').toggleShowIds();
}
toggleFolderCollapse() {
const folderList = this.widget('folderList');
if (folderList && folderList.toggleFolderCollapse) {
folderList.toggleFolderCollapse();
}
}
widget(name) {
if (name === 'root') return this.rootWidget_;
return this.rootWidget_.childByName(name);
@@ -513,8 +506,6 @@ class AppGui {
this.toggleNoteMetadata();
} else if (cmd === 'toggle_ids') {
this.toggleFolderIds();
} else if (cmd === 'toggle_folder_collapse') {
this.toggleFolderCollapse();
} else if (cmd === 'enter_command_line_mode') {
const cmd = await this.widget('statusBar').prompt();
if (!cmd) return;

View File

@@ -6,18 +6,19 @@ import Folder from '@joplin/lib/models/Folder';
import BaseItem from '@joplin/lib/models/BaseItem';
import Note from '@joplin/lib/models/Note';
import Tag from '@joplin/lib/models/Tag';
import Setting, { Env } from '@joplin/lib/models/Setting';
import Setting from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry.js';
import { dirname, fileExtension } from '@joplin/lib/path-utils';
import { splitCommandString } from '@joplin/utils';
import { _ } from '@joplin/lib/locale';
import { pathExists, readFile, readdirSync } from 'fs-extra';
import RevisionService from '@joplin/lib/services/RevisionService';
import shim from '@joplin/lib/shim';
import setupCommand from './setupCommand';
import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
import initializeCommandService from './utils/initializeCommandService';
const { cliUtils } = require('./cli-utils.js');
const Cache = require('@joplin/lib/Cache');
const { splitCommandBatch } = require('@joplin/lib/string-utils');
class Application extends BaseApplication {
@@ -75,12 +76,6 @@ class Application extends BaseApplication {
}
}
public async loadItemOrFail(type: ModelType | 'folderOrNote', pattern: string) {
const output = await this.loadItem(type, pattern);
if (!output) throw new Error(_('Cannot find "%s".', pattern));
return output;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async loadItems(type: ModelType | 'folderOrNote', pattern: string, options: any = null): Promise<(FolderEntity | NoteEntity)[]> {
if (type === 'folderOrNote') {
@@ -220,7 +215,6 @@ class Application extends BaseApplication {
return { ...this.commandMetadata_ };
}
public hasGui() {
return this.gui() && !this.gui().isDummy();
}
@@ -331,7 +325,6 @@ class Application extends BaseApplication {
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
{ keys: ['z'], type: 'function', command: 'toggle_folder_collapse' },
];
// Filter the keymap item by command so that items in keymap.json can override
@@ -381,6 +374,22 @@ class Application extends BaseApplication {
return output;
}
public async commandList(argv: string[]) {
if (argv.length && argv[0] === 'batch') {
const commands = [];
const commandLines = splitCommandBatch(await readFile(argv[1], 'utf-8'));
for (const commandLine of commandLines) {
if (!commandLine.trim()) continue;
const splitted = splitCommandString(commandLine.trim());
commands.push(splitted);
}
return commands;
} else {
return [argv];
}
}
// We need this special case here because by the time the `version` command
// runs, the keychain has already been setup.
public checkIfKeychainEnabled(argv: string[]) {
@@ -403,26 +412,22 @@ class Application extends BaseApplication {
this.initRedux();
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
if (!shim.sharpEnabled()) this.logger().warn('Sharp is disabled - certain image-related features will not be available');
initializeCommandService(this.store(), Setting.value('env') === Env.Dev);
// If we have some arguments left at this point, it's a command
// so execute it.
if (argv.length) {
this.gui_ = this.dummyGui();
const initialFolder = await Folder.load(Setting.value('activeFolderId'));
await this.switchCurrentFolder(initialFolder);
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
await this.applySettingsSideEffects();
try {
await this.execCommand(argv);
const commands = await this.commandList(argv);
for (const command of commands) {
await this.execCommand(command);
}
} catch (error) {
if (this.showStackTraces_) {
console.error(error);
@@ -447,6 +452,11 @@ class Application extends BaseApplication {
this.gui_.setLogger(this.logger());
await this.gui_.start();
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
await refreshFolders((action: any) => this.store().dispatch(action), '');

View File

@@ -0,0 +1,19 @@
const BaseCommand = require('./base-command').default;
const { _ } = require('@joplin/lib/locale');
class Command extends BaseCommand {
usage() {
return 'batch <file-path>';
}
description() {
return _('Runs the commands contained in the text file. There should be one command per line.');
}
async action() {
// Implementation is in app.js::commandList()
throw new Error('No implemented');
}
}
module.exports = Command;

View File

@@ -1,79 +0,0 @@
import { splitCommandBatch } from '@joplin/lib/string-utils';
import BaseCommand from './base-command';
import { _ } from '@joplin/lib/locale';
import { splitCommandString } from '@joplin/utils';
import iterateStdin from './utils/iterateStdin';
import { readFile } from 'fs-extra';
import app from './app';
interface Options {
'file-path': string;
options: {
'continue-on-failure': boolean;
};
}
class Command extends BaseCommand {
public usage() {
return 'batch <file-path>';
}
public options() {
return [
// These are present mostly for testing purposes
['--continue-on-failure', 'Continue running commands when one command in the batch fails.'],
];
}
public description() {
return _('Runs the commands contained in the text file. There should be one command per line.');
}
private streamCommands_ = async function*(filePath: string) {
const processLines = function*(lines: string) {
const commandLines = splitCommandBatch(lines);
for (const command of commandLines) {
if (!command.trim()) continue;
yield splitCommandString(command.trim());
}
};
if (filePath === '-') { // stdin
// Iterating over standard input conflicts with the CLI app's GUI.
if (app().hasGui()) {
throw new Error(_('Reading commands from standard input is only available in CLI mode.'));
}
for await (const lines of iterateStdin('command> ')) {
yield* processLines(lines);
}
} else {
const data = await readFile(filePath, 'utf-8');
yield* processLines(data);
}
};
public async action(options: Options) {
let lastError;
for await (const command of this.streamCommands_(options['file-path'])) {
try {
await app().refreshCurrentFolder();
await app().execCommand(command);
} catch (error) {
if (options.options['continue-on-failure']) {
app().stdout(error.message);
lastError = error;
} else {
throw error;
}
}
}
if (lastError) {
throw lastError;
}
}
}
module.exports = Command;

View File

@@ -13,7 +13,7 @@ describe('command-done', () => {
});
it('should make a note as "done"', async () => {
const note = await Note.save({ title: 'hello', is_todo: 1, todo_completed: 0, parent_id: '' });
const note = await Note.save({ title: 'hello', is_todo: 1, todo_completed: 0 });
const command = setupCommandForTesting(Command);

View File

@@ -26,7 +26,6 @@ class Command extends BaseCommand {
['-v, --verbose', 'More verbose output for the `target-status` command'],
['-o, --output <directory>', 'Output directory'],
['--retry-failed-items', 'Applies to `decrypt` command - retries decrypting items that previously could not be decrypted.'],
['-f, --force', 'Do not ask for input on failure'],
];
}
@@ -68,7 +67,7 @@ class Command extends BaseCommand {
this.stdout(line.join('\n'));
break;
} catch (error) {
if (error.code === 'masterKeyNotLoaded' && !args.options.force) {
if (error.code === 'masterKeyNotLoaded') {
const ok = await askForMasterKey(error);
if (!ok) return;
continue;

View File

@@ -6,7 +6,6 @@ import app from './app';
import { _ } from '@joplin/lib/locale';
import { ImportOptions } from '@joplin/lib/services/interop/types';
import { unique } from '@joplin/lib/array';
import Folder from '@joplin/lib/models/Folder';
class Command extends BaseCommand {
public override usage() {
@@ -33,16 +32,14 @@ class Command extends BaseCommand {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public override async action(args: any) {
let destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
if (args.notebook && !destinationFolder) throw new Error(_('Cannot find "%s".', args.notebook));
if (!destinationFolder) destinationFolder = await Folder.defaultFolder();
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
const importOptions: ImportOptions = {};
importOptions.path = args.path;
importOptions.format = args.options.format ? args.options.format : 'auto';
importOptions.destinationFolderId = destinationFolder ? destinationFolder.id : null;
importOptions.destinationFolderId = folder ? folder.id : null;
let lastProgress = '';

View File

@@ -1,104 +0,0 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import mockShareService from '@joplin/lib/testing/share/mockShareService';
import { createFolderTree, setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import Setting from '@joplin/lib/models/Setting';
const Command = require('./command-publish');
const setUpCommand = () => {
const onStdout = jest.fn();
const command = setupCommandForTesting(Command, onStdout);
return { command, onStdout };
};
describe('command-publish', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
mockShareService({
getShares: async () => {
return { items: [] };
},
postShares: async () => ({ id: 'test-id' }),
getShareInvitations: async () => null,
}, ShareService.instance());
});
test('should publish a note', async () => {
const { command, onStdout } = setUpCommand();
const testFolder = await Folder.save({ title: 'Test' });
const testNote = await Note.save({ title: 'test', parent_id: testFolder.id });
await command.action({
note: testNote.id,
options: {
force: true,
},
});
// Should be shared
await waitFor(async () => {
expect(await Note.load(testNote.id)).toMatchObject({
is_shared: 1,
});
});
// Should have logged the publication URL
expect(onStdout).toHaveBeenCalled();
expect(onStdout.mock.lastCall[0]).toMatch(/Published at URL:/);
});
test('should be enabled for Joplin Server and Cloud sync targets', () => {
const { command } = setUpCommand();
Setting.setValue('sync.target', 1);
expect(command.enabled()).toBe(false);
const supportedSyncTargets = [9, 10, 11];
for (const id of supportedSyncTargets) {
Setting.setValue('sync.target', id);
expect(command.enabled()).toBe(true);
}
});
test('should not ask for confirmation if a note is already published', async () => {
const { command } = setUpCommand();
const promptMock = jest.fn(() => true);
command.setPrompt(promptMock);
await createFolderTree('', [
{
title: 'folder 1',
children: [
{
title: 'note 1',
body: 'test',
},
],
},
]);
const noteId = (await Note.loadByTitle('note 1')).id;
// Should ask for confirmation when first sharing
await command.action({
note: noteId,
options: { },
});
expect(promptMock).toHaveBeenCalledTimes(1);
expect(await Note.load(noteId)).toMatchObject({ is_shared: 1 });
// Should not ask for confirmation if called again for the same note
await command.action({
note: noteId,
options: { },
});
expect(promptMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,64 +0,0 @@
import { _ } from '@joplin/lib/locale';
import BaseCommand from './base-command';
import app from './app';
import Logger from '@joplin/utils/Logger';
import ShareService from '@joplin/lib/services/share/ShareService';
import { ModelType } from '@joplin/lib/BaseModel';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import Setting from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('command-publish');
type Args = {
note: string;
options: {
force?: boolean;
};
};
class Command extends BaseCommand {
public usage() {
return 'publish [note]';
}
public description() {
return _('Publishes a note to Joplin Server or Joplin Cloud');
}
public options() {
return [
['-f, --force', _('Do not ask for user confirmation.')],
];
}
public enabled() {
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
}
public async action(args: Args) {
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
const parent = await app().loadItem(ModelType.Folder, targetNote.parent_id);
const force = args.options.force;
const alreadyShared = !!targetNote.is_shared;
const ok = force || alreadyShared ? true : await this.prompt(
_('Publish note "%s" (in notebook "%s")?', targetNote.title, parent.title ?? '<root>'),
{ booleanAnswerDefault: 'n' },
);
if (!ok) return;
logger.info('Share note: ', targetNote.id);
const share = await ShareService.instance().shareNote(targetNote.id, false);
this.stdout(_('Synchronising...'));
await reg.waitForSyncFinishedThenSync();
const userId = ShareService.instance().userId;
const shareUrl = ShareService.instance().shareUrl(userId, share);
this.stdout(_('Published at URL: %s', shareUrl));
}
}
module.exports = Command;

View File

@@ -26,7 +26,8 @@ class Command extends BaseCommand {
const pattern = args['notebook'];
const force = args.options && args.options.force === true;
const folder = await app().loadItemOrFail(BaseModel.TYPE_FOLDER, pattern);
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
const permanent = args.options?.permanent === true || !!folder.deleted_time;
const ellipsizedFolderTitle = substrWithEllipsis(folder.title, 0, 32);

View File

@@ -14,25 +14,17 @@ class Command extends BaseCommand {
return `${_('Start, stop or check the API server. To specify on which port it should run, set the api.port config variable. Commands are (%s).', ['start', 'stop', 'status'].join('|'))} This is an experimental feature - use at your own risks! It is recommended that the server runs off its own separate profile so that no two CLI instances access that profile at the same time. Use --profile to specify the profile path.`;
}
options() {
return [
['--exit-early', 'Allow the command to exit while the server is still running. The server will still stop when the app exits. Valid only for the `start` subcommand.'],
['--quiet', 'Log less information to the console. More verbose logs will still be available through log-clipper.txt.'],
];
}
async action(args) {
const command = args.command;
const ClipperServer = require('@joplin/lib/ClipperServer').default;
ClipperServer.instance().initialize();
const stdoutFn = (...s) => this.stdout(s.join(' '));
const ignoreOutputFn = ()=>{};
const clipperLogger = new Logger();
clipperLogger.addTarget('file', { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget('console', { console: {
info: args.options.quiet ? ignoreOutputFn : stdoutFn,
warn: args.options.quiet ? ignoreOutputFn : stdoutFn,
info: stdoutFn,
warn: stdoutFn,
error: stdoutFn,
} });
ClipperServer.instance().setDispatch(() => {});
@@ -46,11 +38,7 @@ class Command extends BaseCommand {
this.stdout(_('Server is already running on port %d', runningOnPort));
} else {
await shim.fsDriver().writeFile(pidPath, process.pid.toString(), 'utf-8');
const promise = ClipperServer.instance().start();
if (!args.options['exit-early']) {
await promise; // Never exit
}
await ClipperServer.instance().start(); // Never exit
}
} else if (command === 'status') {
this.stdout(runningOnPort ? _('Server is running on port %d', runningOnPort) : _('Server is not running.'));

View File

@@ -1,179 +0,0 @@
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import mockShareService, { ApiMock } from '@joplin/lib/testing/share/mockShareService';
import { setupCommandForTesting, setupApplication } from './utils/testUtils';
import Folder from '@joplin/lib/models/Folder';
import ShareService from '@joplin/lib/services/share/ShareService';
import BaseItem from '@joplin/lib/models/BaseItem';
import { ModelType } from '@joplin/lib/BaseModel';
import { ShareInvitation, ShareUserStatus, StateShare } from '@joplin/lib/services/share/reducer';
import app from './app';
const Command = require('./command-share');
const setUpCommand = () => {
const output: string[] = [];
const stdout = (content: string) => {
output.push(...content.split('\n'));
};
const command = setupCommandForTesting(Command, stdout);
return { command, output };
};
const shareId = 'test-id';
const defaultFolderShare: StateShare = {
id: shareId,
type: ModelType.Folder,
folder_id: 'some-folder-id-here',
note_id: undefined,
master_key_id: undefined,
user: {
full_name: 'Test user',
email: 'test@localhost',
id: 'some-user-id',
},
};
const mockShareServiceForFolderSharing = (eventHandlerOverrides: Partial<ApiMock>&{ onExec?: undefined }) => {
const invitations: ShareInvitation[] = [];
mockShareService({
getShareInvitations: async () => ({
items: invitations,
}),
getShares: async () => ({ items: [defaultFolderShare] }),
getShareUsers: async (_id: string) => ({ items: [] }),
postShareUsers: async (_id, _body) => { },
postShares: async () => ({ id: shareId }),
...eventHandlerOverrides,
}, ShareService.instance(), app().store());
return {
addInvitation: (invitation: Partial<ShareInvitation>) => {
const defaultInvitation: ShareInvitation = {
share: defaultFolderShare,
id: 'some-invitation-id',
master_key: undefined,
status: ShareUserStatus.Waiting,
can_read: 1,
can_write: 1,
};
invitations.push({ ...defaultInvitation, ...invitation });
},
};
};
describe('command-share', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
BaseItem.shareService_ = ShareService.instance();
});
test('should allow adding a user to a share', async () => {
const folder = await Folder.save({ title: 'folder1' });
let lastShareUserUpdate: unknown|null = null;
mockShareServiceForFolderSharing({
getShares: async () => {
const isShared = !!lastShareUserUpdate;
if (isShared) {
return {
items: [{ ...defaultFolderShare, folder_id: folder.id }],
};
} else {
return { items: [] };
}
},
// Called when a new user is added to a share
postShareUsers: async (_id, body) => {
lastShareUserUpdate = body;
},
});
const { command } = setUpCommand();
// Should share read-write by default
await command.action({
'command': 'add',
'notebook': 'folder1',
'user': 'test@localhost',
options: {},
});
expect(lastShareUserUpdate).toMatchObject({
email: 'test@localhost',
can_write: 1,
can_read: 1,
});
// Should also support sharing as read only
await command.action({
'command': 'add',
'notebook': 'folder1',
'user': 'test2@localhost',
options: {
'read-only': true,
},
});
expect(lastShareUserUpdate).toMatchObject({
email: 'test2@localhost',
can_write: 0,
can_read: 1,
});
});
test.each([
{
label: 'should list a single pending invitation',
invitations: [{ id: 'test', status: ShareUserStatus.Waiting }],
expectedOutput: [
'Incoming shares:',
'\tWaiting: Notebook some-folder-id-here from test@localhost',
'All shared folders:',
'\tNone',
].join('\n'),
},
{
label: 'should list accepted invitations for non-existent folders with [None] as the folder title',
invitations: [
{ id: 'test2', status: ShareUserStatus.Accepted },
],
expectedOutput: [
'Incoming shares:',
'\tAccepted: Notebook [None] from test@localhost',
'All shared folders:',
'\tNone',
].join('\n'),
},
{
label: 'should not list rejected shares',
invitations: [
{ id: 'test3', status: ShareUserStatus.Rejected },
],
expectedOutput: [
'Incoming shares:',
'\tNone',
'All shared folders:',
'\tNone',
].join('\n'),
},
])('share invitations: $label', async ({ invitations, expectedOutput }) => {
const mock = mockShareServiceForFolderSharing({});
for (const invitation of invitations) {
mock.addInvitation(invitation);
}
await ShareService.instance().refreshShareInvitations();
const { command, output } = setUpCommand();
await command.action({
'command': 'list',
options: {},
});
expect(output.join('\n')).toBe(expectedOutput);
});
});

View File

@@ -1,298 +0,0 @@
import { _ } from '@joplin/lib/locale';
import BaseCommand from './base-command';
import app from './app';
import { reg } from '@joplin/lib/registry';
import Logger from '@joplin/utils/Logger';
import ShareService from '@joplin/lib/services/share/ShareService';
import { ModelType } from '@joplin/lib/BaseModel';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { ShareUserStatus } from '@joplin/lib/services/share/reducer';
import Folder from '@joplin/lib/models/Folder';
import invitationRespond from '@joplin/lib/services/share/invitationRespond';
import CommandService from '@joplin/lib/services/CommandService';
import { substrWithEllipsis } from '@joplin/lib/string-utils';
const logger = Logger.create('command-share');
type Args = {
command: string;
// eslint-disable-next-line id-denylist -- The "notebook" identifier comes from the UI.
notebook?: string;
user?: string;
options: {
'read-only'?: boolean;
json?: boolean;
force?: boolean;
};
};
const folderTitle = (folder: FolderEntity|null) => {
return folder ? substrWithEllipsis(folder.title, 0, 32) : _('[None]');
};
const getShareState = () => app().store().getState().shareService;
const getShareFromFolderId = (folderId: string) => {
const shareState = getShareState();
const allShares = shareState.shares;
const share = allShares.find(share => share.folder_id === folderId);
return share;
};
const getShareUsers = (folderId: string) => {
const share = getShareFromFolderId(folderId);
if (!share) {
throw new Error(`No share found for folder ${folderId}`);
}
return getShareState().shareUsers[share.id];
};
class Command extends BaseCommand {
public usage() {
return 'share <command> [notebook] [user]';
}
public description() {
return [
_('Shares or unshares the specified [notebook] with [user]. Requires Joplin Cloud or Joplin Server.'),
_('Commands: `add`, `remove`, `list`, `delete`, `accept`, `leave`, and `reject`.'),
].join('\n');
}
public options() {
return [
['--read-only', _('Don\'t allow the share recipient to write to the shared notebook. Valid only for the `add` subcommand.')],
['-f, --force', _('Do not ask for user confirmation.')],
['--json', _('Prefer JSON output.')],
];
}
public async action(args: Args) {
const commandShareAdd = async (folder: FolderEntity, email: string) => {
await reg.waitForSyncFinishedThenSync();
const share = await ShareService.instance().shareFolder(folder.id);
const permissions = {
can_read: 1,
can_write: args.options['read-only'] ? 0 : 1,
};
logger.debug('Sharing folder', folder.id, 'with', email, 'permissions=', permissions);
await ShareService.instance().addShareRecipient(share.id, share.master_key_id, email, permissions);
await ShareService.instance().refreshShares();
await ShareService.instance().refreshShareUsers(share.id);
await reg.waitForSyncFinishedThenSync();
};
const commandShareRemove = async (folder: FolderEntity, email: string) => {
await ShareService.instance().refreshShares();
const share = getShareFromFolderId(folder.id);
if (!share) {
throw new Error(`No share found for folder ${folder.id}`);
}
await ShareService.instance().refreshShareUsers(share.id);
const shareUsers = getShareUsers(folder.id);
if (!shareUsers) {
throw new Error(`No share found for folder ${folder.id}`);
}
const targetUser = shareUsers.find(user => user.user?.email === email);
if (!targetUser) {
throw new Error(`No recipient found with email ${email}`);
}
await ShareService.instance().deleteShareRecipient(targetUser.id);
this.stdout(_('Removed %s from share.', targetUser.user.email));
};
const commandShareList = async () => {
let folder = null;
if (args.notebook) {
folder = await app().loadItemOrFail(ModelType.Folder, args.notebook);
}
await ShareService.instance().maintenance();
if (folder) {
const share = getShareFromFolderId(folder.id);
await ShareService.instance().refreshShareUsers(share.id);
const shareUsers = getShareUsers(folder.id);
const output = {
folderTitle: folderTitle(folder),
sharedWith: (shareUsers ?? []).map(user => ({
email: user.user.email,
readOnly: user.can_read && !user.can_write,
})),
};
if (args.options.json) {
this.stdout(JSON.stringify(output));
} else {
this.stdout(_('Folder "%s" is shared with:', output.folderTitle));
for (const user of output.sharedWith) {
this.stdout(`\t${user.email}\t${user.readOnly ? _('(Read-only)') : ''}`);
}
}
} else {
const shareState = getShareState();
const output = {
invitations: shareState.shareInvitations.map(invitation => ({
accepted: invitation.status === ShareUserStatus.Accepted,
waiting: invitation.status === ShareUserStatus.Waiting,
rejected: invitation.status === ShareUserStatus.Rejected,
folderId: invitation.share.folder_id,
canWrite: !!invitation.can_write,
fromUser: {
email: invitation.share.user?.email,
},
})),
shares: shareState.shares.map(share => ({
isFolder: !!share.folder_id,
isNote: !!share.note_id,
itemId: share.folder_id ?? share.note_id,
fromUser: {
email: share.user?.email,
},
})),
};
if (args.options.json) {
this.stdout(JSON.stringify(output));
} else {
this.stdout(_('Incoming shares:'));
let loggedInvitation = false;
for (const invitation of output.invitations) {
let message;
if (invitation.waiting) {
message = _('Waiting: Notebook %s from %s', invitation.folderId, invitation.fromUser.email);
}
if (invitation.accepted) {
const folder = await Folder.load(invitation.folderId);
message = _('Accepted: Notebook %s from %s', folderTitle(folder), invitation.fromUser.email);
}
if (message) {
this.stdout(`\t${message}`);
loggedInvitation = true;
}
}
if (!loggedInvitation) {
this.stdout(`\t${_('None')}`);
}
this.stdout(_('All shared folders:'));
if (output.shares.length) {
for (const share of output.shares) {
let title;
if (share.isFolder) {
title = folderTitle(await Folder.load(share.itemId));
} else {
title = share.itemId;
}
if (share.fromUser?.email) {
this.stdout(`\t${_('%s from %s', title, share.fromUser?.email)}`);
} else {
this.stdout(`\t${title} - ${share.itemId}`);
}
}
} else {
this.stdout(`\t${_('None')}`);
}
}
}
};
const commandShareAcceptOrReject = async (folderId: string, accept: boolean) => {
await ShareService.instance().maintenance();
const shareState = getShareState();
const invitations = shareState.shareInvitations.filter(invitation => {
return invitation.share.folder_id === folderId && invitation.status === ShareUserStatus.Waiting;
});
if (invitations.length === 0) throw new Error('No such invitation found');
// If there are multiple invitations for the same folder, stop early to avoid
// accepting the wrong invitation.
if (invitations.length > 1) throw new Error('Multiple invitations found with the same ID');
const invitation = invitations[0];
this.stdout(accept ? _('Accepting share...') : _('Rejecting share...'));
await invitationRespond(invitation.id, invitation.share.folder_id, invitation.master_key, accept);
};
const commandShareAccept = (folderId: string) => (
commandShareAcceptOrReject(folderId, true)
);
const commandShareReject = (folderId: string) => (
commandShareAcceptOrReject(folderId, false)
);
const commandShareDelete = async (folder: FolderEntity) => {
const force = args.options.force;
const ok = force ? true : await this.prompt(
_('Unshare notebook "%s"? This may cause other users to lose access to the notebook.', folderTitle(folder)),
{ booleanAnswerDefault: 'n' },
);
if (!ok) return;
logger.info('Unsharing folder', folder.id);
await ShareService.instance().unshareFolder(folder.id);
await reg.scheduleSync();
};
if (args.command === 'add' || args.command === 'remove' || args.command === 'delete') {
if (!args.notebook) throw new Error('[notebook] is required');
const folder = await app().loadItemOrFail(ModelType.Folder, args.notebook);
if (args.command === 'delete') {
return commandShareDelete(folder);
} else {
if (!args.user) throw new Error('[user] is required');
const email = args.user;
if (args.command === 'add') {
return commandShareAdd(folder, email);
} else if (args.command === 'remove') {
return commandShareRemove(folder, email);
}
}
}
if (args.command === 'leave') {
const folder = args.notebook ? await app().loadItemOrFail(ModelType.Folder, args.notebook) : null;
await ShareService.instance().maintenance();
return CommandService.instance().execute(
'leaveSharedFolder', folder?.id, { force: args.options.force },
);
}
if (args.command === 'list') {
return commandShareList();
}
if (args.command === 'accept') {
return commandShareAccept(args.notebook);
}
if (args.command === 'reject') {
return commandShareReject(args.notebook);
}
throw new Error(`Unknown subcommand: ${args.command}`);
}
}
module.exports = Command;

View File

@@ -17,7 +17,6 @@ import { pathExists, writeFile } from 'fs-extra';
import { checkIfLoginWasSuccessful, generateApplicationConfirmUrl } from '@joplin/lib/services/joplinCloudUtils';
import Logger from '@joplin/utils/Logger';
import { uuidgen } from '@joplin/lib/uuid';
import ShareService from '@joplin/lib/services/share/ShareService';
const logger = Logger.create('command-sync');
@@ -231,10 +230,6 @@ class Command extends BaseCommand {
return cleanUp();
}
// Refresh share invitations -- if running without a GUI, some of the
// maintenance tasks may otherwise be skipped.
await ShareService.instance().maintenance();
this.stdout(_('Starting synchronisation...'));
const contextKey = `sync.${this.syncTargetId_}.context`;

View File

@@ -1,43 +0,0 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import mockShareService from '@joplin/lib/testing/share/mockShareService';
import { setupDatabaseAndSynchronizer, switchClient, waitFor } from '@joplin/lib/testing/test-utils';
import { setupApplication, setupCommandForTesting } from './utils/testUtils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
const Command = require('./command-unpublish');
describe('command-unpublish', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await setupApplication();
mockShareService({
getShares: async () => {
return { items: [{ id: 'test-id' }] };
},
postShares: async () => {
throw new Error('Unexpected call to postShares');
},
getShareInvitations: async () => null,
}, ShareService.instance());
});
test('should unpublish a note', async () => {
const command = setupCommandForTesting(Command, ()=>{});
const testFolder = await Folder.save({ title: 'Test' });
const testNote = await Note.save({ title: 'test', parent_id: testFolder.id, is_shared: 1 });
await command.action({
note: testNote.id,
});
await waitFor(async () => {
expect(await Note.load(testNote.id)).toMatchObject({
is_shared: 0,
});
});
});
});

View File

@@ -1,57 +0,0 @@
import { _ } from '@joplin/lib/locale';
import BaseCommand from './base-command';
import app from './app';
import Logger from '@joplin/utils/Logger';
import ShareService from '@joplin/lib/services/share/ShareService';
import { ModelType } from '@joplin/lib/BaseModel';
import Note from '@joplin/lib/models/Note';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import Setting from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry';
const logger = Logger.create('command-unpublish');
type Args = {
note: string;
};
class Command extends BaseCommand {
public usage() {
return 'publish [note]';
}
public description() {
return _('Publishes a note to Joplin Server or Joplin Cloud');
}
public options() {
return [
['-f, --force', _('Do not ask for user confirmation.')],
];
}
public enabled() {
return SyncTargetRegistry.isJoplinServerOrCloud(Setting.value('sync.target'));
}
public async action(args: Args) {
const targetNote = await app().loadItemOrFail(ModelType.Note, args.note);
if (!targetNote.is_shared) {
throw new Error(_('Note not published: %s', targetNote.title));
}
logger.info('Unshare note: ', targetNote.id);
await ShareService.instance().unshareNote(targetNote.id);
const note = await Note.load(targetNote.id);
if (note.is_shared) {
throw new Error('Assertion failure: The note is still shared.');
}
this.stdout(_('Synchronising...'));
await reg.waitForSyncFinishedThenSync();
}
}
module.exports = Command;

View File

@@ -2,7 +2,6 @@ import BaseCommand from './base-command';
import app from './app';
import { _ } from '@joplin/lib/locale';
import BaseModel from '@joplin/lib/BaseModel';
import Folder from '@joplin/lib/models/Folder';
class Command extends BaseCommand {
public override usage() {
@@ -21,18 +20,6 @@ class Command extends BaseCommand {
public override async action(args: any) {
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
// Auto-expand parent folders in GUI if present
if (app().gui() && app().gui().widget && app().gui().widget('folderList')) {
const folderListWidget = app().gui().widget('folderList');
if (folderListWidget.expandToFolder) {
// Get all folders to pass to expandToFolder
const folders = await Folder.all();
folderListWidget.folders = folders; // Ensure widget has current folders
folderListWidget.expandToFolder(folder.id);
}
}
app().switchCurrentFolder(folder);
}
}

View File

@@ -4,14 +4,11 @@ import BaseModel from '@joplin/lib/BaseModel';
import Setting from '@joplin/lib/models/Setting';
import { _ } from '@joplin/lib/locale';
import { FolderEntity } from '@joplin/lib/services/database/types';
import {
getDisplayParentId,
getTrashFolderId,
} from '@joplin/lib/services/trash';
import { getDisplayParentId, getTrashFolderId } from '@joplin/lib/services/trash';
const ListWidget = require('tkwidgets/ListWidget.js');
export default class FolderListWidget extends ListWidget {
private folders_: FolderEntity[] = [];
public constructor() {
@@ -34,18 +31,7 @@ export default class FolderListWidget extends ListWidget {
if (item === '-') {
output.push('-'.repeat(this.innerWidth));
} else if (item.type_ === Folder.modelType()) {
const depth = this.folderDepth(this.folders, item.id);
output.push(' '.repeat(depth));
// Add collapse/expand indicator
const hasChildren = this.folderHasChildren_(this.folders, item.id);
if (hasChildren) {
const collapsedFolders = Setting.value('collapsedFolderIds');
const isCollapsed = collapsedFolders.includes(item.id);
output.push(isCollapsed ? '[+] ' : '[-] ');
} else {
output.push(' '); // Space for alignment
}
output.push(' '.repeat(this.folderDepth(this.folders, item.id)));
if (this.showIds) {
output.push(Folder.shortId(item.id));
@@ -79,10 +65,7 @@ export default class FolderListWidget extends ListWidget {
let output = 0;
while (true) {
const folder = BaseModel.byId(folders, folderId);
const folderParentId = getDisplayParentId(
folder,
folders.find((f) => f.id === folder.parent_id),
);
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
if (!folder || !folderParentId) return output;
output++;
folderId = folderParentId;
@@ -170,10 +153,7 @@ export default class FolderListWidget extends ListWidget {
public folderHasChildren_(folders: FolderEntity[], folderId: string) {
for (let i = 0; i < folders.length; i++) {
const folder = folders[i];
const folderParentId = getDisplayParentId(
folder,
folders.find((f) => f.id === folder.parent_id),
);
const folderParentId = getDisplayParentId(folder, folders.find(f => f.id === folder.parent_id));
if (folderParentId === folderId) return true;
}
return false;
@@ -181,12 +161,7 @@ export default class FolderListWidget extends ListWidget {
public render() {
if (this.updateItems_) {
this.logger().debug(
'Rebuilding items...',
this.notesParentType,
this.selectedJoplinItemId,
this.selectedSearchId,
);
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
const wasSelectedItemId = this.selectedJoplinItemId;
const previousParentType = this.notesParentType;
@@ -195,20 +170,12 @@ export default class FolderListWidget extends ListWidget {
const orderFolders = (parentId: string) => {
for (let i = 0; i < this.folders.length; i++) {
const f = this.folders[i];
const originalParent = this.folders_.find(
(f) => f.id === f.parent_id,
);
const originalParent = this.folders_.find(f => f.id === f.parent_id);
const folderParentId = getDisplayParentId(f, originalParent); // f.parent_id ? f.parent_id : '';
if (folderParentId === parentId) {
newItems.push(f);
// Only recurse into children if the folder is not collapsed
if (this.folderHasChildren_(this.folders, f.id)) {
const collapsedFolders = Setting.value('collapsedFolderIds');
if (!collapsedFolders.includes(f.id)) {
orderFolders(f.id);
}
}
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
}
}
};
@@ -254,53 +221,4 @@ export default class FolderListWidget extends ListWidget {
const index = this.itemIndexByKey('id', itemId);
this.currentIndex = index >= 0 ? index : 0;
}
public toggleFolderCollapse() {
const item = this.currentItem;
if (item && item.type_ === Folder.modelType() && this.folderHasChildren_(this.folders, item.id)) {
const collapsedFolders = Setting.value('collapsedFolderIds');
const isCollapsed = collapsedFolders.includes(item.id);
if (isCollapsed) {
const newCollapsed = collapsedFolders.filter((id: string) => id !== item.id);
Setting.setValue('collapsedFolderIds', newCollapsed);
} else {
Setting.setValue('collapsedFolderIds', [...collapsedFolders, item.id]);
}
this.updateItems_ = true;
this.invalidate();
return true;
}
return false;
}
public expandToFolder(folderId: string) {
// Find all parent folders and expand them
const parentsToExpand: string[] = [];
let currentId = folderId;
while (currentId) {
const folder = BaseModel.byId(this.folders, currentId);
if (!folder) break;
const parentId = getDisplayParentId(
folder,
this.folders.find((f) => f.id === folder.parent_id),
);
if (parentId) {
parentsToExpand.unshift(parentId);
currentId = parentId;
} else {
break;
}
}
// Expand all parent folders
const collapsedFolders = Setting.value('collapsedFolderIds');
const newCollapsed = collapsedFolders.filter((id: string) => !parentsToExpand.includes(id));
Setting.setValue('collapsedFolderIds', newCollapsed);
this.updateItems_ = true;
this.invalidate();
}
}

View File

@@ -22,7 +22,7 @@ const Setting = require('@joplin/lib/models/Setting').default;
const Revision = require('@joplin/lib/models/Revision').default;
const Logger = require('@joplin/utils/Logger').default;
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
const shimInitCli = require('./utils/shimInitCli').default;
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const shim = require('@joplin/lib/shim').default;
const { _ } = require('@joplin/lib/locale');
const FileApiDriverLocal = require('@joplin/lib/file-api-driver-local').default;
@@ -73,7 +73,7 @@ function appVersion() {
return p.version;
}
shimInitCli({ sharp, keytar, appVersion, nodeSqlite });
shimInit({ sharp, keytar, appVersion, nodeSqlite });
const logger = new Logger();
Logger.initializeGlobalLogger(logger);

View File

@@ -1,14 +0,0 @@
import CommandService from '@joplin/lib/services/CommandService';
import stateToWhenClauseContext from '@joplin/lib/services/commands/stateToWhenClauseContext';
import libCommands from '@joplin/lib/commands/index';
import { State } from '@joplin/lib/reducer';
import { Store } from 'redux';
export default function initializeCommandService(store: Store<State>, devMode: boolean) {
CommandService.instance().initialize(store, devMode, stateToWhenClauseContext);
for (const command of libCommands) {
CommandService.instance().registerDeclaration(command.declaration);
CommandService.instance().registerRuntime(command.declaration.name, command.runtime());
}
}

View File

@@ -1,54 +0,0 @@
import { createInterface } from 'readline/promises';
const iterateStdin = async function*(prompt: string) {
let nextLineListeners: (()=> void)[] = [];
const dispatchAllListeners = () => {
const listeners = nextLineListeners;
nextLineListeners = [];
for (const listener of listeners) {
listener();
}
};
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
rl.setPrompt(prompt);
let buffer: string[] = [];
rl.on('line', (line) => {
buffer.push(line);
dispatchAllListeners();
});
let done = false;
rl.on('close', () => {
done = true;
dispatchAllListeners();
});
const readNextLines = () => {
return new Promise<string|null>(resolve => {
if (done) {
resolve(null);
} else if (buffer.length > 0) {
resolve(buffer.join('\n'));
buffer = [];
} else {
nextLineListeners.push(() => {
resolve(buffer.join('\n'));
buffer = [];
});
}
});
};
while (!done) {
rl.prompt();
const lines = await readNextLines();
yield lines;
}
};
export default iterateStdin;

View File

@@ -1,32 +0,0 @@
import shim, { ShowMessageBoxOptions } from '@joplin/lib/shim';
import type { ShimInitOptions } from '@joplin/lib/shim-init-node';
import app from '../app';
import { _ } from '@joplin/lib/locale';
const { shimInit } = require('@joplin/lib/shim-init-node.js');
const shimInitCli = (options: ShimInitOptions) => {
shimInit(options);
shim.showMessageBox = async (message: string, options: ShowMessageBoxOptions) => {
const gui = app()?.gui();
let answers = options.buttons ?? [_('OK'), _('Cancel')];
if (options.type === 'error' || options.type === 'info') {
answers = [];
}
message += answers.length ? `(${answers.join(', ')})` : '';
const answer = await gui.prompt(options.title ?? '', `${message} `, { answers });
if (answers.includes(answer)) {
return answers.indexOf(answer);
} else if (answer) {
return answers.findIndex(a => a.startsWith(answer));
}
return -1;
};
};
export default shimInitCli;

View File

@@ -15,7 +15,4 @@ export const setupApplication = async () => {
// such notebook.
await Folder.save({ title: 'default' });
await app().refreshCurrentFolder();
// Some tests also need access to the Redux store
app().initRedux();
};

View File

@@ -35,7 +35,7 @@
],
"owner": "Laurent Cozic"
},
"version": "3.4.1",
"version": "3.4.0",
"bin": "./main.js",
"engines": {
"node": ">=10.0.0"
@@ -55,9 +55,8 @@
"node-rsa": "1.1.1",
"open": "8.4.2",
"proper-lockfile": "4.1.2",
"redux": "4.2.1",
"server-destroy": "1.0.1",
"sharp": "0.34.2",
"sharp": "0.33.5",
"sprintf-js": "1.1.3",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
@@ -72,12 +71,12 @@
"devDependencies": {
"@joplin/tools": "~3.4",
"@types/fs-extra": "11.0.4",
"@types/jest": "29.5.14",
"@types/node": "18.19.103",
"@types/jest": "29.5.12",
"@types/node": "18.19.67",
"@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2",
"jest": "29.7.0",
"temp": "0.9.4",
"typescript": "5.8.2"
"typescript": "5.4.5"
}
}

View File

@@ -1,6 +1,6 @@
import MarkupToHtml from '@joplin/renderer/MarkupToHtml';
import { RenderResult, MarkupLanguage } from '@joplin/renderer/types';
import MarkupToHtml, { MarkupLanguage } from '@joplin/renderer/MarkupToHtml';
import { RenderResult } from '@joplin/renderer/types';
describe('MarkupToHtml', () => {

View File

@@ -1 +0,0 @@
<p><span style="/* Comment */ text-decoration: underline;">Test</span>. In the past, <span style="font-size: auto;/* Test! */">comments</span> in CSS have caused issues.</p>

View File

@@ -1 +0,0 @@
<ins>Test</ins>. In the past, comments in CSS have caused issues.

View File

@@ -1,13 +0,0 @@
<p>A task list created by the TipTap editor:</p>
<ul data-type="taskList">
<li><label contenteditable="false"><input type="checkbox"><span></span></label>
<div>
<p>Testing...</p>
</div>
</li>
<li><label contenteditable="false"><input type="checkbox"><span></span></label>
<div>
<p>testing</p>
</div>
</li>
</ul>

View File

@@ -1,5 +0,0 @@
A task list created by the TipTap editor:
- [ ] Testing...
- [ ] testing

View File

@@ -1,26 +0,0 @@
<p>List 1:</p>
<ul>
<li><label><input type="checkbox"/>This</label></li>
<li><label><input type="checkbox" checked/>is a test.</label></li>
</ul>
<p>List 2:</p>
<ul>
<li>
<input type="checkbox" id="checkbox-1"/><label for="checkbox-1">This</label>
</li>
<li>
<input type="checkbox" checked id="checkbox-2"/><label for="checkbox-2">is another test.</label>
</li>
</ul>
<p>List 3:</p>
<ul>
<li>
<input type="checkbox" id="checkbox-a1"/><label for="checkbox-a1">This</label>
</li>
<li>
<input type="checkbox" checked id="checkbox-a2"/><label for="checkbox-a2">is another test.</label>
</li>
<li>
<input type="checkbox" checked id="checkbox-a3"/><label for="checkbox-a3"></label>
</li>
</ul>

View File

@@ -1,15 +0,0 @@
List 1:
- [ ] This
- [x] is a test.
List 2:
- [ ] This
- [x] is another test.
List 3:
- [ ] This
- [x] is another test.
- [x] &nbsp;

View File

@@ -1,7 +1,7 @@
<ul class="joplin-checklist" data-is-checklist="1">
<ul class="joplin-checklist">
<li>Not checked</li>
<li class="checked">Checked!!
<ul class="joplin-checklist" data-is-checklist="1">
<ul class="joplin-checklist">
<li class="checked">Indented, with <strong>bold</strong></li>
<li>Indented, not checked</li>
</ul>

View File

@@ -1,15 +1,15 @@
<span class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22345" data-original-alt data-original-title="test" contenteditable="false"><img src="data:image/svg+xml;utf8,
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22345" data-original-alt data-original-title="test" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></span>
<span class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22346" data-original-alt="test" data-original-title contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;"/></div>
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22346" data-original-alt="test" data-original-title contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></span>
<span class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22347" data-original-before=" " data-original-after=" class=&quot;jop-noMdConv&quot;/" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;"/></div>
<div class="not-loaded-resource not-loaded-image-resource resource-status-notDownloaded" data-resource-id="a1test2a1test2a1test2a1test22347" data-original-before=" " data-original-after=" class=&quot;jop-noMdConv&quot;/" contenteditable="false"><img src="data:image/svg+xml;utf8,
&Tab;&Tab;&lt;svg width=&quot;1700&quot; height=&quot;1536&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&Tab;&Tab; &lt;path d=&quot;M1280 1344c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm256 0c0-35-29-64-64-64s-64 29-64 64 29 64 64 64 64-29 64-64zm128-224v320c0 53-43 96-96 96H96c-53 0-96-43-96-96v-320c0-53 43-96 96-96h465l135 136c37 36 85 56 136 56s99-20 136-56l136-136h464c53 0 96 43 96 96zm-325-569c10 24 5 52-14 70l-448 448c-12 13-29 19-45 19s-33-6-45-19L339 621c-19-18-24-46-14-70 10-23 33-39 59-39h256V64c0-35 29-64 64-64h256c35 0 64 29 64 64v448h256c26 0 49 16 59 39z&quot;/&gt;
&Tab;&Tab;&lt;/svg&gt;
&Tab;"/></span>
&Tab;"/></div>

View File

@@ -1,7 +0,0 @@
---
aliases:
- An Obsidian-style note
---
This is a note with no `title` field in the YAML Frontmatter.
Joplin should be smart enough to pull the title from the filename in such cases.

View File

@@ -8,7 +8,7 @@ import { FileLocker } from '@joplin/utils/fs';
import { IpcMessageHandler, IpcServer, Message, newHttpError, sendMessage, SendMessageOptions, startServer, stopServer } from '@joplin/utils/ipc';
import { BrowserWindow, Tray, WebContents, screen, App, nativeTheme } from 'electron';
import bridge from './bridge';
import * as url from 'url';
const url = require('url');
const path = require('path');
const { dirname } = require('@joplin/lib/path-utils');
const fs = require('fs-extra');
@@ -343,14 +343,6 @@ export default class ElectronAppWrapper {
}, 1000);
}
const sendWindowFocused = (focusedWebContents: WebContents) => {
const joplinId = this.windowIdFromWebContents(focusedWebContents);
if (joplinId !== null) {
this.win_.webContents.send('window-focused', joplinId);
}
};
const addWindowEventHandlers = (webContents: WebContents) => {
// will-frame-navigate is fired by clicking on a link within the BrowserWindow.
webContents.on('will-frame-navigate', event => {
@@ -384,10 +376,13 @@ export default class ElectronAppWrapper {
addWindowEventHandlers(event.webContents);
});
const onFocus = () => {
sendWindowFocused(webContents);
};
webContents.on('focus', onFocus);
webContents.on('focus', () => {
const joplinId = this.windowIdFromWebContents(webContents);
if (joplinId !== null) {
this.win_.webContents.send('window-focused', joplinId);
}
});
};
addWindowEventHandlers(this.win_.webContents);
@@ -459,10 +454,6 @@ export default class ElectronAppWrapper {
this.win_.close();
}
});
if (window.isFocused()) {
sendWindowFocused(window.webContents);
}
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@@ -55,16 +55,11 @@ import userFetcher, { initializeUserFetcher } from '@joplin/lib/utils/userFetche
import { parseNotesParent } from '@joplin/lib/reducer';
import OcrService from '@joplin/lib/services/ocr/OcrService';
import OcrDriverTesseract from '@joplin/lib/services/ocr/drivers/OcrDriverTesseract';
import OcrDriverTranscribe from '@joplin/lib/services/ocr/drivers/OcrDriverTranscribe';
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';
import OcrDriverBase from '@joplin/lib/services/ocr/OcrDriverBase';
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
const perfLogger = PerformanceLogger.create();
const pluginClasses = [
require('./plugins/GotoAnything').default,
@@ -72,8 +67,6 @@ const pluginClasses = [
const appDefaultState = createAppDefaultState(resourceEditWatcherDefaultState);
type StartupTask = { label: string; task: ()=> void|Promise<void> };
class Application extends BaseApplication {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -355,19 +348,16 @@ class Application extends BaseApplication {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const Tesseract = (window as any).Tesseract;
const drivers: OcrDriverBase[] = [];
drivers.push(new OcrDriverTesseract(
const driver = new OcrDriverTesseract(
{ createWorker: Tesseract.createWorker },
{
workerPath: `${bridge().buildDir()}/tesseract.js/worker.min.js`,
corePath: `${bridge().buildDir()}/tesseract.js-core`,
languageDataPath: Setting.value('ocr.languageDataPath') || null,
},
));
);
drivers.push(new OcrDriverTranscribe());
this.ocrService_ = new OcrService(drivers);
this.ocrService_ = new OcrService(driver);
}
void this.ocrService_.runInBackground();
@@ -421,53 +411,56 @@ class Application extends BaseApplication {
});
}
private buildStartupTasks_() {
const tasks: StartupTask[] = [];
const addTask = (label: string, task: StartupTask['task']) => {
tasks.push({ label, task });
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async start(argv: string[], startOptions: StartOptions = null): Promise<any> {
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
// insert an extra argument so that they can be processed in a consistent way everywhere.
if (!bridge().electronIsDev()) argv.splice(1, 0, '.');
addTask('app/set up extra debug logging', () => {
reg.logger().info('app.start: doing regular boot');
const dir: string = Setting.value('profileDir');
argv = await super.start(argv, startOptions);
syncDebugLog.enabled = false;
await this.setupIntegrationTestUtils();
if (dir.endsWith('dev-desktop-2')) {
syncDebugLog.addTarget(TargetType.File, {
path: `${homedir()}/synclog.txt`,
});
syncDebugLog.enabled = true;
syncDebugLog.info(`Profile dir: ${dir}`);
}
});
bridge().setLogFilePath(Logger.globalLogger.logFilePath());
addTask('app/set up registry', () => {
reg.setDispatch(this.dispatch.bind(this));
reg.setShowErrorMessageBoxHandler((message: string) => { bridge().showErrorMessageBox(message); });
});
await this.applySettingsSideEffects();
addTask('app/set up auto updater', () => {
this.setupAutoUpdaterService();
});
addTask('app/set up AlarmService', () => {
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
});
if (Setting.value('flagOpenDevTools')) {
addTask('app/openDevTools', () => {
bridge().openDevTools();
});
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
reg.logger().info('app.start: doing upgradeSyncTarget action');
bridge().mainWindow().show();
return { action: 'upgradeSyncTarget' };
}
addTask('app/set up custom protocol handler', async () => {
this.protocolHandler_ = bridge().electronApp().getCustomProtocolHandler();
this.protocolHandler_.allowReadAccessToDirectory(__dirname); // App bundle directory
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('cacheDir'));
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('resourceDir'));
});
reg.logger().info('app.start: doing regular boot');
const dir: string = Setting.value('profileDir');
syncDebugLog.enabled = false;
if (dir.endsWith('dev-desktop-2')) {
syncDebugLog.addTarget(TargetType.File, {
path: `${homedir()}/synclog.txt`,
});
syncDebugLog.enabled = true;
syncDebugLog.info(`Profile dir: ${dir}`);
}
this.setupAutoUpdaterService();
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
reg.setDispatch(this.dispatch.bind(this));
reg.setShowErrorMessageBoxHandler((message: string) => { bridge().showErrorMessageBox(message); });
if (Setting.value('flagOpenDevTools')) {
bridge().openDevTools();
}
this.protocolHandler_ = bridge().electronApp().getCustomProtocolHandler();
this.protocolHandler_.allowReadAccessToDirectory(__dirname); // App bundle directory
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('cacheDir'));
this.protocolHandler_.allowReadAccessToDirectory(Setting.value('resourceDir'));
// this.protocolHandler_.allowReadAccessTo(Setting.value('tempDir'));
// For now, this doesn't seem necessary:
// this.protocolHandler_.allowReadAccessTo(Setting.value('profileDir'));
@@ -475,52 +468,44 @@ class Application extends BaseApplication {
// handler, and, as such, it may make sense to also limit permissions of
// allowed pages with a Content Security Policy.
addTask('app/initialize PluginManager, redux, CommandService, and KeymapService', async () => {
PluginManager.instance().dispatch_ = this.dispatch.bind(this);
PluginManager.instance().setLogger(reg.logger());
PluginManager.instance().register(pluginClasses);
PluginManager.instance().dispatch_ = this.dispatch.bind(this);
PluginManager.instance().setLogger(reg.logger());
PluginManager.instance().register(pluginClasses);
this.initRedux();
this.initRedux();
initializeCommandService(this.store(), Setting.value('env') === 'dev');
PerFolderSortOrderService.initialize();
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService.initialize(menuCommandNames());
initializeCommandService(this.store(), Setting.value('env') === 'dev');
try {
await keymapService.loadCustomKeymap(`${Setting.value('profileDir')}/keymap-desktop.json`);
} catch (error) {
reg.logger().error(error);
}
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService.initialize(menuCommandNames());
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (error) {
reg.logger().error(error);
}
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
await refreshFolders((action: any) => this.dispatch(action), '');
const tags = await Tag.allWithNotes();
this.dispatch({
type: 'TAG_UPDATE_ALL',
items: tags,
});
addTask('app/initialize PerFolderSortOrderService', () => {
PerFolderSortOrderService.initialize();
});
addTask('app/dispatch initial settings', () => {
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
});
addTask('app/update folders and tags', async () => {
await refreshFolders((action) => this.dispatch(action), '');
const tags = await Tag.allWithNotes();
this.dispatch({
type: 'TAG_UPDATE_ALL',
items: tags,
});
});
addTask('app/set up custom CSS', async () => {
await this.setupCustomCss();
});
await this.setupCustomCss();
// const masterKeys = await MasterKey.all();
@@ -529,237 +514,188 @@ class Application extends BaseApplication {
// items: masterKeys,
// });
addTask('app/send initial selection to redux', async () => {
const getNotesParent = async () => {
let notesParent = parseNotesParent(Setting.value('notesParent'), Setting.value('activeFolderId'));
if (notesParent.type === 'Tag' && !(await Tag.load(notesParent.selectedItemId))) {
notesParent = {
type: 'Folder',
selectedItemId: Setting.value('activeFolderId'),
};
}
return notesParent;
};
const notesParent = await getNotesParent();
if (notesParent.type === 'SmartFilter') {
this.store().dispatch({
type: 'SMART_FILTER_SELECT',
id: notesParent.selectedItemId,
});
} else if (notesParent.type === 'Tag') {
this.store().dispatch({
type: 'TAG_SELECT',
id: notesParent.selectedItemId,
});
} else {
this.store().dispatch({
type: 'FOLDER_SELECT',
id: notesParent.selectedItemId,
});
const getNotesParent = async () => {
let notesParent = parseNotesParent(Setting.value('notesParent'), Setting.value('activeFolderId'));
if (notesParent.type === 'Tag' && !(await Tag.load(notesParent.selectedItemId))) {
notesParent = {
type: 'Folder',
selectedItemId: Setting.value('activeFolderId'),
};
}
return notesParent;
};
const notesParent = await getNotesParent();
if (notesParent.type === 'SmartFilter') {
this.store().dispatch({
type: 'FOLDER_SET_COLLAPSED_ALL',
ids: Setting.value('collapsedFolderIds'),
type: 'SMART_FILTER_SELECT',
id: notesParent.selectedItemId,
});
} else if (notesParent.type === 'Tag') {
this.store().dispatch({
type: 'NOTE_DEVTOOLS_SET',
value: Setting.value('flagOpenDevTools'),
type: 'TAG_SELECT',
id: notesParent.selectedItemId,
});
} else {
this.store().dispatch({
type: 'FOLDER_SELECT',
id: notesParent.selectedItemId,
});
}
this.store().dispatch({
type: 'FOLDER_SET_COLLAPSED_ALL',
ids: Setting.value('collapsedFolderIds'),
});
addTask('app/initializeUserFetcher', async () => {
initializeUserFetcher();
shim.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60);
this.store().dispatch({
type: 'NOTE_DEVTOOLS_SET',
value: Setting.value('flagOpenDevTools'),
});
addTask('app/updateTray', () => this.updateTray());
// 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);
}
addTask('app/set main window state', () => {
if (Setting.value('startMinimized') && Setting.value('showTrayIcon')) {
bridge().mainWindow().hide();
} else {
bridge().mainWindow().show();
// 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
if (!Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
void checkForUpdates(true, bridge().mainWindow(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
// Initial check on startup
shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
}
});
}
addTask('app/start maintenance tasks', () => {
// 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);
}
initializeUserFetcher();
shim.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60);
// 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
if (!Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {
void checkForUpdates(true, bridge().mainWindow(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
};
this.updateTray();
// Initial check on startup
shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
}
}
shim.setTimeout(() => {
void AlarmService.garbageCollect();
}, 1000 * 60 * 60);
shim.setTimeout(() => {
void AlarmService.garbageCollect();
}, 1000 * 60 * 60);
void ShareService.instance().maintenance();
if (Setting.value('startMinimized') && Setting.value('showTrayIcon')) {
bridge().mainWindow().hide();
} else {
bridge().mainWindow().show();
}
ResourceService.runInBackground();
void ShareService.instance().maintenance();
if (Setting.value('env') === 'dev') {
ResourceService.runInBackground();
if (Setting.value('env') === 'dev') {
void AlarmService.updateAllNotifications();
} else {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
void reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
void AlarmService.updateAllNotifications();
} else {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
void reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
void AlarmService.updateAllNotifications();
void DecryptionWorker.instance().scheduleStart();
});
}
RevisionService.instance().runInBackground();
this.startRotatingLogMaintenance(Setting.value('profileDir'));
});
addTask('app/set up ClipperServer', () => {
const clipperLogger = new Logger();
clipperLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget(TargetType.Console);
ClipperServer.instance().initialize(actionApi);
ClipperServer.instance().setEnabled(!Setting.value('altInstanceId'));
ClipperServer.instance().setLogger(clipperLogger);
ClipperServer.instance().setDispatch(this.store().dispatch);
if (ClipperServer.instance().enabled() && Setting.value('clipperServer.autoStart')) {
void ClipperServer.instance().start();
}
});
addTask('app/set up external edit watchers', () => {
ExternalEditWatcher.instance().setLogger(reg.logger());
ExternalEditWatcher.instance().initialize(bridge, this.store().dispatch);
ResourceEditWatcher.instance().initialize(
reg.logger(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(action: any) => { this.store().dispatch(action); },
(path: string) => bridge().openItem(path),
() => this.store().getState().windowId,
);
// Forwards the local event to the global event manager, so that it can
// be picked up by the plugin manager.
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
ResourceEditWatcher.instance().on('resourceChange', (event: any) => {
eventManager.emit(EventName.ResourceChange, event);
void DecryptionWorker.instance().scheduleStart();
});
}
const clipperLogger = new Logger();
clipperLogger.addTarget(TargetType.File, { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget(TargetType.Console);
ClipperServer.instance().initialize(actionApi);
ClipperServer.instance().setEnabled(!Setting.value('altInstanceId'));
ClipperServer.instance().setLogger(clipperLogger);
ClipperServer.instance().setDispatch(this.store().dispatch);
if (ClipperServer.instance().enabled() && Setting.value('clipperServer.autoStart')) {
void ClipperServer.instance().start();
}
ExternalEditWatcher.instance().setLogger(reg.logger());
ExternalEditWatcher.instance().initialize(bridge, this.store().dispatch);
ResourceEditWatcher.instance().initialize(
reg.logger(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(action: any) => { this.store().dispatch(action); },
(path: string) => bridge().openItem(path),
() => this.store().getState().windowId,
);
// Forwards the local event to the global event manager, so that it can
// be picked up by the plugin manager.
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
ResourceEditWatcher.instance().on('resourceChange', (event: any) => {
eventManager.emit(EventName.ResourceChange, event);
});
RevisionService.instance().runInBackground();
// Make it available to the console window - useful to call revisionService.collectRevisions()
if (Setting.value('env') === 'dev') {
addTask('app/add debug variables', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(window as any).joplin = {
revisionService: RevisionService.instance(),
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
resourceService: ResourceService.instance(),
searchEngine: SearchEngine.instance(),
ocrService: () => this.ocrService_,
};
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
(window as any).joplin = {
revisionService: RevisionService.instance(),
migrationService: MigrationService.instance(),
decryptionWorker: DecryptionWorker.instance(),
commandService: CommandService.instance(),
pluginService: PluginService.instance(),
bridge: bridge(),
debug: new DebugService(reg.db()),
resourceService: ResourceService.instance(),
searchEngine: SearchEngine.instance(),
ocrService: () => this.ocrService_,
};
}
addTask('app/listen for main process events', () => {
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
bridge().setOnAllowedExtensionsChangeListener((newExtensions) => {
Setting.setValue('linking.extraAllowedExtensions', newExtensions);
});
ipcRenderer.on('window-focused', (_event, newWindowId) => {
const currentWindowId = this.store().getState().windowId;
if (newWindowId !== currentWindowId) {
this.dispatch({
type: 'WINDOW_FOCUS',
windowId: newWindowId,
lastWindowId: currentWindowId,
});
}
});
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
bridge().setOnAllowedExtensionsChangeListener((newExtensions) => {
Setting.setValue('linking.extraAllowedExtensions', newExtensions);
});
addTask('app/initPluginService', () => this.initPluginService());
addTask('app/setupContextMenu', () => {
this.setupContextMenu();
ipcRenderer.on('window-focused', (_event, newWindowId) => {
const currentWindowId = this.store().getState().windowId;
if (newWindowId !== currentWindowId) {
this.dispatch({
type: 'WINDOW_FOCUS',
windowId: newWindowId,
lastWindowId: currentWindowId,
});
}
});
addTask('app/set up SpellCheckerService', async () => {
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
await this.initPluginService();
this.setupContextMenu();
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
this.startRotatingLogMaintenance(Setting.value('profileDir'));
await this.setupOcrService();
eventManager.on(EventName.OcrServiceResourcesProcessed, async () => {
await ResourceService.instance().indexNoteResources();
});
addTask('app/listen for resource events', () => {
eventManager.on(EventName.OcrServiceResourcesProcessed, async () => {
await ResourceService.instance().indexNoteResources();
});
eventManager.on(EventName.NoteResourceIndexed, async () => {
SearchEngine.instance().scheduleSyncTables();
});
eventManager.on(EventName.NoteResourceIndexed, async () => {
SearchEngine.instance().scheduleSyncTables();
});
addTask('app/setupOcrService', () => this.setupOcrService());
return tasks;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async start(argv: string[], startOptions: StartOptions = null): Promise<any> {
const startupTask = perfLogger.taskStart('app/start');
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
// insert an extra argument so that they can be processed in a consistent way everywhere.
if (!bridge().electronIsDev()) argv.splice(1, 0, '.');
argv = await super.start(argv, startOptions);
await this.setupIntegrationTestUtils();
bridge().setLogFilePath(Logger.globalLogger.logFilePath());
await this.applySettingsSideEffects();
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
reg.logger().info('app.start: doing upgradeSyncTarget action');
bridge().mainWindow().show();
startupTask.onEnd();
return { action: 'upgradeSyncTarget' };
}
const startupTasks = this.buildStartupTasks_();
for (const task of startupTasks) {
await perfLogger.track(task.label, async () => task.task());
}
// Used by tests
ipcRenderer.send('startup-finished');
// setTimeout(() => {
// void populateDatabase(reg.db(), {
@@ -813,10 +749,6 @@ class Application extends BaseApplication {
// await runIntegrationTests();
// Used by tests
ipcRenderer.send('startup-finished');
startupTask.onEnd();
return null;
}

View File

@@ -8,8 +8,8 @@ import { urlDecode } from '@joplin/lib/string-utils';
import * as Sentry from '@sentry/electron/main';
import { homedir } from 'os';
import { msleep } from '@joplin/utils/time';
import { pathExists, pathExistsSync, writeFileSync, ensureDirSync } from 'fs-extra';
import { extname, normalize, join } from 'path';
import { pathExists, pathExistsSync, writeFileSync } from 'fs-extra';
import { extname, normalize } from 'path';
import isSafeToOpen from './utils/isSafeToOpen';
import { closeSync, openSync, readSync, statSync } from 'fs';
import { KB } from '@joplin/utils/bytes';
@@ -67,30 +67,6 @@ export class Bridge {
this.logFilePath_ = v;
}
private getCrashDumpDirectory(): string {
try {
const platformName = shim.platformName();
switch (platformName) {
case 'win32':
// Windows: Use %LOCALAPPDATA%\CrashDumps
return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'CrashDumps');
case 'darwin':
// macOS: Use ~/Library/Logs/DiagnosticReports
return join(homedir(), 'Library', 'Logs', 'DiagnosticReports');
case 'linux':
// Linux: Use XDG_STATE_HOME (for logs) or fallback to ~/.local/state
return join(process.env.XDG_STATE_HOME || join(homedir(), '.local', 'state'), 'joplin');
default:
// For unknown platforms, default to the home directory
return homedir();
}
} catch (error) {
// If we can't get the platform name, fallback to the home directory
return homedir();
}
}
private sentryInit() {
const getLogLines = () => {
try {
@@ -133,10 +109,7 @@ export class Bridge {
log: logAttachment ? logAttachment.data.trim().split('\n') : [],
};
const crashDumpDir = this.getCrashDumpDirectory();
ensureDirSync(crashDumpDir);
const crashDumpPath = join(crashDumpDir, `joplin_crash_dump_${date}.json`);
writeFileSync(crashDumpPath, JSON.stringify(errorEventWithLog, null, '\t'), 'utf-8');
writeFileSync(`${homedir()}/joplin_crash_dump_${date}.json`, JSON.stringify(errorEventWithLog, null, '\t'), 'utf-8');
} catch (error) {
// Ignore the error since we can't handle it here
}

View File

@@ -1,96 +0,0 @@
import * as convertHtmlToMarkdown from './convertNoteToMarkdown';
import { AppState, createAppDefaultState } from '../app.reducer';
import Note from '@joplin/lib/models/Note';
import { MarkupLanguage } from '@joplin/renderer';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import Folder from '@joplin/lib/models/Folder';
import { NoteEntity } from '@joplin/lib/services/database/types';
describe('convertNoteToMarkdown', () => {
let state: AppState = undefined;
beforeEach(async () => {
state = createAppDefaultState({});
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
});
it('should set the original note to be trashed', async () => {
const folder = await Folder.save({ title: 'test_folder' });
const htmlNote = await Note.save({ title: 'test', body: '<p>Hello</p>', parent_id: folder.id, markup_language: MarkupLanguage.Html });
state.selectedNoteIds = [htmlNote.id];
await convertHtmlToMarkdown.runtime().execute({ state, dispatch: () => {} });
const refreshedNote = await Note.load(htmlNote.id);
expect(htmlNote.deleted_time).toBe(0);
expect(refreshedNote.deleted_time).not.toBe(0);
});
it('should recreate a new note that is a clone of the original', async () => {
let noteConvertedToMarkdownId = '';
const dispatchFn = jest.fn()
.mockImplementationOnce(() => {})
.mockImplementationOnce(action => {
noteConvertedToMarkdownId = action.id;
});
const folder = await Folder.save({ title: 'test_folder' });
const htmlNoteProperties = {
title: 'test',
body: '<p>Hello</p>',
parent_id: folder.id,
markup_language: MarkupLanguage.Html,
author: 'test-author',
is_todo: 1,
todo_completed: 1,
};
const htmlNote = await Note.save(htmlNoteProperties);
state.selectedNoteIds = [htmlNote.id];
await convertHtmlToMarkdown.runtime().execute({ state, dispatch: dispatchFn });
expect(dispatchFn).toHaveBeenCalledTimes(2);
expect(noteConvertedToMarkdownId).not.toBe('');
const markdownNote = await Note.load(noteConvertedToMarkdownId);
const fields: (keyof NoteEntity)[] = ['parent_id', 'title', 'author', 'is_todo', 'todo_completed'];
for (const field of fields) {
expect(htmlNote[field]).toEqual(markdownNote[field]);
}
});
it('should generate action to trigger notification', async () => {
let originalHtmlNoteId = '';
let actionType = '';
const dispatchFn = jest.fn()
.mockImplementationOnce(action => {
originalHtmlNoteId = action.value;
actionType = action.type;
})
.mockImplementationOnce(() => {});
const folder = await Folder.save({ title: 'test_folder' });
const htmlNoteProperties = {
title: 'test',
body: '<p>Hello</p>',
parent_id: folder.id,
markup_language: MarkupLanguage.Html,
author: 'test-author',
is_todo: 1,
todo_completed: 1,
};
const htmlNote = await Note.save(htmlNoteProperties);
state.selectedNoteIds = [htmlNote.id];
await convertHtmlToMarkdown.runtime().execute({ state, dispatch: dispatchFn });
expect(dispatchFn).toHaveBeenCalledTimes(2);
expect(originalHtmlNoteId).toBe(htmlNote.id);
expect(actionType).toBe('NOTE_HTML_TO_MARKDOWN_DONE');
});
});

View File

@@ -1,52 +0,0 @@
import { _ } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
import { stateUtils } from '@joplin/lib/reducer';
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { MarkupLanguage } from '@joplin/renderer';
import { runtime as convertHtmlToMarkdown } from '@joplin/lib/commands/convertHtmlToMarkdown';
import bridge from '../services/bridge';
export const declaration: CommandDeclaration = {
name: 'convertNoteToMarkdown',
label: () => _('Convert note to Markdown'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, noteId: string = null) => {
noteId = noteId || stateUtils.selectedNoteId(context.state);
const note = await Note.load(noteId);
if (!note) return;
try {
const markdownBody = await convertHtmlToMarkdown().execute(context, note.body);
const newNote = await Note.duplicate(note.id);
newNote.body = markdownBody;
newNote.markup_language = MarkupLanguage.Markdown;
await Note.save(newNote);
await Note.delete(note.id, { toTrash: true });
context.dispatch({
type: 'NOTE_HTML_TO_MARKDOWN_DONE',
value: note.id,
});
context.dispatch({
type: 'NOTE_SELECT',
id: newNote.id,
});
} catch (error) {
bridge().showErrorMessageBox(_('Could not convert note to Markdown: %s', error.message));
}
},
enabledCondition: 'oneNoteSelected && noteIsHtml && !noteIsReadOnly',
};
};

View File

@@ -1,5 +1,4 @@
// AUTO-GENERATED using `gulp buildScriptIndexes`
import * as convertNoteToMarkdown from './convertNoteToMarkdown';
import * as copyDevCommand from './copyDevCommand';
import * as copyToClipboard from './copyToClipboard';
import * as editProfileConfig from './editProfileConfig';
@@ -25,7 +24,6 @@ import * as toggleSafeMode from './toggleSafeMode';
import * as toggleTabMovesFocus from './toggleTabMovesFocus';
const index: any[] = [
convertNoteToMarkdown,
copyDevCommand,
copyToClipboard,
editProfileConfig,

View File

@@ -1,28 +0,0 @@
import * as React from 'react';
import { useContext, useEffect } from 'react';
import { _ } from '@joplin/lib/locale';
import { Dispatch } from 'redux';
import { PopupNotificationContext } from '../PopupNotification/PopupNotificationProvider';
import { NotificationType } from '../PopupNotification/types';
interface Props {
noteId: string;
dispatch: Dispatch;
}
export default (props: Props) => {
const popupManager = useContext(PopupNotificationContext);
useEffect(() => {
if (!props.noteId || props.noteId === '') return;
props.dispatch({ type: 'NOTE_HTML_TO_MARKDOWN_DONE', value: '' });
const notification = popupManager.createPopup(() => (
<div>{_('The note has been converted to Markdown and the original note has been moved to the trash')}</div>
), { type: NotificationType.Success });
notification.scheduleDismiss();
}, [props.dispatch, popupManager, props.noteId]);
return <div style={{ display: 'none' }}/>;
};

View File

@@ -1,4 +1,4 @@
import { useMemo, useRef, useState, useCallback } from 'react';
import { useMemo, useRef, useState } from 'react';
interface Props {
width: number;
@@ -9,62 +9,40 @@ interface Props {
const fontSizeCache_: Record<string, number> = {};
export default (props: Props) => {
const containerRef = useRef<HTMLDivElement>(null);
const containerRef = useRef(null);
const [containerReady, setContainerReady] = useState(false);
const refCallback = useCallback((el: HTMLDivElement | null) => {
if (el && !containerRef.current) {
containerRef.current = el;
requestAnimationFrame(() => {
setContainerReady(true);
});
}
}, []);
const fontSize = useMemo(() => {
if (!containerReady || !containerRef.current) {
return Math.min(props.height * 0.7, 14);
}
if (!containerReady) return props.height;
const cacheKey = [props.width, props.height, props.emoji].join('-');
if (fontSizeCache_[cacheKey]) {
return fontSizeCache_[cacheKey];
}
// Set the emoji font size so that it fits within the specified width
// and height. In fact, currently it only looks at the height.
let spanFontSize = props.height;
const span = document.createElement('span');
span.innerText = props.emoji;
span.style.fontSize = `${spanFontSize}px`;
span.style.visibility = 'hidden';
span.style.position = 'absolute';
span.style.whiteSpace = 'nowrap';
containerRef.current.appendChild(span);
let rect = span.getBoundingClientRect();
while ((rect.height > props.height || rect.width > props.width) && spanFontSize > 1) {
spanFontSize -= 0.5;
while (rect.height > props.height) {
spanFontSize -= .5;
span.style.fontSize = `${spanFontSize}px`;
rect = span.getBoundingClientRect();
}
span.remove();
fontSizeCache_[cacheKey] = spanFontSize;
return spanFontSize;
}, [props.width, props.height, props.emoji, containerReady]);
}, [props.width, props.height, props.emoji, containerReady, containerRef]);
return <div
ref={refCallback}
style={{
width: props.width,
height: props.height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
fontSize,
}}
>
{props.emoji}
</div>;
return <div className="emoji-box" ref={el => { containerRef.current = el; setContainerReady(true); }} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: props.width, height: props.height, fontSize }}>{props.emoji}</div>;
};

View File

@@ -38,14 +38,12 @@ import restart from '../services/restart';
import { connect } from 'react-redux';
import { NoteListColumns } from '@joplin/lib/services/plugins/api/noteListType';
import validateColumns from './NoteListHeader/utils/validateColumns';
import ConversionNotification from './ConversionNotification/ConversionNotification';
import TrashNotification from './TrashNotification/TrashNotification';
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';
import { Dispatch } from 'redux';
const ipcRenderer = require('electron').ipcRenderer;
@@ -86,7 +84,6 @@ interface Props {
showInvalidJoplinCloudCredential: boolean;
toast: Toast;
shouldSwitchToAppleSiliconVersion: boolean;
noteHtmlToMarkdownDone: string;
}
interface ShareFolderDialogOptions {
@@ -800,10 +797,6 @@ class MainScreenComponent extends React.Component<Props, State> {
return (
<div style={style}>
<ConversionNotification
noteId={this.props.noteHtmlToMarkdownDone}
dispatch={this.props.dispatch as Dispatch}
/>
<TrashNotification
lastDeletion={this.props.lastDeletion}
lastDeletionNotificationTime={this.props.lastDeletionNotificationTime}
@@ -860,7 +853,6 @@ const mapStateToProps = (state: AppState) => {
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && state.mustAuthenticate,
toast: state.toast,
shouldSwitchToAppleSiliconVersion: shim.isAppleSilicon() && process.arch !== 'arm64',
noteHtmlToMarkdownDone: state.noteHtmlToMarkdownDone,
};
};

View File

@@ -803,7 +803,6 @@ function useMenu(props: Props) {
menuItemDic.toggleNoteList,
menuItemDic.toggleVisiblePanes,
menuItemDic.toggleEditorPlugin,
menuItemDic.toggleEditors,
{
label: _('Layout button sequence'),
submenu: layoutButtonSequenceMenuItems,
@@ -907,7 +906,6 @@ function useMenu(props: Props) {
separator(),
menuItemDic.setTags,
menuItemDic.showShareNoteDialog,
menuItemDic.convertNoteToMarkdown,
separator(),
menuItemDic.showNoteProperties,
menuItemDic.showNoteContentProperties,

View File

@@ -67,11 +67,6 @@ import 'codemirror/mode/diff/diff';
import 'codemirror/mode/erlang/erlang';
import 'codemirror/mode/sql/sql';
interface ExtendedWindow {
CodeMirror?: unknown;
}
declare const window: ExtendedWindow;
export interface EditorProps {
value: string;
@@ -105,14 +100,6 @@ function Editor(props: EditorProps, ref: any) {
const editorParent = useRef(null);
const lastEditTime = useRef(NaN);
useEffect(() => {
window.CodeMirror = CodeMirror;
return () => {
window.CodeMirror = undefined;
};
}, []);
// Codemirror plugins add new commands to codemirror (or change it's behavior)
// This command adds the smartListIndent function which will be bound to tab
useListIdent(CodeMirror);

View File

@@ -1,6 +1,5 @@
import { useEffect, useState } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import bridge from '../../../../../../services/bridge';
import { contentScriptsToCodeMirrorPlugin } from '@joplin/lib/services/plugins/utils/loadContentScripts';
import { extname } from 'path';
import shim from '@joplin/lib/shim';
@@ -8,18 +7,6 @@ import uuid from '@joplin/lib/uuid';
import { reg } from '@joplin/lib/registry';
const addPluginDependency = (path: string) => {
const id = `content-script-${encodeURIComponent(path)}`;
if (document.getElementById(id)) {
return;
}
const element = document.createElement('script');
element.setAttribute('id', id);
element.setAttribute('src', path);
document.head.appendChild(element);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
export default function useExternalPlugins(CodeMirror: any, plugins: PluginStates) {
const [options, setOptions] = useState({});
@@ -36,14 +23,7 @@ export default function useExternalPlugins(CodeMirror: any, plugins: PluginState
if (mod.codeMirrorResources) {
for (const asset of mod.codeMirrorResources) {
try {
let assetPath = shim.fsDriver().resolveRelativePathWithinDir(`${bridge().vendorDir()}/lib/codemirror/`, asset);
// Compatibility with old versions of Joplin, where the file extension was automatically added by require().
if (extname(assetPath) === '') {
assetPath += '.js';
}
addPluginDependency(assetPath);
require(`codemirror/${asset}`);
} catch (error) {
error.message = `${asset} is not a valid CodeMirror asset, keymap or mode. You can find a list of valid assets here: https://codemirror.net/doc/manual.html#addons`;
throw error;

View File

@@ -340,8 +340,6 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
props.setShowLocalSearch(event.searchState.dialogVisible);
}
lastSearchState.current = event.searchState;
} else if (event.kind === EditorEventType.FollowLink) {
void CommandService.instance().execute('openItem', event.link);
}
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch]);
@@ -364,9 +362,6 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
readOnly: props.disabled,
markdownMarkEnabled: Setting.value('markdown.plugin.mark'),
katexEnabled: Setting.value('markdown.plugin.katex'),
inlineRenderingEnabled: Setting.value('editor.inlineRendering'),
imageRenderingEnabled: Setting.value('editor.imageRendering'),
highlightActiveLine: Setting.value('editor.highlightActiveLine'),
themeData: {
...styles.globalTheme,
marginLeft: 0,
@@ -415,7 +410,6 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
onSelectPastBeginning={onSelectPastBeginning}
externalSearch={props.searchMarkers}
useLocalSearch={props.useLocalSearch}
onLocalize={_}
/>
</div>
);

View File

@@ -15,10 +15,6 @@ import useEditorSearch from '../utils/useEditorSearchExtension';
import CommandService from '@joplin/lib/services/CommandService';
import { SearchMarkers } from '../../../utils/useSearchMarkers';
import localisation from './utils/localisation';
import Resource from '@joplin/lib/models/Resource';
import { parseResourceUrl } from '@joplin/lib/urlUtils';
import { resourceFilename } from '@joplin/lib/models/utils/resourceUtils';
import getResourceBaseUrl from '../../../utils/getResourceBaseUrl';
interface Props extends EditorProps {
style: React.CSSProperties;
@@ -108,16 +104,7 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
onLogMessage: message => onLogMessageRef.current(message),
};
const editor = createEditor(editorContainerRef.current, {
...editorProps,
resolveImageSrc: async src => {
const url = parseResourceUrl(src);
if (!url.itemId) return null;
const item = await Resource.load(url.itemId);
if (!item) return null;
return `${getResourceBaseUrl()}/${resourceFilename(item)}`;
},
});
const editor = createEditor(editorContainerRef.current, editorProps);
editor.addStyles({
'.cm-scroller': { overflow: 'auto' },
'&.CodeMirror': {

View File

@@ -18,12 +18,11 @@ const logger = Logger.create('shouldPasteResources');
// instead the clipboard resources, which will contain the actual image.
//
// We have a lot of log statements so that if someone reports a bug we can ask
// them to check the console and give us the messages they have. However, to avoid
// including sensitive information in the logs, users will need to check the console,
// not the log file.
// them to check the console and give us the messages they have.
export default (pastedText: string, pastedHtml: string, resourceMds: string[]) => {
const debugInformation = JSON.stringify({ pastedText, pastedHtml, resourceMds }, undefined, '\t');
logger.debug('Input data:', debugInformation);
logger.info('Pasted text:', pastedText);
logger.info('Pasted HTML:', pastedHtml);
logger.info('Resources:', resourceMds);
if (pastedText) {
logger.info('Not pasting resources only because the clipboard contains plain text');

View File

@@ -5,7 +5,6 @@ import shim from '@joplin/lib/shim';
const useLinkTooltips = (editor: Editor|null) => {
const resetModifiedTitles = useCallback(() => {
if (!editor) return;
for (const element of editor.getDoc().querySelectorAll('a[data-joplin-original-title]')) {
element.setAttribute('title', element.getAttribute('data-joplin-original-title') ?? '');
element.removeAttribute('data-joplin-original-title');

View File

@@ -56,7 +56,6 @@ import useResourceUnwatcher from './utils/useResourceUnwatcher';
import StatusBar from './StatusBar';
import useVisiblePluginEditorViewIds from '@joplin/lib/hooks/plugins/useVisiblePluginEditorViewIds';
import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
import getResourceBaseUrl from './utils/getResourceBaseUrl';
const debounce = require('debounce');
@@ -170,7 +169,7 @@ function NoteEditorContent(props: NoteEditorProps) {
const theme = themeStyle(options.themeId ? options.themeId : props.themeId);
const markupToHtml = markupLanguageUtils.newMarkupToHtml(props.plugins, {
resourceBaseUrl: getResourceBaseUrl(),
resourceBaseUrl: `joplin-content://note-viewer/${Setting.value('resourceDir')}/`,
customCss: props.customCss,
});
@@ -467,7 +466,6 @@ function NoteEditorContent(props: NoteEditorProps) {
// It is currently used to remember pdf scroll position for each attachments of each note uniquely.
noteId: props.noteId,
watchedNoteFiles: props.watchedNoteFiles,
enableHtmlToMarkdownBanner: props.enableHtmlToMarkdownBanner,
};
let editor = null;
@@ -490,17 +488,6 @@ function NoteEditorContent(props: NoteEditorProps) {
setShowRevisions(false);
}, []);
const onBannerConvertItToMarkdown = useCallback(async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
if (!props.selectedNoteIds || props.selectedNoteIds.length === 0) return;
await CommandService.instance().execute('convertNoteToMarkdown', props.selectedNoteIds[0]);
}, [props.selectedNoteIds]);
const onHideBannerConvertItToMarkdown = async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
Setting.setValue('editor.enableHtmlToMarkdownBanner', false);
};
const onBannerResourceClick = useCallback(async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
const resourceId = event.currentTarget.getAttribute('data-resource-id');
@@ -645,30 +632,9 @@ function NoteEditorContent(props: NoteEditorProps) {
const theme = themeStyle(props.themeId);
function renderConvertHtmlToMarkdown(): React.ReactNode {
if (!props.enableHtmlToMarkdownBanner) return null;
const note = props.notes.find(n => n.id === props.selectedNoteIds[0]);
if (!note) return null;
if (note.markup_language !== MarkupLanguage.Html) return null;
return (
<div style={styles.resourceWatchBanner}>
<p style={styles.resourceWatchBannerLine}>
{_('This note is in HTML format. Convert it to Markdown to edit it more easily.')}
&nbsp;
<a href="#" style={styles.resourceWatchBannerAction} onClick={onBannerConvertItToMarkdown}>{`${_('Convert it')}`}</a>
{' / '}
<a href="#" style={styles.resourceWatchBannerAction} onClick={onHideBannerConvertItToMarkdown}>{_('Don\'t show this message again')}</a>
</p>
</div>
);
}
return (
<div style={styles.root} onDragOver={onDragOver} onDrop={onDrop} ref={containerRef}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{renderConvertHtmlToMarkdown()}
{renderResourceWatchingNotification()}
{renderResourceInSearchResultsNotification()}
<NoteTitleBar
@@ -756,7 +722,6 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
syncUserId: state.settings['sync.userId'],
shareCacheSetting: state.settings['sync.shareCache'],
searchResults: state.searchResults,
enableHtmlToMarkdownBanner: state.settings['editor.enableHtmlToMarkdownBanner'],
};
};

View File

@@ -4,8 +4,7 @@ import { AppState } from '../../../app.reducer';
import Setting from '@joplin/lib/models/Setting';
import BannerContent from './BannerContent';
import { _ } from '@joplin/lib/locale';
import onRichTextReadMoreLinkClick from '@joplin/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick';
import onRichTextDismissLinkClick from '@joplin/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick';
import bridge from '../../../services/bridge';
import { useMemo } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import PluginService from '@joplin/lib/services/plugins/PluginService';
@@ -17,6 +16,14 @@ interface Props {
plugins: PluginStates;
}
const onRichTextDismissLinkClick = () => {
Setting.setValue('richTextBannerDismissed', true);
};
const onRichTextReadMoreLinkClick = () => {
void bridge().openExternal('https://joplinapp.org/help/apps/rich_text_editor');
};
const onSwitchToLegacyEditor = () => {
Setting.setValue('editor.legacyMarkdown', true);
};

View File

@@ -69,10 +69,6 @@ export default function styles(props: NoteEditorProps) {
marginTop: 0,
marginBottom: 10,
},
resourceWatchBannerAction: {
textDecoration: 'underline',
color: theme.colorWarnUrl,
},
};
});
}

View File

@@ -8,15 +8,14 @@ const MenuItem = bridge().MenuItem;
import Resource, { resourceOcrStatusToString } from '@joplin/lib/models/Resource';
import BaseItem from '@joplin/lib/models/BaseItem';
import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
import { NoteEntity, ResourceEntity, ResourceOcrDriverId, ResourceOcrStatus } from '@joplin/lib/services/database/types';
import { NoteEntity, ResourceEntity, ResourceOcrStatus } from '@joplin/lib/services/database/types';
import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types';
import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly';
import Setting from '@joplin/lib/models/Setting';
import ItemChange from '@joplin/lib/models/ItemChange';
import shim, { MessageBoxType } from '@joplin/lib/shim';
import shim from '@joplin/lib/shim';
import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils';
import CommandService from '@joplin/lib/services/CommandService';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
@@ -138,40 +137,6 @@ export function menuItems(dispatch: Function): ContextMenuItems {
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
},
recognizeHandwrittenImage: {
label: _('Recognize handwritten image'),
onAction: async (options: ContextMenuOptions) => {
const syncTargetId = Setting.value('sync.target');
if (!SyncTargetRegistry.isJoplinServerOrCloud(syncTargetId)) {
await shim.showMessageBox(_('This feature is only available on Joplin Cloud and Joplin Server.'), { type: MessageBoxType.Error });
return;
}
if (!Setting.value('ocr.handwrittenTextDriverEnabled')) {
await shim.showMessageBox(_('This feature is disabled by default, you need to manually enable it by turning on the option to \'Enable handwritten transcription\'.'), { type: MessageBoxType.Error });
return;
}
const { resource } = await resourceInfo(options);
if (!['image/png', 'image/jpg', 'image/jpeg', 'image/bmp'].includes(resource.mime)) {
await shim.showMessageBox(_('This image type is not supported by the recognition system.'), { type: MessageBoxType.Error });
return;
}
await Resource.save({
id: resource.id,
ocr_status: ResourceOcrStatus.Todo,
ocr_driver_id: ResourceOcrDriverId.HandwrittenText,
ocr_details: '',
ocr_error: '',
ocr_text: '',
});
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => {
return itemType === ContextMenuItemType.Resource || (itemType === ContextMenuItemType.Image && options.resourceId);
},
},
revealInFolder: {
label: _('Reveal file in folder'),
onAction: async (options: ContextMenuOptions) => {

View File

@@ -1,4 +0,0 @@
import Setting from '@joplin/lib/models/Setting';
const getResourceBaseUrl = () => `joplin-content://note-viewer/${Setting.value('resourceDir')}/`;
export default getResourceBaseUrl;

View File

@@ -67,7 +67,6 @@ export interface NoteEditorProps {
onTitleChange?: (title: string)=> void;
bodyEditor: string;
startupPluginsLoaded: boolean;
enableHtmlToMarkdownBanner: boolean;
}
export interface NoteBodyEditorRef {
@@ -139,7 +138,6 @@ export interface NoteBodyEditorProps {
noteId: string;
useCustomPdfViewer: boolean;
watchedNoteFiles: string[];
enableHtmlToMarkdownBanner: boolean;
}
export interface NoteBodyEditorPropsAndRef extends NoteBodyEditorProps {

View File

@@ -49,7 +49,7 @@ const useScheduleSaveCallbacks = (props: Props) => {
}, [props.dispatch, props.editorId, props.setFormNote]);
const saveNoteIfWillChange = useCallback(async (formNote: FormNote) => {
if (!formNote.id || !formNote.bodyWillChangeId || !props.editorRef.current) return;
if (!formNote.id || !formNote.bodyWillChangeId) return;
const body = await props.editorRef.current.content();

View File

@@ -343,7 +343,6 @@ class NotePropertiesDialog extends React.Component<Props, State> {
style={styles.input}
id={uniqueId(key)}
name={uniqueId(key)}
autoFocus
/>;
editCompHandler = () => {
@@ -364,7 +363,6 @@ class NotePropertiesDialog extends React.Component<Props, State> {
id={uniqueId(key)}
name={uniqueId(key)}
aria-invalid={!this.state.isValid.location}
autoFocus
/>
{
this.state.isValid.location ? null
@@ -389,7 +387,6 @@ class NotePropertiesDialog extends React.Component<Props, State> {
style={styles.input}
id={uniqueId(key)}
name={uniqueId(key)}
autoFocus
/>
);
}
@@ -414,14 +411,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
const ll = this.latLongFromLocation(value);
url = Note.geoLocationUrlFromLatLong(ll.latitude, ll.longitude);
}
const urlStyle: React.CSSProperties = {
...theme.urlStyle,
maxWidth: '180px',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
display: 'inline-block',
};
const urlStyle: React.CSSProperties = { ...theme.urlStyle, maxWidth: '180px', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' };
controlComp = (
<a href="#" onClick={() => bridge().openExternal(url)} style={urlStyle}>
{displayedValue}

View File

@@ -8,7 +8,6 @@ import { focus } from '@joplin/lib/utils/focusHandler';
import Dialog from './Dialog';
import { ChangeEvent } from 'react';
import { formatDateTimeLocalToMs, isValidDate } from '@joplin/utils/time';
import lightTheme from '@joplin/lib/themes/light';
interface Props {
themeId: number;
@@ -118,15 +117,6 @@ export default class PromptDialog extends React.Component<Props, any> {
borderColor: theme.dividerColor,
};
// The button to change the date/time cannot be customized easily so we need to use the
// light theme for that particular component.
this.styles_.dateTimeInput = {
...this.styles_.input,
color: lightTheme.color,
backgroundColor: lightTheme.backgroundColor,
borderColor: lightTheme.dividerColor,
};
this.styles_.select = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
control: (provided: any) => {
@@ -266,7 +256,7 @@ export default class PromptDialog extends React.Component<Props, any> {
onChange={onChange}
type="datetime-local"
className='datetime-picker'
style={styles.dateTimeInput}
style={styles.input}
/>;
} else if (this.props.inputType === 'tags') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@@ -27,7 +27,7 @@ const CollapseExpandAllButton = (props: CollapseExpandAllButtonProps) => {
const icon = props.allFoldersCollapsed ? 'far fa-caret-square-right' : 'far fa-caret-square-down';
const label = props.allFoldersCollapsed ? _('Expand all notebooks') : _('Collapse all notebooks');
return <button onClick={() => onToggleAllFolders(props.allFoldersCollapsed)} className='sidebar-header-button -collapseall' title={label}>
return <button onClick={() => onToggleAllFolders(props.allFoldersCollapsed)} className='sidebar-header-button -collapseall'>
<i
aria-label={label}
role='img'
@@ -39,11 +39,9 @@ const CollapseExpandAllButton = (props: CollapseExpandAllButtonProps) => {
const NewFolderButton = () => {
// To allow it to be accessed by accessibility tools, the new folder button
// is not included in the portion of the list with role='tree'.
const label = _('New notebook');
return <button onClick={onAddFolderButtonClick} className='sidebar-header-button -newfolder' title={label}>
return <button onClick={onAddFolderButtonClick} className='sidebar-header-button -newfolder'>
<i
aria-label={label}
aria-label={_('New notebook')}
role='img'
className='fas fa-plus'
/>

View File

@@ -7,6 +7,7 @@ import * as editAlarm from './editAlarm';
import * as exportPdf from './exportPdf';
import * as gotoAnything from './gotoAnything';
import * as hideModalMessage from './hideModalMessage';
import * as leaveSharedFolder from './leaveSharedFolder';
import * as linkToNote from './linkToNote';
import * as moveToFolder from './moveToFolder';
import * as newFolder from './newFolder';
@@ -55,6 +56,7 @@ const index: any[] = [
exportPdf,
gotoAnything,
hideModalMessage,
leaveSharedFolder,
linkToNote,
moveToFolder,
newFolder,

View File

@@ -1,8 +1,8 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService';
import { _ } from '../locale';
import ShareService from '../services/share/ShareService';
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import ShareService from '@joplin/lib/services/share/ShareService';
import Logger from '@joplin/utils/Logger';
import shim from '../shim';
import shim from '@joplin/lib/shim';
const logger = Logger.create('leaveSharedFolder');
@@ -11,16 +11,10 @@ export const declaration: CommandDeclaration = {
label: () => _('Leave notebook...'),
};
interface Options {
force?: boolean;
}
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, folderId: string = null, { force = false }: Options = {}) => {
const answer = force ? true : await shim.showConfirmationDialog(
_('This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?'),
);
execute: async (_context: CommandContext, folderId: string = null) => {
const answer = await shim.showConfirmationDialog(_('This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?'));
if (!answer) return;
try {

View File

@@ -79,7 +79,5 @@ export default function() {
'switchProfile3',
'pasteAsText',
'showNoteProperties',
'convertNoteToMarkdown',
'toggleEditors',
];
}

View File

@@ -2,36 +2,34 @@
// Disable React message in console "Download the React DevTools for a better development experience"
// https://stackoverflow.com/questions/42196819/disable-hide-download-the-react-devtools#42196820
// eslint-disable-next-line no-undef, @typescript-eslint/no-explicit-any
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
// eslint-disable-next-line no-undef
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
supportsFiber: true,
inject: function() {},
onCommitFiberRoot: function() {},
onCommitFiberUnmount: function() {},
};
import './utils/sourceMapSetup';
import app from './app';
import Folder from '@joplin/lib/models/Folder';
import Resource from '@joplin/lib/models/Resource';
import BaseItem from '@joplin/lib/models/BaseItem';
import Note from '@joplin/lib/models/Note';
import Tag from '@joplin/lib/models/Tag';
import NoteTag from '@joplin/lib/models/NoteTag';
import MasterKey from '@joplin/lib/models/MasterKey';
import Setting, { AppType } from '@joplin/lib/models/Setting';
import Revision from '@joplin/lib/models/Revision';
import Logger from '@joplin/utils/Logger';
import FsDriverNode from '@joplin/lib/fs-driver-node';
import bridge from './services/bridge';
import shim from '@joplin/lib/shim';
const app = require('./app').default;
const Folder = require('@joplin/lib/models/Folder').default;
const Resource = require('@joplin/lib/models/Resource').default;
const BaseItem = require('@joplin/lib/models/BaseItem').default;
const Note = require('@joplin/lib/models/Note').default;
const Tag = require('@joplin/lib/models/Tag').default;
const NoteTag = require('@joplin/lib/models/NoteTag').default;
const MasterKey = require('@joplin/lib/models/MasterKey').default;
const Setting = require('@joplin/lib/models/Setting').default;
const Revision = require('@joplin/lib/models/Revision').default;
const Logger = require('@joplin/utils/Logger').default;
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
const bridge = require('./services/bridge').default;
const shim = require('@joplin/lib/shim').default;
const { shimInit } = require('@joplin/lib/shim-init-node.js');
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import FileApiDriverLocal from '@joplin/lib/file-api-driver-local';
import * as React from 'react';
import nodeSqlite = require('sqlite3');
import initLib from '@joplin/lib/initLib';
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
const EncryptionService = require('@joplin/lib/services/e2ee/EncryptionService').default;
const FileApiDriverLocal = require('@joplin/lib/file-api-driver-local').default;
const React = require('react');
const nodeSqlite = require('sqlite3');
const initLib = require('@joplin/lib/initLib').default;
const pdfJs = require('pdfjs-dist');
const { isAppleSilicon } = require('is-apple-silicon');
require('@sentry/electron/renderer');
@@ -39,10 +37,34 @@ require('@sentry/electron/renderer');
// Allows components to use React as a global
window.React = React;
const perfLogger = PerformanceLogger.create();
const main = async () => {
if (bridge().env() === 'dev') {
const newConsole = function(oldConsole) {
const output = {};
const fnNames = ['assert', 'clear', 'context', 'count', 'countReset', 'debug', 'dir', 'dirxml', 'error', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'memory', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 'timeLog', 'timeStamp', 'trace', 'warn'];
for (const fnName of fnNames) {
if (fnName === 'warn') {
output.warn = function(...text) {
const s = [...text].join('');
// React spams the console with walls of warnings even outside of strict mode, and even after having renamed
// unsafe methods to UNSAFE_xxxx, so we need to hack the console to remove them...
if (s.indexOf('Warning: componentWillReceiveProps has been renamed, and is not recommended for use') === 0) return;
if (s.indexOf('Warning: componentWillUpdate has been renamed, and is not recommended for use.') === 0) return;
oldConsole.warn(...text);
};
} else {
output[fnName] = function(...text) {
return oldConsole[fnName](...text);
};
}
}
return output;
}(window.console);
window.console = newConsole;
}
// eslint-disable-next-line no-console
console.info(`Environment: ${bridge().env()}`);
@@ -63,7 +85,7 @@ const main = async () => {
BaseItem.loadClass('Revision', Revision);
Setting.setConstant('appId', bridge().appId());
Setting.setConstant('appType', AppType.Desktop);
Setting.setConstant('appType', 'desktop');
Setting.setConstant('pluginAssetDir', `${__dirname}/pluginAssets`);
// eslint-disable-next-line no-console
@@ -109,7 +131,7 @@ const main = async () => {
}
};
perfLogger.track('main', main).catch((error) => {
main().catch((error) => {
const env = bridge().env();
console.error(error);
@@ -130,6 +152,6 @@ perfLogger.track('main', main).catch((error) => {
// In dev, we give the option to leave the app open as debug statements in the
// console can be useful
const canIgnore = env === 'dev';
void bridge().electronApp().handleAppFailure(errorMessage, canIgnore);
bridge().electronApp().handleAppFailure(errorMessage, canIgnore);
});

View File

@@ -1,18 +1,17 @@
// This is the basic initialization for the Electron MAIN process
import './utils/sourceMapSetup';
import { app as electronApp } from 'electron';
const electronApp = require('electron').app;
require('@electron/remote/main').initialize();
import ElectronAppWrapper from './ElectronAppWrapper';
import { pathExistsSync, readFileSync, mkdirpSync } from 'fs-extra';
import { initBridge } from './bridge';
import Logger from '@joplin/utils/Logger';
import FsDriverNode from '@joplin/lib/fs-driver-node';
const ElectronAppWrapper = require('./ElectronAppWrapper').default;
const { pathExistsSync, readFileSync, mkdirpSync } = require('fs-extra');
const { initBridge } = require('./bridge');
const Logger = require('@joplin/utils/Logger').default;
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
const envFromArgs = require('@joplin/lib/envFromArgs');
const packageInfo = require('./packageInfo.js');
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import determineBaseAppDirs from '@joplin/lib/determineBaseAppDirs';
import registerCustomProtocols from './utils/customProtocols/registerCustomProtocols';
const { isCallbackUrl } = require('@joplin/lib/callbackUrlUtils');
const determineBaseAppDirs = require('@joplin/lib/determineBaseAppDirs').default;
const registerCustomProtocols = require('./utils/customProtocols/registerCustomProtocols').default;
// Electron takes the application name from package.json `name` and
// displays this in the tray icon toolip and message box titles, however in
@@ -26,7 +25,7 @@ process.on('unhandledRejection', (reason, p) => {
process.exit(1);
});
const getFlagValueFromArgs = (args: string[], flag: string, defaultValue: string|null) => {
const getFlagValueFromArgs = (args, flag, defaultValue) => {
if (!args) return null;
const index = args.indexOf(flag);
if (index <= 0 || index >= args.length - 1) return defaultValue;
@@ -53,10 +52,6 @@ const { rootProfileDir } = determineBaseAppDirs(profileFromArgs, appName, altIns
// various places early in the initialisation code.
mkdirpSync(rootProfileDir);
// Required for correct display of Windows notifications. Should be done near the beginning of startup. See
// https://www.electron.build/nsis.html#guid-vs-application-name
electronApp.setAppUserModelId(appId);
const settingsPath = `${rootProfileDir}/settings.json`;
let autoUploadCrashDumps = false;
@@ -79,13 +74,7 @@ const wrapper = new ElectronAppWrapper(electronApp, {
env, profilePath: rootProfileDir, isDebugMode, initialCallbackUrl, isEndToEndTesting,
});
type ExtendedGlobal = {
joplinBridge: unknown;
};
(globalThis as unknown as ExtendedGlobal).joplinBridge = (
initBridge(wrapper, appId, appName, rootProfileDir, autoUploadCrashDumps, altInstanceId)
);
globalThis.joplinBridge = initBridge(wrapper, appId, appName, rootProfileDir, autoUploadCrashDumps, altInstanceId);
wrapper.start().catch((error) => {
console.error('Electron App fatal error:');

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "3.4.11",
"version": "3.4.1",
"description": "Joplin for Desktop",
"main": "main.bundle.js",
"private": true,
@@ -12,7 +12,7 @@
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"start": "gulp before-start && JOPLIN_SOURCE_MAP_ENABLED=1 electron . --env dev --log-level debug --open-dev-tools --no-welcome",
"start": "gulp before-start && electron . --env dev --log-level debug --open-dev-tools --no-welcome",
"test": "jest",
"test-ui": "gulp before-start && playwright test",
"test-ci": "yarn test",
@@ -132,8 +132,8 @@
"devDependencies": {
"7zip-bin": "5.2.0",
"@axe-core/playwright": "4.10.1",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.2",
"@electron/notarize": "2.3.2",
"@electron/rebuild": "3.6.0",
"@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4",
"@joplin/default-plugins": "~3.4",
@@ -142,14 +142,14 @@
"@joplin/renderer": "~3.4",
"@joplin/tools": "~3.4",
"@joplin/utils": "~3.4",
"@playwright/test": "1.52.0",
"@playwright/test": "1.51.1",
"@sentry/electron": "4.24.0",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.14",
"@types/mustache": "4.2.6",
"@types/node": "18.19.103",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
"@types/jest": "29.5.12",
"@types/mustache": "4.2.5",
"@types/node": "18.19.67",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/react-redux": "7.1.33",
"@types/styled-components": "5.1.32",
"@types/tesseract.js": "2.0.0",
@@ -160,13 +160,13 @@
"compare-versions": "6.1.1",
"countable": "3.0.1",
"debounce": "1.2.1",
"electron": "35.7.5",
"electron": "35.5.1",
"electron-builder": "24.13.3",
"electron-updater": "6.6.2",
"electron-updater": "6.2.1",
"electron-window-state": "5.0.3",
"esbuild": "^0.25.3",
"formatcoords": "1.1.3",
"glob": "11.0.2",
"glob": "10.4.5",
"gulp": "4.0.2",
"highlight.js": "11.11.1",
"immer": "9.0.21",
@@ -179,15 +179,16 @@
"moment": "2.30.1",
"mustache": "4.2.0",
"nan": "2.22.2",
"node-fetch": "2.6.7",
"node-notifier": "10.0.1",
"node-rsa": "1.1.1",
"pdfjs-dist": "3.11.174",
"pretty-bytes": "5.6.0",
"re-resizable": "6.11.2",
"re-resizable": "6.9.17",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-redux": "8.1.3",
"react-select": "5.10.1",
"react-select": "5.8.0",
"react-test-renderer": "18.3.1",
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",
@@ -195,22 +196,20 @@
"reselect": "4.1.8",
"roboto-fontface": "0.10.0",
"smalltalk": "2.5.1",
"source-map-support": "0.5.21",
"styled-components": "5.3.11",
"styled-system": "5.1.5",
"taboverride": "4.0.3",
"tesseract.js": "5.1.1",
"tinymce": "6.8.5",
"ts-jest": "29.3.1",
"ts-jest": "29.1.5",
"ts-node": "10.9.2",
"typescript": "5.8.2"
"typescript": "5.4.5"
},
"dependencies": {
"@electron/remote": "2.1.2",
"@joplin/onenote-converter": "~3.4",
"fs-extra": "11.2.0",
"keytar": "7.9.0",
"node-fetch": "2.6.7",
"sqlite3": "5.1.6"
}
}

View File

@@ -345,8 +345,8 @@ class DialogComponent extends React.PureComponent<Props, State> {
return {
id: result.commandName,
title: result.title,
parent_id: null as string,
fields: [] as string[],
parent_id: null,
fields: [],
type: BaseModel.TYPE_COMMAND,
};
});

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